Select completed source-to-bundle mappings and candidates through the shared view owner, preserve reference-file provenance, and retain full Evidence. Reuse bounded JSON reading, heap admission, and cancellation before session writes. Verified public Redux Toolkit source/map facts with an independent oracle, equivalent CLI/MCP workflows, focused negative controls, and complete CI/review. Historical paired SDK context estimates fall about 64%; timing/resource tradeoffs and final verification receipts are recorded without a general speedup or actual model-token claim. Fixes #6112.
380 lines
12 KiB
TypeScript
380 lines
12 KiB
TypeScript
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
|
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { expect, it, onTestFinished } from "vitest";
|
|
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
import { createServer } from "../../../src/server/createServer.js";
|
|
import { parseEvidence } from "../../../src/domain/evidence.js";
|
|
import { sourceToBundleComparisonResultSchema } from "../../../src/domain/javascript/sourceToBundleComparisonSchemas.js";
|
|
import { analysisViewResultSchema } from "../../../src/domain/analysisView/analysisView.js";
|
|
import { createTestBinarySession } from "../../fixtures/binarySession.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { importReferenceSource } from "../../support/referenceSourceResourceScope.js";
|
|
import { analyzeJavaScriptApplication } from "../../support/javascriptApplicationScope.js";
|
|
|
|
it.each(["full", "compact"] as const)(
|
|
"selects real source-map correspondence with stable identities and full parent retention (%s)",
|
|
async (profile) => {
|
|
const { root, path, reference, session, client, inputs, call } =
|
|
await comparisonFixture(profile);
|
|
const parent = await call("compare_source_to_bundle", inputs);
|
|
const full = sourceToBundleComparisonResultSchema.parse(
|
|
parent.normalized_result,
|
|
);
|
|
expect(full.summary).toMatchObject({ split: 1, unknown: 2 });
|
|
const fileParent = await call("compare_source_to_bundle", {
|
|
...inputs,
|
|
reference: { kind: "file", path },
|
|
});
|
|
expect(fileParent.normalized_result).toEqual(parent.normalized_result);
|
|
expect(fileParent.parameters.reference_source).toEqual({
|
|
kind: "file",
|
|
path,
|
|
requested_path: path,
|
|
});
|
|
expect(fileParent.evidence_id).not.toBe(parent.evidence_id);
|
|
const secondPath = join(root, "same-graph.json");
|
|
await writeFile(secondPath, JSON.stringify(reference.value));
|
|
const secondParent = await call("compare_source_to_bundle", {
|
|
...inputs,
|
|
reference: { kind: "file", path: secondPath },
|
|
});
|
|
expect(secondParent.normalized_result).toEqual(parent.normalized_result);
|
|
expect(secondParent.evidence_id).not.toBe(fileParent.evidence_id);
|
|
const summary = await call("compare_source_to_bundle", {
|
|
...inputs,
|
|
view: { kind: "summary" },
|
|
});
|
|
expect(summary.normalized_result).toMatchObject({
|
|
kind: "summary",
|
|
parent_evidence_id: parent.evidence_id,
|
|
summary: { summary: full.summary, source_coverage: full.coverage },
|
|
});
|
|
const { tools } = await client.listTools();
|
|
const ajv = new Ajv2020({ strict: false, validateFormats: false });
|
|
const comparisonTool = tools.find(
|
|
(tool) => tool.name === "compare_source_to_bundle",
|
|
)!;
|
|
const viewTool = tools.find(
|
|
(tool) => tool.name === "inspect_analysis_view",
|
|
)!;
|
|
for (const tool of [comparisonTool, viewTool]) {
|
|
expect(ajv.validateSchema(tool.inputSchema)).toBe(true);
|
|
const outputSchema: Record<string, unknown> = tool.outputSchema!;
|
|
expect(ajv.validateSchema(outputSchema)).toBe(true);
|
|
}
|
|
const request = {
|
|
kind: "source-to-bundle",
|
|
collection: "mappings",
|
|
source_paths: ["src/a.js"],
|
|
fields: ["status", "candidate_count"],
|
|
limit: 1,
|
|
};
|
|
expect(
|
|
ajv.compile(comparisonTool.inputSchema)({ ...inputs, view: request }),
|
|
).toBe(true);
|
|
const selected = await call("compare_source_to_bundle", {
|
|
...inputs,
|
|
reference: { kind: "file", path },
|
|
view: request,
|
|
});
|
|
const outputSchema: Record<string, unknown> = comparisonTool.outputSchema!;
|
|
expect(ajv.compile(outputSchema)(selected)).toBe(true);
|
|
const page = analysisViewResultSchema.parse(selected.normalized_result);
|
|
expect(page).toMatchObject({
|
|
kind: "source-to-bundle",
|
|
parent_evidence_id: fileParent.evidence_id,
|
|
source_coverage: full.coverage,
|
|
items: [
|
|
{
|
|
mapping_id: full.items[0]!.mapping_id,
|
|
source_index: 0,
|
|
source_path: "src/a.js",
|
|
status: "split",
|
|
candidate_count: 2,
|
|
},
|
|
],
|
|
coverage: { total: 1, exhausted: true },
|
|
});
|
|
expect(session.evidenceById(fileParent.evidence_id)).toEqual(fileParent);
|
|
const source = {
|
|
kind: "retained-evidence",
|
|
evidence_id: fileParent.evidence_id,
|
|
};
|
|
await assertRetainedPages(call, source, request, full);
|
|
await writeFile(
|
|
path,
|
|
JSON.stringify({ ...reference.value, root_sha256: "0".repeat(64) }),
|
|
);
|
|
expect(
|
|
(
|
|
await client.callTool({
|
|
name: "compare_source_to_bundle",
|
|
arguments: { ...inputs, reference: { kind: "file", path } },
|
|
})
|
|
).isError,
|
|
).toBe(true);
|
|
for (const value of [
|
|
{ kind: "file", path: "relative.json" },
|
|
{ kind: "file", path: root },
|
|
{ kind: "file", path: join(root, "missing.json") },
|
|
])
|
|
expect(
|
|
(
|
|
await client.callTool({
|
|
name: "compare_source_to_bundle",
|
|
arguments: { ...inputs, reference: value },
|
|
})
|
|
).isError,
|
|
).toBe(true);
|
|
expect(
|
|
(
|
|
await client.callTool({
|
|
name: "inspect_analysis_view",
|
|
arguments: { source, view: { ...request, fields: ["not_a_field"] } },
|
|
})
|
|
).isError,
|
|
).toBe(true);
|
|
},
|
|
);
|
|
|
|
const comparisonFixture = async (profile: "full" | "compact") => {
|
|
const root = await createTestTempDirectory("rea-source-view-");
|
|
const previous = join(root, "previous"),
|
|
current = join(root, "current");
|
|
await mkdir(join(previous, "src"), { recursive: true });
|
|
await mkdir(join(previous, "src/other"), { recursive: true });
|
|
await writeFile(
|
|
join(previous, "src/other/a.js"),
|
|
"export const unrelated = 3;\n",
|
|
);
|
|
await mkdir(join(current, "dist"), { recursive: true });
|
|
await writeFile(join(previous, "src/a.js"), "export const value = 1;\n");
|
|
await writeFile(
|
|
join(previous, "src/unseen.js"),
|
|
"export const unseen = 1;\n",
|
|
);
|
|
for (const name of ["one", "two"]) {
|
|
await writeFile(
|
|
join(current, `dist/${name}.js`),
|
|
`var value = 2;\n//# sourceMappingURL=${name}.js.map\n`,
|
|
);
|
|
await writeFile(
|
|
join(current, `dist/${name}.js.map`),
|
|
JSON.stringify({
|
|
version: 3,
|
|
file: `${name}.js`,
|
|
sources: ["../src/a.js"],
|
|
sourcesContent: ["export const value = 2;\n"],
|
|
names: [],
|
|
mappings: "AAAA",
|
|
}),
|
|
);
|
|
}
|
|
await writeFile(join(previous, "src/b.js"), "export const other = 1;\n");
|
|
await writeFile(
|
|
join(current, "dist/three.js"),
|
|
"var other = 2;\n//# sourceMappingURL=three.js.map\n",
|
|
);
|
|
await writeFile(
|
|
join(current, "dist/three.js.map"),
|
|
JSON.stringify({
|
|
version: 3,
|
|
file: "three.js",
|
|
sources: ["../src/b.js"],
|
|
sourcesContent: ["export const other = 2;\n"],
|
|
names: [],
|
|
mappings: "AAAA",
|
|
}),
|
|
);
|
|
const reference = await importReferenceSource({
|
|
root: previous,
|
|
caller: "source-view-test",
|
|
policy: { secretPatterns: [] },
|
|
});
|
|
if (!reference.ok) throw reference.error;
|
|
const application = await analyzeJavaScriptApplication({
|
|
input_path: current,
|
|
max_semantic_nodes: 1,
|
|
max_semantic_nodes_per_file: 1,
|
|
});
|
|
if (!application.ok) throw application.error;
|
|
const path = join(root, "reference.json");
|
|
await writeFile(path, JSON.stringify(reference.value));
|
|
const session = createTestBinarySession(() => {
|
|
throw new Error("Views must not open a native provider");
|
|
});
|
|
const server = createServer(
|
|
{ kind: "session", session },
|
|
{
|
|
environment: { ...process.env, REA_MCP_INPUT_SCHEMA_PROFILE: profile },
|
|
},
|
|
);
|
|
const client = new Client({ name: "source-view", version: "1" });
|
|
onTestFinished(async () => {
|
|
await client.close();
|
|
await server.close();
|
|
await session.close();
|
|
});
|
|
const [a, b] = InMemoryTransport.createLinkedPair();
|
|
await server.connect(b);
|
|
await client.connect(a);
|
|
expect(session.recordEvidence(application.value).ok).toBe(true);
|
|
const inputs = {
|
|
reference: reference.value,
|
|
application: {
|
|
kind: "retained-evidence",
|
|
evidence_id: application.value.evidence_id,
|
|
},
|
|
};
|
|
const call = async (name: string, args: Record<string, unknown>) => {
|
|
const reply = await client.callTool({ name, arguments: args });
|
|
expect(reply.isError, JSON.stringify(reply)).not.toBe(true);
|
|
return parseEvidence(reply.structuredContent);
|
|
};
|
|
return { root, path, reference, session, client, inputs, call };
|
|
};
|
|
|
|
const assertRetainedPages = async (
|
|
call: Awaited<ReturnType<typeof comparisonFixture>>["call"],
|
|
source: Record<string, unknown>,
|
|
request: Record<string, unknown>,
|
|
full: ReturnType<typeof sourceToBundleComparisonResultSchema.parse>,
|
|
) => {
|
|
const view = async (value: Record<string, unknown>) =>
|
|
analysisViewResultSchema.parse(
|
|
(await call("inspect_analysis_view", { source, view: value }))
|
|
.normalized_result,
|
|
);
|
|
for (const selector of [
|
|
{ source_paths: [] },
|
|
{ mapping_ids: [] },
|
|
{ statuses: [] },
|
|
{ source_paths: ["Src/a.js"] },
|
|
{ offset: 100 },
|
|
])
|
|
expect(
|
|
await view({
|
|
kind: "source-to-bundle",
|
|
collection: "mappings",
|
|
...selector,
|
|
}),
|
|
).toMatchObject({ items: [], coverage: { exhausted: true } });
|
|
const identity = await view({ ...request, fields: [] });
|
|
expect(identity).toMatchObject({
|
|
items: [
|
|
{
|
|
mapping_id: full.items[0]!.mapping_id,
|
|
source_path: "src/a.js",
|
|
source_index: 0,
|
|
},
|
|
],
|
|
});
|
|
if (identity.kind !== "source-to-bundle") throw new Error("Expected page");
|
|
expect(Object.keys(identity.items[0]!)).toEqual([
|
|
"mapping_id",
|
|
"source_path",
|
|
"source_index",
|
|
]);
|
|
for (let offset = 0; offset < 2; offset++) {
|
|
const candidate = full.items[0]!.candidates[offset]!;
|
|
expect(
|
|
await view({
|
|
kind: "source-to-bundle",
|
|
collection: "candidates",
|
|
mapping_ids: [full.items[0]!.mapping_id],
|
|
fields: ["signals"],
|
|
offset,
|
|
limit: 1,
|
|
}),
|
|
).toMatchObject({
|
|
items: [
|
|
{
|
|
mapping_id: full.items[0]!.mapping_id,
|
|
candidate_index: offset,
|
|
current_node_id: candidate.current_node_id,
|
|
mapped: full.items[0]!.current_node_ids.includes(
|
|
candidate.current_node_id,
|
|
),
|
|
signals: candidate.signals,
|
|
},
|
|
],
|
|
coverage: { total: 2, next_offset: offset === 0 ? 1 : null },
|
|
});
|
|
}
|
|
await assertCandidateBoundaryPages(view);
|
|
const weak = await view({
|
|
kind: "source-to-bundle",
|
|
collection: "candidates",
|
|
source_paths: ["src/other/a.js"],
|
|
fields: ["confidence"],
|
|
});
|
|
expect(weak).toMatchObject({
|
|
items: [
|
|
{ source_path: "src/other/a.js", mapped: false, confidence: "low" },
|
|
{ source_path: "src/other/a.js", mapped: false, confidence: "low" },
|
|
],
|
|
coverage: { total: 2, exhausted: true },
|
|
});
|
|
expect(await view({ kind: "summary" })).toMatchObject({
|
|
summary: {
|
|
summary: full.summary,
|
|
source_coverage: full.coverage,
|
|
unmapped_current_node_count: full.unmapped_current_node_ids.length,
|
|
},
|
|
});
|
|
};
|
|
|
|
const assertCandidateBoundaryPages = async (
|
|
view: (
|
|
value: Record<string, unknown>,
|
|
) => Promise<ReturnType<typeof analysisViewResultSchema.parse>>,
|
|
) => {
|
|
const boundary = await view({
|
|
kind: "source-to-bundle",
|
|
collection: "candidates",
|
|
fields: [],
|
|
offset: 1,
|
|
limit: 3,
|
|
});
|
|
expect(boundary).toMatchObject({
|
|
items: [
|
|
{
|
|
source_path: "src/a.js",
|
|
source_index: 0,
|
|
candidate_index: 1,
|
|
mapped: true,
|
|
},
|
|
{
|
|
source_path: "src/b.js",
|
|
source_index: 1,
|
|
candidate_index: 0,
|
|
mapped: true,
|
|
},
|
|
{
|
|
source_path: "src/other/a.js",
|
|
source_index: 2,
|
|
candidate_index: 0,
|
|
mapped: false,
|
|
},
|
|
],
|
|
coverage: { total: 5, next_offset: 4, exhausted: false },
|
|
});
|
|
expect(
|
|
await view({
|
|
kind: "source-to-bundle",
|
|
collection: "candidates",
|
|
source_paths: ["src/a.js", "src/b.js"],
|
|
fields: [],
|
|
offset: 1,
|
|
limit: 2,
|
|
}),
|
|
).toMatchObject({
|
|
items: [
|
|
{ source_path: "src/a.js", source_index: 0, candidate_index: 1 },
|
|
{ source_path: "src/b.js", source_index: 1, candidate_index: 0 },
|
|
],
|
|
coverage: { total: 3, next_offset: null, exhausted: true },
|
|
});
|
|
};
|