fix(cli): load Evidence record pairs from JSON files (#5129)
* fix(cli): read explicit Evidence record pairs from JSON files * test(cli): isolate record-pair file regression * style(cli): format record-pair file regression * test(cli): reuse comparison fixture for record-pair file input * style(cli): format reused record-pair test invocation * test(cli): keep paired-input coverage within lint function limit
This commit is contained in:
1 parent
fe9797b487
commit
1902fcb13d
3 files changed
+38
-44
No files matched your search
+6
-4
@@ -744,10 +744,12 @@ not read artifact contents: the inventories of two builds are unresolved
|
||||
records, or one opaque change when you pair them. Use `compare-artifacts`,
|
||||
described at the end of this section, to compare their contents.
|
||||
|
||||
`--record_pairs` supplies an explicit JSON array of observation pairs, matching
|
||||
MCP `compare_bundles.record_pairs`. Pair IDs must exist on their named sides and
|
||||
be one-to-one. Without pairs, one-sided membership remains unknown; REA does
|
||||
not infer correspondence from position or display names.
|
||||
`--record_pairs` accepts an inline JSON array or the path to a local JSON
|
||||
file containing that array, matching MCP `compare_bundles.record_pairs`. For a
|
||||
file, use `rea compare left.json right.json --record_pairs pairs.json --json`.
|
||||
Pair IDs must exist on their named sides and be one-to-one. Without pairs,
|
||||
one-sided membership remains unknown; REA does not infer correspondence from
|
||||
position or display names.
|
||||
|
||||
Exports preserve an existing destination unless `--overwrite` is explicit.
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { withCommandCancellation } from "./cli/commandCancellation.js";
|
||||
import { safeParseJson } from "./domain/safeJson.js";
|
||||
import { AnalysisInputError } from "./domain/analysisErrorCore.js";
|
||||
import { parseCliJsonInput } from "./cliJsonInput.js";
|
||||
import { analysisInputErrorFromIssues } from "./domain/inputIssueProjection.js";
|
||||
import { bundleComparisonInputSchema } from "./domain/bundleComparison.js";
|
||||
import { Cli, z } from "incur";
|
||||
@@ -72,7 +71,7 @@ export const registerEvidenceCommands = (
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
"JSON array of explicit left_evidence_id/right_evidence_id observation pairs",
|
||||
"Inline JSON array or local JSON file of left_evidence_id/right_evidence_id pairs",
|
||||
),
|
||||
}),
|
||||
run: ({ args, options }) =>
|
||||
@@ -81,21 +80,12 @@ export const registerEvidenceCommands = (
|
||||
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",
|
||||
},
|
||||
],
|
||||
),
|
||||
);
|
||||
: await parseCliJsonInput(
|
||||
options.record_pairs,
|
||||
"compare_bundles",
|
||||
signal,
|
||||
);
|
||||
if (!decoded.ok) return decoded.error;
|
||||
const input = { record_pairs: decoded.value };
|
||||
const selected = bundleComparisonInputSchema
|
||||
.pick({ record_pairs: true })
|
||||
|
||||
@@ -123,16 +123,10 @@ cliTest(
|
||||
right_evidence_id: different.records[0]!.evidence_id,
|
||||
},
|
||||
];
|
||||
const paired = await cli.run({
|
||||
arguments: [
|
||||
"compare",
|
||||
leftPath,
|
||||
changedPath,
|
||||
"--record_pairs",
|
||||
JSON.stringify(pairs),
|
||||
"--json",
|
||||
],
|
||||
});
|
||||
const pairArgs = ["compare", leftPath, changedPath, "--record_pairs"];
|
||||
const compareWithPairs = (value: string) =>
|
||||
cli.run({ arguments: [...pairArgs, value, "--json"] });
|
||||
const paired = await compareWithPairs(JSON.stringify(pairs));
|
||||
expect(paired.exitCode).toBe(0);
|
||||
const pairedResult = bundleComparisonResultSchema.parse(paired.json);
|
||||
expect(pairedResult).toMatchObject({
|
||||
@@ -142,6 +136,21 @@ cliTest(
|
||||
{ classification: "changed", conclusion_kind: "observed_change" },
|
||||
],
|
||||
});
|
||||
const pairFile = join(root, "pairs.json");
|
||||
await writeFile(pairFile, JSON.stringify(pairs));
|
||||
const fromFile = await compareWithPairs(pairFile);
|
||||
expect(fromFile.exitCode).toBe(0);
|
||||
expect(bundleComparisonResultSchema.parse(fromFile.json)).toEqual(
|
||||
pairedResult,
|
||||
);
|
||||
const missingFile = join(root, "missing-pairs.json");
|
||||
const absentFile = await compareWithPairs(missingFile);
|
||||
expect(absentFile.exitCode).toBe(1);
|
||||
expect(absentFile.json).toMatchObject({
|
||||
code: "invalid_request",
|
||||
input_path: missingFile,
|
||||
input_reason: "read-failed",
|
||||
});
|
||||
const call = await cli.sharedMcpCall?.();
|
||||
if (call === undefined) throw new Error("Expected shared MCP session");
|
||||
const mcp = await call("compare_bundles", {
|
||||
@@ -155,18 +164,11 @@ cliTest(
|
||||
parseEvidence(mcp.structuredContent).normalized_result,
|
||||
),
|
||||
).toEqual(pairedResult);
|
||||
const absentPair = await cli.run({
|
||||
arguments: [
|
||||
"compare",
|
||||
leftPath,
|
||||
changedPath,
|
||||
"--record_pairs",
|
||||
JSON.stringify([
|
||||
{ ...pairs[0], left_evidence_id: `ev_${"f".repeat(64)}` },
|
||||
]),
|
||||
"--json",
|
||||
],
|
||||
});
|
||||
const absentPair = await compareWithPairs(
|
||||
JSON.stringify([
|
||||
{ ...pairs[0], left_evidence_id: `ev_${"f".repeat(64)}` },
|
||||
]),
|
||||
);
|
||||
expect(absentPair.exitCode).toBe(1);
|
||||
expect(absentPair.json).toMatchObject({
|
||||
code: "invalid_request",
|
||||
|
||||
Reference in new issue
Block a user