Files
reverse-engineering-anything/tests/acceptance/applications/applicationWorkflowCli.test.ts
T
JNTMTMTM ab13a1c67a fix(javascript): include response carriers in endpoint traces
Include request and response endpoint carriers in the shared semantic query seed matcher so traces retain resolved response consumers, source identities, and incomplete coverage.

Validated with 44 domain tests, compiled CLI/MCP parity, full static/architecture checks, and current-head automated review/CI.

Fixes #4213.
2026-10-17 01:11:36 +08:00

1770 lines
61 KiB
TypeScript

import { execFile } from "node:child_process";
import { createHash } from "node:crypto";
import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
import { createRequire } from "node:module";
import { join, relative, resolve } from "node:path";
import { pathToFileURL } from "node:url";
import { promisify } from "node:util";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { afterAll, describe, expect, it } from "vitest";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { writeElectronApiAliasApplication } from "../../fixtures/electronApiAliasApplication.js";
import {
JAVASCRIPT_FEATURE_TRACE_EXAMPLE,
JAVASCRIPT_APPLICATION_VERSION_COMPARISON_EXAMPLE,
SOURCE_TO_BUNDLE_COMPARISON_EXAMPLE,
} from "../../../src/contracts/javascript/javascriptApplicationWorkflowExamples.js";
import { analyzeJavaScriptApplication } from "../../support/javascriptApplicationScope.js";
import { parseEvidence } from "../../../src/domain/evidence.js";
import {
javascriptApplicationAnalysisResultSchema,
type JavaScriptApplicationAnalysisResult,
} from "../../../src/domain/javascript/javascriptApplicationAnalysis.js";
import { parseJavaScriptApplicationGraph } from "../../../src/domain/javascript/javascriptApplicationGraph.js";
import { z } from "zod";
import { ELECTRON_IDENTITY_LIMITATION } from "../../../src/domain/javascript/javascriptElectronMemberWrites.js";
import { javaScriptSemanticTraceResultSchema } from "../../../src/domain/javascript/javascriptSemanticTraceSchemas.js";
import type { JavaScriptSemanticQueryInput } from "../../../src/domain/javascript/javascriptSemanticQuerySchemas.js";
const execute = promisify(execFile);
const requireFixture = createRequire(import.meta.url);
// Analysis requests are stateless; retain one compiled server and its SDK
// validators while every scenario still owns a separate application directory.
const mcpTransport = new StdioClientTransport({
command: process.execPath,
args: [resolve("scripts/rea.mjs"), "mcp"],
cwd: process.cwd(),
env: { PATH: process.env.PATH ?? "", REA_LOG_LEVEL: "silent" },
stderr: "pipe",
});
mcpTransport.stderr?.on("data", () => undefined);
const mcpClient = new Client({
name: "application-workflow-parity",
version: "1",
});
let mcpConnection: Promise<void> | undefined;
const connectedMcpClient = async (): Promise<Client> => {
await (mcpConnection ??= mcpClient.connect(mcpTransport));
return mcpClient;
};
afterAll(async () => {
try {
await mcpClient.close();
} finally {
await mcpTransport.close();
}
});
const WEBPACK_FACTORY_BODY = `
const key = "dynamic";
const __webpack_require__ = {
d(target, definitions) {
for (const name of Object.keys(definitions))
Object.defineProperty(target, name, {
enumerable: true,
get: definitions[name],
});
},
};
exports[key] = 1;
exports["dot.key"] = 2;
exports[""] = 3;
exports.nested = {};
exports.nested.child = 6;
exports["nested.child"] = 7;
module.exports["module.dot"] = 5;
module["exports.decoy"] = 99;
Object.defineProperty(exports, "", {
value: 4,
configurable: true,
enumerable: true,
});
__webpack_require__.d(exports, { "from.helper": () => 7 });
exports = function ignored() { return 8; };
`;
const writeSelfImportFixture = async (root: string): Promise<void> => {
await Promise.all([
writeFile(
join(root, "esm.js"),
'import "./esm.js"; export const value = 1;\n',
),
writeFile(
join(root, "common.cjs"),
'const self = require("./common.cjs"); module.exports = self;\n',
),
writeFile(
join(root, "util.ts"),
'import "./util.ts"; export const value = 1;\n',
),
]);
};
const expectSelfImportEvidence = (
graph: JavaScriptApplicationAnalysisResult["graph"],
): void => {
const rawSelfImports = graph.edges.filter(
({ relation, properties, evidence }) =>
relation === "imports" &&
(properties.kind === "static-import" || properties.kind === "require") &&
evidence.location.available &&
evidence.location.value.kind === "source-range" &&
((properties.specifier === "./esm.js" &&
properties.kind === "static-import" &&
evidence.location.value.source === "esm.js") ||
(properties.specifier === "./common.cjs" &&
properties.kind === "require" &&
evidence.location.value.source === "common.cjs") ||
(properties.specifier === "./util.ts" &&
properties.kind === "static-import" &&
evidence.location.value.source === "util.ts")),
);
expect(rawSelfImports).toEqual(
expect.arrayContaining([
expect.objectContaining({
properties: expect.objectContaining({
kind: "static-import",
specifier: "./esm.js",
}),
evidence: expect.objectContaining({
authority: "static-relationship-inference",
location: expect.objectContaining({
available: true,
value: expect.objectContaining({
kind: "source-range",
source: "esm.js",
start: expect.objectContaining({ line: 1, column: 0 }),
}),
}),
}),
}),
expect.objectContaining({
properties: expect.objectContaining({
kind: "require",
specifier: "./common.cjs",
}),
evidence: expect.objectContaining({
authority: "static-relationship-inference",
location: expect.objectContaining({
available: true,
value: expect.objectContaining({
kind: "source-range",
source: "common.cjs",
start: expect.objectContaining({ line: 1, column: 13 }),
}),
}),
}),
}),
expect.objectContaining({
properties: expect.objectContaining({
kind: "static-import",
specifier: "./util.ts",
}),
evidence: expect.objectContaining({
authority: "static-relationship-inference",
location: expect.objectContaining({
available: true,
value: expect.objectContaining({
kind: "source-range",
source: "util.ts",
start: expect.objectContaining({ line: 1, column: 0 }),
}),
}),
}),
}),
]),
);
expect(rawSelfImports).toHaveLength(3);
};
const analyzeThroughStdioMcp = async (
inputPath: string,
): Promise<JavaScriptApplicationAnalysisResult> => {
const client = await connectedMcpClient();
const response = await client.callTool({
name: "analyze_javascript_application",
arguments: { input_path: inputPath },
});
expect(response.isError).not.toBe(true);
expect(response.content).toEqual(
expect.arrayContaining([expect.objectContaining({ type: "text" })]),
);
const result = z
.object({ normalized_result: javascriptApplicationAnalysisResultSchema })
.parse(response.structuredContent).normalized_result;
expect(result).toMatchObject({
input_path: inputPath,
format: "directory",
});
return result;
};
const expectCliAnalysisParity = async (
root: string,
mcp: JavaScriptApplicationAnalysisResult,
): Promise<void> => {
const cli = z
.object({ normalized_result: javascriptApplicationAnalysisResultSchema })
.parse(
await runCli(["analyze-javascript-application", root, "--json"]),
).normalized_result;
expect(cli).toEqual(mcp);
};
describe("Electron alias identity through stdio MCP", () => {
it.each([
{
name: "CommonJS binding chains",
cliParity: true,
esm: false,
main: 'const { BrowserWindow, ipcMain } = require("electron"); const First = BrowserWindow; const Win = First; const ipc = ipcMain;',
preload:
'const { contextBridge, ipcRenderer } = require("electron"); const First = contextBridge; const bridge = First; const ipc = ipcRenderer;',
},
{
name: "static namespace members",
esm: false,
main: 'const electron = require("electron"); const copied = electron; copied.unrelated = {}; const Win = copied["BrowserWindow"]; const ipc = copied.ipcMain;',
preload:
'const electron = require("electron"); const copied = electron; const bridge = copied.contextBridge; const ipc = copied["ipcRenderer"];',
},
{
name: "namespace destructuring",
esm: false,
main: 'const electron = require("electron"); const { BrowserWindow: Win, ipcMain: ipc } = electron;',
preload:
'const electron = require("electron"); const { contextBridge: bridge, ipcRenderer: ipc } = electron;',
},
{
name: "unwritten let and var bindings",
esm: false,
main: 'const { BrowserWindow, ipcMain } = require("electron"); let Win = BrowserWindow; var ipc = ipcMain;',
preload:
'const { contextBridge, ipcRenderer } = require("electron"); var bridge = contextBridge; let ipc = ipcRenderer;',
},
{
name: "ESM binding aliases",
cliParity: true,
esm: true,
main: 'import { BrowserWindow, ipcMain } from "electron"; const Win = BrowserWindow; const ipc = ipcMain;',
preload:
'import { contextBridge, ipcRenderer } from "electron"; const bridge = contextBridge; const ipc = ipcRenderer;',
},
])(
"recovers Electron boundaries through $name (#1656)",
async ({ cliParity, main, preload, esm }) => {
const root = await createTestTempDirectory("rea-electron-alias-cli-");
await Promise.all([
writeFile(
join(root, "package.json"),
JSON.stringify({
name: "alias-repro",
version: "1.0.0",
main: "main.js",
type: esm ? "module" : "commonjs",
}),
),
writeFile(
join(root, "main.js"),
`${main}\n${esm ? 'import { fileURLToPath } from "node:url";' : 'const path = require("node:path");'}\nnew Win({ webPreferences: { preload: ${esm ? 'fileURLToPath(new URL("./preload.js", import.meta.url))' : 'path.join(__dirname, "preload.js")'} } });\nipc.handle("ch:ping", async () => "pong");\nfunction shadow(Win, ipc) { new Win(); ipc.handle("ch:decoy", () => null); }\n`,
),
writeFile(
join(root, "preload.js"),
`${preload}\nbridge.exposeInMainWorld("api", { ping: () => ipc.invoke("ch:ping") });\nfunction shadow(bridge, ipc) { bridge.exposeInMainWorld("decoy", {}); ipc.invoke("ch:decoy"); }\n`,
),
]);
const result = await analyzeThroughStdioMcp(root);
// Keep CLI parity for both module formats; the other rows vary only AST
// semantics already exercised by the same public analysis workflow.
if (cliParity) await expectCliAnalysisParity(root, result);
expect(result.summary).toMatchObject({
browser_windows: 1,
explicit_web_preferences: 1,
preload_entrypoints: 1,
context_bridge_apis: 1,
exposed_api_members: 1,
ipc: {
operations: 2,
literal_channels: 1,
main_handlers: 1,
renderer_transmissions: 1,
paired_renderer_transmissions: 1,
},
});
expect(result.limitations).toContain(ELECTRON_IDENTITY_LIMITATION);
expect(result.graph.nodes).toEqual(
expect.arrayContaining([
expect.objectContaining({
kind: "ipc-channel",
observations: expect.arrayContaining([
expect.objectContaining({
properties: expect.objectContaining({
channel: "ch:ping",
operation: "handle",
}),
evidence: expect.objectContaining({
authority: "ast-static-analysis",
location: expect.objectContaining({
available: true,
value: expect.objectContaining({
kind: "source-range",
source: "main.js",
}),
}),
}),
}),
expect.objectContaining({
properties: expect.objectContaining({
channel: "ch:ping",
operation: "invoke",
}),
evidence: expect.objectContaining({
authority: "ast-static-analysis",
location: expect.objectContaining({
available: true,
value: expect.objectContaining({
kind: "source-range",
source: "preload.js",
}),
}),
}),
}),
]),
}),
]),
);
expect(result.graph.edges).toEqual(
expect.arrayContaining([
expect.objectContaining({ relation: "handles" }),
expect.objectContaining({ relation: "invokes" }),
expect.objectContaining({ relation: "loads" }),
expect.objectContaining({ relation: "exposes" }),
]),
);
},
);
});
describe("Electron unavailable identity through stdio MCP", () => {
it.each([
{
name: "reassigned aliases",
cliParity: true,
main: 'const { BrowserWindow, ipcMain } = require("electron"); let Win = BrowserWindow; let ipc = ipcMain; Win = class {}; ipc = { handle() {} };',
preload:
'const { contextBridge, ipcRenderer } = require("electron"); let bridge = contextBridge; let ipc = ipcRenderer; bridge = { exposeInMainWorld() {} }; ipc = { invoke() {} };',
},
{
name: "conditional initializers",
main: 'const { BrowserWindow, ipcMain } = require("electron"); if (enabled) { var Win = BrowserWindow; var ipc = ipcMain; }',
preload:
'const { contextBridge, ipcRenderer } = require("electron"); if (enabled) { var bridge = contextBridge; var ipc = ipcRenderer; }',
},
{
name: "dynamic property selections",
main: 'const electron = require("electron"); const Win = electron[windowKey]; const ipc = electron[ipcKey];',
preload:
'const electron = require("electron"); const bridge = electron[bridgeKey]; const ipc = electron[ipcKey];',
},
{
name: "overwritten namespace exports",
main: 'const electron = require("electron"); const copied = electron; copied.BrowserWindow = class {}; electron.ipcMain = { handle() {} }; const Win = electron.BrowserWindow; const ipc = copied.ipcMain;',
preload:
'const electron = require("electron"); electron.contextBridge = { exposeInMainWorld() {} }; electron.ipcRenderer = { invoke() {} }; const bridge = electron.contextBridge; const ipc = electron.ipcRenderer;',
},
{
name: "overwritten API methods",
main: 'const electron = require("electron"); const ipc = electron.ipcMain; ipc.handle = () => null; const Win = electron[windowKey];',
preload:
'const electron = require("electron"); const bridge = electron.contextBridge; delete bridge.exposeInMainWorld; const ipc = electron.ipcRenderer; ipc.invoke++;',
},
{
name: "direct require namespace writes",
main: 'require("electron").BrowserWindow = class {}; require("electron").ipcMain = {handle() {}}; const {BrowserWindow: Win, ipcMain: ipc} = require("electron");',
preload:
'require("electron").contextBridge = {}; require("electron").ipcRenderer = {}; const {contextBridge: bridge, ipcRenderer: ipc} = require("electron");',
},
{
name: "unknown namespace write keys",
main: 'const electron = require("electron"); electron[unknownKey] = {}; const {BrowserWindow: Win, ipcMain: ipc} = electron;',
preload:
'const electron = require("electron"); electron[unknownKey] = {}; const {contextBridge: bridge, ipcRenderer: ipc} = electron;',
},
{
name: "destructuring assignment targets",
main: 'const electron = require("electron"); ({ BrowserWindow: electron.BrowserWindow, ipcMain: electron.ipcMain } = replacement); const {BrowserWindow: Win, ipcMain: ipc} = electron;',
preload:
'const electron = require("electron"); [electron.contextBridge, electron.ipcRenderer] = replacements; const {contextBridge: bridge, ipcRenderer: ipc} = electron;',
},
{
name: "loop assignment targets",
main: 'const electron = require("electron"); for ({ BrowserWindow: electron.BrowserWindow, ipcMain: electron.ipcMain } of replacements) {} const {BrowserWindow: Win, ipcMain: ipc} = electron;',
preload:
'const electron = require("electron"); for (electron.contextBridge in replacements) {} for (electron.ipcRenderer of replacements) {} const {contextBridge: bridge, ipcRenderer: ipc} = electron;',
},
{
name: "literal keys containing dots",
main: 'const electron = require("electron"); const Win = electron["fake.BrowserWindow"]; const ipc = electron["fake.ipcMain"]; electron["ipcMain.handle"]("ch:literal", () => null);',
preload:
'const electron = require("electron"); const bridge = electron["fake.contextBridge"]; const ipc = electron["fake.ipcRenderer"];',
},
])(
"keeps Electron identity unresolved for $name (#1656)",
async ({ cliParity, main, preload }) => {
const root = await createTestTempDirectory("rea-electron-alias-unknown-");
await Promise.all([
writeFile(
join(root, "package.json"),
JSON.stringify({
name: "alias-unknown",
version: "1.0.0",
main: "main.js",
}),
),
writeFile(
join(root, "main.js"),
`${main}\nnew Win(); ipc.handle("ch:ping", () => "pong");\n`,
),
writeFile(
join(root, "preload.js"),
`${preload}\nbridge.exposeInMainWorld("api", { ping: () => ipc.invoke("ch:ping") });\n`,
),
]);
const result = await analyzeThroughStdioMcp(root);
if (cliParity) await expectCliAnalysisParity(root, result);
expect(result.summary).toMatchObject({
browser_windows: 0,
context_bridge_apis: 0,
ipc: { operations: 0 },
});
expect(
result.graph.nodes.some(({ kind }) => kind === "ipc-channel"),
).toBe(false);
expect(result.limitations).toContain(ELECTRON_IDENTITY_LIMITATION);
},
);
});
it("preserves complete line-comment source-map URLs through CLI and MCP", async () => {
const root = await createTestTempDirectory("rea-source-map-url-cli-");
const directives = [
"//# sourceMappingURL=app.js.map?variant=a*modern",
"//@ sourceMappingURL=app.js.map?variant=a*legacy",
"//# sourceMappingURL=app.js.map#first*section",
];
await writeFile(
join(root, "app.js"),
["export const value = 1;", ...directives, ""].join("\n"),
);
await writeFile(
join(root, "app.js.map"),
JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }),
);
const { graph } = z
.object({ normalized_result: javascriptApplicationAnalysisResultSchema })
.parse(
await runCli(["analyze-javascript-application", root, "--json"]),
).normalized_result;
const map = graph.nodes.find(
({ kind, observations }) =>
kind === "source-map" &&
observations.some(({ properties }) => properties.path === "app.js.map"),
);
const references = map?.observations.filter(
({ label }) => label === "source map reference",
);
expect(references).toHaveLength(directives.length);
for (const [index, directive] of directives.entries()) {
const declaredUrl = directive.slice(directive.indexOf("=") + 1);
const digest = createHash("sha256").update(declaredUrl).digest("hex");
expect(references).toContainEqual(
expect.objectContaining({
properties: {
declared_url: declaredUrl,
declared_url_sha256: digest,
resolved_path: "app.js.map",
resolution_status: "resolved",
available: true,
},
evidence: expect.objectContaining({
location: {
available: true,
value: {
kind: "source-range",
source: "app.js",
start: { line: index + 2, column: 0 },
end: { line: index + 2, column: directive.length },
},
},
}),
}),
);
expect(graph.edges).toContainEqual(
expect.objectContaining({
relation: "maps_to",
target_node_id: map?.node_id,
properties: {
declared_url_sha256: digest,
resolved_path: "app.js.map",
resolution_status: "resolved",
},
}),
);
}
expect((await analyzeThroughStdioMcp(root)).graph).toEqual(graph);
}, 20_000);
it("resolves source-map URL paths once without changing their declarations", async () => {
const root = await createTestTempDirectory("rea-source-map-encoding-cli-");
const cases = [
["space%20part.js.map", "space part.js.map"],
["literal%25.js.map", "literal%.js.map"],
["encoded%2520.js.map", "encoded%20.js.map"],
["space%20part.js.map?v=1#section", "space part.js.map"],
["caf%C3%A9.js.map", "café.js.map"],
["literal%ZZ.js.map", "literal%ZZ.js.map"],
] as const;
for (const path of new Set(cases.map(([, path]) => path))) {
await writeFile(
join(root, path),
JSON.stringify({
version: 3,
file: path,
sources: [],
names: [],
mappings: "",
}),
);
}
await writeFile(
join(root, "app.js"),
cases.map(([url]) => `//# sourceMappingURL=${url}\n`).join("") +
'new Worker(new URL("./worker%20file.js", import.meta.url));\n' +
'navigator.serviceWorker.register("./service%20worker.js");\n',
);
await writeFile(join(root, "worker file.js"), "export const worker = 1;\n");
await writeFile(
join(root, "service worker.js"),
"export const service = 2;\n",
);
const { graph } = z
.object({ normalized_result: javascriptApplicationAnalysisResultSchema })
.parse(
await runCli(["analyze-javascript-application", root, "--json"]),
).normalized_result;
for (const [index, [declaredUrl, path]] of cases.entries()) {
const map = graph.nodes.find(
({ kind, observations }) =>
kind === "source-map" &&
observations.some(({ properties }) => properties.path === path),
);
const digest = createHash("sha256").update(declaredUrl).digest("hex");
expect(map?.observations).toContainEqual(
expect.objectContaining({
properties: {
declared_url: declaredUrl,
declared_url_sha256: digest,
resolved_path: path,
resolution_status: "resolved",
available: true,
},
evidence: expect.objectContaining({
location: {
available: true,
value: {
kind: "source-range",
source: "app.js",
start: { line: index + 1, column: 0 },
end: {
line: index + 1,
column: "//# sourceMappingURL=".length + declaredUrl.length,
},
},
},
}),
}),
);
expect(graph.edges).toContainEqual(
expect.objectContaining({
relation: "maps_to",
target_node_id: map?.node_id,
properties: {
declared_url_sha256: digest,
resolved_path: path,
resolution_status: "resolved",
},
}),
);
}
const worker = graph.nodes.find((node) => node.kind === "worker");
expect(worker?.observations).toContainEqual(
expect.objectContaining({
properties: expect.objectContaining({
declared_specifier: "./worker%20file.js",
resolved_path: "worker file.js",
}),
}),
);
expect(graph.edges).toContainEqual(
expect.objectContaining({
source_node_id: worker?.node_id,
relation: "maps_to",
properties: { resolved_path: "worker file.js" },
}),
);
// A service worker script URL is relative to the unestablished document base.
const serviceWorker = graph.nodes.find(
(node) => node.kind === "service-worker",
);
expect(serviceWorker?.observations).toContainEqual(
expect.objectContaining({
properties: expect.objectContaining({
declared_specifier: null,
expression: 'navigator.serviceWorker.register("./service%20worker.js")',
resolved_path: null,
}),
}),
);
expect(
graph.edges.some(
({ source_node_id, relation }) =>
source_node_id === serviceWorker?.node_id && relation === "maps_to",
),
).toBe(false);
expect((await analyzeThroughStdioMcp(root)).graph).toEqual(graph);
}, 20_000);
describe("JavaScript application path CLI", () => {
it("accepts a relative local application path and preserves canonical Evidence identity", async () => {
const root = await createTestTempDirectory("rea-relative-application-cli-");
await writeFile(join(root, "app.js"), "export const value = 1;\n");
const absolute = await analyzeJavaScriptApplication({ input_path: root });
if (!absolute.ok) throw absolute.error;
const absoluteAnalysis = javascriptApplicationAnalysisResultSchema.parse(
absolute.value.normalized_result,
);
const relativePath = relative(process.cwd(), root);
const fromCli = await runCli([
"analyze-javascript-application",
relativePath,
"--json",
]);
expect(fromCli).toMatchObject({
evidence_id: absolute.value.evidence_id,
normalized_result: {
input_path: absoluteAnalysis.input_path,
},
subject: { local_path: absolute.value.subject?.local_path },
});
}, 20_000);
it("returns the self-import graph consistently through CLI and stdio MCP", async () => {
const root = await createTestTempDirectory(
"rea-self-import-application-cli-",
);
await writeSelfImportFixture(root);
const analyzed = await runCli([
"analyze-javascript-application",
root,
"--json",
]);
const result = z
.object({ normalized_result: javascriptApplicationAnalysisResultSchema })
.parse(analyzed);
const graph = result.normalized_result.graph;
expectSelfImportEvidence(graph);
const mcpResult = await analyzeThroughStdioMcp(root);
expect(mcpResult.graph).toEqual(graph);
expect(mcpResult.semantic_graph).toEqual(
result.normalized_result.semantic_graph,
);
}, 20_000);
it("preserves canonical Electron API aliases through CLI and stdio MCP", async () => {
const root = await createTestTempDirectory("rea-electron-api-alias-");
await writeElectronApiAliasApplication(root);
const cliResult = z
.object({ normalized_result: javascriptApplicationAnalysisResultSchema })
.parse(
await runCli(["analyze-javascript-application", root, "--json"]),
).normalized_result;
expect(cliResult.summary).toMatchObject({
browser_windows: 1,
explicit_web_preferences: 1,
preload_entrypoints: 1,
context_bridge_apis: 1,
exposed_api_members: 1,
ipc: {
operations: 2,
literal_channels: 1,
main_handlers: 1,
renderer_transmissions: 1,
paired_renderer_transmissions: 1,
unpaired_literal_renderer_transmissions: 0,
},
});
const graph = parseJavaScriptApplicationGraph(cliResult.graph);
const window = graph.nodes.find(({ kind }) => kind === "browser-window");
expect(window?.observations[0]?.evidence.location).toMatchObject({
available: true,
value: {
kind: "source-range",
source: "main.cjs",
start: { line: 6 },
},
});
const bridge = graph.nodes.find(
({ kind }) => kind === "context-bridge-api",
);
expect(bridge?.observations[0]?.evidence.location).toMatchObject({
available: true,
value: {
kind: "source-range",
source: "preload.cjs",
start: { line: 5 },
},
});
const mcpResult = await analyzeThroughStdioMcp(root);
expect(mcpResult.summary).toEqual(cliResult.summary);
expect(mcpResult.graph).toEqual(cliResult.graph);
expect(mcpResult.semantic_graph).toEqual(cliResult.semantic_graph);
}, 20_000);
});
describe("application workflow CLI parity", () => {
it("accepts inline trace JSON and file-backed comparison JSON", async () => {
const traced = await runCli([
"trace-application-feature",
JSON.stringify(JAVASCRIPT_FEATURE_TRACE_EXAMPLE),
"--json",
]);
expect(traced).toMatchObject({
operation: "trace_application_feature",
predicate_type: "rea.application-feature-trace",
normalized_result: {
seed: { kind: "module", value: "renderer.js", match: "exact" },
summary: {
matched_seeds: 1,
traced_nodes: 1,
traced_edges: 0,
terminal_paths: 0,
native_handoffs: 0,
observed_facts: 1,
inferred_facts: 0,
unknown_facts: 0,
unavailable_facts: 0,
},
coverage: {
status: "complete-within-source",
source_graph_status: "complete",
total_seed_matches: 1,
},
},
});
const root = await createTestTempDirectory("rea-application-cli-");
const comparisonPath = join(root, "comparison.json");
await writeFile(
comparisonPath,
JSON.stringify(JAVASCRIPT_APPLICATION_VERSION_COMPARISON_EXAMPLE),
);
const compared = await runCli([
"compare-application-versions",
comparisonPath,
"--json",
]);
expect(compared).toMatchObject({
operation: "compare_application_versions",
predicate_type: "rea.application-version-comparison",
normalized_result: {
summary: {
unchanged: 0,
added: 2,
removed: 0,
changed: 1,
unknown: 0,
},
coverage: {
left_graph_status: "complete",
right_graph_status: "complete",
left_graph_omitted_count: 0,
right_graph_omitted_count: 0,
status: "complete-within-inputs",
},
},
});
const sourceCompared = await runCli([
"compare-source-to-bundle",
JSON.stringify({
reference: SOURCE_TO_BUNDLE_COMPARISON_EXAMPLE.reference,
application: JAVASCRIPT_FEATURE_TRACE_EXAMPLE.application,
}),
"--json",
]);
expect(sourceCompared).toMatchObject({
operation: "compare_source_to_bundle",
predicate_type: "rea.source-to-bundle-comparison",
normalized_result: {
reference: { inventory_state: "complete" },
summary: {
unchanged: 0,
modified: 0,
removed: 1,
split: 0,
merged: 0,
duplicated: 0,
unknown: 0,
},
coverage: {
status: "complete-within-inputs",
reference_inventory_state: "complete",
application_graph_status: "complete",
retained_source_files: 1,
retained_application_nodes: 1,
},
},
});
}, 20_000);
it("traces the same authenticated semantic graph through the CLI", async () => {
const root = await createTestTempDirectory("rea-semantic-cli-");
await writeFile(
join(root, "app.js"),
"function add(value) { return value + 1; } add(2);",
);
const analyzed = await analyzeJavaScriptApplication({
input_path: root,
});
if (!analyzed.ok) throw analyzed.error;
const result = javascriptApplicationAnalysisResultSchema.parse(
analyzed.value.normalized_result,
);
const seed = result.semantic_graph.relations[0]?.source_node_id;
if (seed === undefined)
throw new TypeError("Expected at least one semantic relation");
const traced = await runCliWithRequestFile(
root,
"trace-javascript-semantics",
{
application: analyzed.value,
query: {
seed: { kind: "semantic-node", node_id: seed },
direction: "forward-influence",
include_ambiguous_dynamic_edges: true,
},
},
);
expect(traced).toMatchObject({
operation: "trace_javascript_semantics",
predicate_type: "rea.javascript-semantic-trace",
normalized_result: {
source_evidence_id: analyzed.value.evidence_id,
source_graph_id: result.semantic_graph.graph_id,
},
});
}, 20_000);
});
it("traces all endpoint carriers and their response consumers through CLI and MCP", async () => {
const root = await createTestTempDirectory("rea-endpoint-response-trace-");
const endpoint = "https://example.invalid/v1/data";
const source = `export async function load() {
const response = await fetch(${JSON.stringify(endpoint)});
return await response.json();
}`;
const sourcePath = join(root, "app.js");
await writeFile(sourcePath, source);
const application = parseEvidence(
await runCli(["analyze-javascript-application", root, "--json"]),
);
const graph = javascriptApplicationAnalysisResultSchema.parse(
application.normalized_result,
).semantic_graph;
const carriers = graph.nodes.filter(
({ properties }) => properties.endpoint === endpoint,
);
expect(carriers.map(({ kind }) => kind).toSorted()).toEqual([
"request",
"response",
]);
const response = carriers.find(({ kind }) => kind === "response");
if (response === undefined) throw new Error("Expected matching response");
const consumer = graph.relations.find(
({ source_node_id, relation }) =>
source_node_id === response.node_id && relation === "consumed-by",
);
expect(consumer).toMatchObject({ resolution: "resolved" });
const client = await connectedMcpClient();
const trace = async (seed: JavaScriptSemanticQueryInput["seed"]) => {
const input = {
application,
query: { seed, direction: "forward-influence" },
};
const cliEvidence = parseEvidence(
await runCliWithRequestFile(root, "trace-javascript-semantics", input),
);
const mcpResponse = await client.callTool({
name: "trace_javascript_semantics",
arguments: input,
});
expect(mcpResponse.isError).not.toBe(true);
expect(parseEvidence(mcpResponse.structuredContent)).toEqual(cliEvidence);
const result = javaScriptSemanticTraceResultSchema.parse(
cliEvidence.normalized_result,
);
expect(result.source_evidence_id).toBe(application.evidence_id);
expect(result.source_graph_id).toBe(graph.graph_id);
return result;
};
const selected = await trace({ kind: "endpoint", value: endpoint });
expect(selected.seed_node_ids).toEqual(
carriers.map(({ node_id }) => node_id).toSorted(),
);
const exactNodes = new Map<string, (typeof selected.nodes)[number]>();
const exactRelations = new Map<string, (typeof selected.relations)[number]>();
for (const carrier of carriers) {
const exact = await trace({
kind: "semantic-node",
node_id: carrier.node_id,
});
for (const node of exact.nodes) exactNodes.set(node.node_id, node);
for (const relation of exact.relations)
exactRelations.set(relation.relation_id, relation);
}
expect(new Map(selected.nodes.map((node) => [node.node_id, node]))).toEqual(
exactNodes,
);
expect(
new Map(
selected.relations.map((relation) => [relation.relation_id, relation]),
),
).toEqual(exactRelations);
expect(selected.nodes.map(({ kind }) => kind).toSorted()).toEqual([
"call-site",
"request",
"response",
]);
expect(selected.relations).toEqual([consumer]);
expect(selected).toMatchObject({
status: "ambiguous",
summary: {
total_seed_matches: 2,
traversed_nodes: 3,
traversed_relations: 1,
},
coverage: { status: "partial" },
});
expect(graph.coverage.status).toBe("unknown");
const absent = await trace({
kind: "endpoint",
value: `${endpoint}/missing`,
});
expect(absent).toMatchObject({
status: "partial",
seed_node_ids: [],
nodes: [],
relations: [],
coverage: { status: "partial" },
});
expect(await readFile(sourcePath, "utf8")).toBe(source);
}, 20_000);
describe("rest parameter semantic trace CLI", () => {
it("traces each ordinary rest argument to its parameter through public application Evidence", async () => {
const root = await createTestTempDirectory("rea-rest-arguments-cli-");
await writeFile(
join(root, "app.js"),
"function collect(first, ...rest) { return rest; } collect('first', 'second', 'third');",
);
const application = await runCli([
"analyze-javascript-application",
root,
"--json",
]);
const graph = z
.object({ normalized_result: javascriptApplicationAnalysisResultSchema })
.parse(application).normalized_result.semantic_graph;
for (const index of [1, 2]) {
const argument = graph.nodes.find(
({ kind, label }) =>
kind === "expression" && label === `argument ${String(index)}`,
);
if (argument === undefined) throw new Error("Missing argument node");
const requestPath = join(root, `trace-request-${index}.json`);
await writeFile(
requestPath,
JSON.stringify({
application,
query: {
seed: { kind: "semantic-node", node_id: argument.node_id },
direction: "forward-influence",
allowed_relations: ["argument-to-parameter"],
expected: { role: "sink", classes: ["parameter"] },
},
}),
);
const traced = await runCli([
"trace-javascript-semantics",
requestPath,
"--json",
]);
expect(traced).toMatchObject({
normalized_result: {
status: "found",
nodes: expect.arrayContaining([
expect.objectContaining({ kind: "parameter", label: "rest" }),
]),
},
});
}
}, 20_000);
});
describe("empty property key application CLI", () => {
it.each(["const root = routes[''];", "const {'': root} = routes;"])(
"analyzes a root-route dictionary with %s",
async (read) => {
const root = await createTestTempDirectory("rea-empty-key-cli-");
await writeFile(
join(root, "app.js"),
`const routes = {'': 'HOME'}; ${read}`,
);
const evidence = await runCli([
"analyze-javascript-application",
root,
"--json",
]);
expect(evidence).toMatchObject({
operation: "analyze_javascript_application",
normalized_result: {
semantic_graph: {
nodes: expect.arrayContaining([
expect.objectContaining({
kind: "property-slot",
label: '""',
properties: expect.objectContaining({ name: "" }),
}),
]),
},
},
});
},
20_000,
);
});
describe("Webpack factory CommonJS export keys", () => {
it("keeps exact static keys and omits computed names through CLI and MCP", async () => {
const root = await createTestTempDirectory("rea-webpack-export-keys-");
const bundle = `globalThis.webpackChunkStaticExports.push([["main"], {
1: function(module, exports, __webpack_require__) {${WEBPACK_FACTORY_BODY}},
2: function(module) { module.exports = function realDefault() {}; },
3: function(module, exports) { exports = function ignoredDefault() {}; },
4: function(module) {
const key = "dynamic";
module.exports = { [key]: 1, stable: 2 };
},
5: function(module) { module.exports = { __proto__: {} }; },
6: function(module) {
module.exports = { ["__proto__"]: 1, __proto__() {} };
}
}]);`;
await writeFile(join(root, "bundle.js"), bundle);
// Execute the same factory body as CommonJS, outside the analyzer, to
// verify Node's alias behavior and the exact runtime object keys.
const oracleRoot = await createTestTempDirectory(
"rea-webpack-export-oracle-",
);
const oraclePath = join(oracleRoot, "oracle.cjs");
await writeFile(oraclePath, WEBPACK_FACTORY_BODY);
const actual = requireFixture(oraclePath) as Record<string, unknown>;
expect(Reflect.ownKeys(actual).sort()).toEqual([
"",
"dot.key",
"dynamic",
"from.helper",
"module.dot",
"nested",
"nested.child",
]);
expect(actual).toMatchObject({
"": 4,
"dot.key": 2,
dynamic: 1,
"from.helper": 7,
"module.dot": 5,
nested: { child: 6 },
"nested.child": 7,
});
const prototypeSetterPath = join(oracleRoot, "prototype-setter.cjs");
const ownPrototypeKeyPath = join(oracleRoot, "own-prototype-key.cjs");
await Promise.all([
writeFile(prototypeSetterPath, "module.exports = { __proto__: {} }"),
writeFile(
ownPrototypeKeyPath,
'module.exports = { ["__proto__"]: 1, __proto__() {} }',
),
]);
expect(
Reflect.ownKeys(requireFixture(prototypeSetterPath) as object),
).toEqual([]);
expect(
Reflect.ownKeys(requireFixture(ownPrototypeKeyPath) as object),
).toEqual(["__proto__"]);
const aliasPath = join(oracleRoot, "alias.cjs");
const replacementPath = join(oracleRoot, "replacement.cjs");
await Promise.all([
writeFile(aliasPath, "exports = function ignored() {};"),
writeFile(
replacementPath,
"module.exports = function actual() { return 42; };",
),
]);
expect(requireFixture(aliasPath)).toEqual({});
const replacement: unknown = requireFixture(replacementPath);
if (typeof replacement !== "function")
throw new Error("Expected Node to load the replacement export");
expect(replacement()).toBe(42);
const applicationEvidenceSchema = z.object({
evidence_id: z.string(),
normalized_result: javascriptApplicationAnalysisResultSchema,
});
const cliEvidence = applicationEvidenceSchema.parse(
await runCli([
"analyze-javascript-application",
root,
"--artifact-format",
"directory",
"--json",
]),
);
const client = await connectedMcpClient();
const response = await client.callTool({
name: "analyze_javascript_application",
arguments: { input_path: root, format: "directory" },
});
expect(response.isError).not.toBe(true);
const mcpEvidence = applicationEvidenceSchema.parse(
response.structuredContent,
);
expect(await client.ping()).toEqual({});
expect(mcpEvidence.evidence_id).toBe(cliEvidence.evidence_id);
const expectBundleExports = (
evidence: typeof cliEvidence,
moduleKey: string,
expected: readonly string[],
) => {
const module = evidence.normalized_result.graph.nodes.find(
({ kind, observations }) =>
kind === "javascript-module" &&
observations.some(
({ properties }) =>
properties.runtime === "webpackChunkStaticExports" &&
properties.module_key === moduleKey,
),
);
if (module === undefined)
throw new Error(`Missing webpack factory ${moduleKey}`);
expect(module.observations[0]?.properties.exports).toEqual(expected);
};
for (const evidence of [cliEvidence, mcpEvidence]) {
expectBundleExports(evidence, "1", [
"",
"dot.key",
"from.helper",
"module.dot",
"nested",
"nested.child",
]);
expectBundleExports(evidence, "2", ["default"]);
expectBundleExports(evidence, "3", []);
expectBundleExports(evidence, "4", ["stable"]);
expectBundleExports(evidence, "5", []);
expectBundleExports(evidence, "6", ["__proto__"]);
}
}, 20_000);
});
describe("application workflow CLI input", () => {
it("rejects Evidence ID-only workflow inputs", async () => {
const result = await runCli([
"compare-application-versions",
JSON.stringify({
left: JAVASCRIPT_APPLICATION_VERSION_COMPARISON_EXAMPLE.left
.evidence_id,
right:
JAVASCRIPT_APPLICATION_VERSION_COMPARISON_EXAMPLE.right.evidence_id,
}),
"--json",
]);
expect(result).toMatchObject({ code: "invalid_request" });
});
});
describe("application workflow CLI copy boundaries", () => {
it("compares source values excluded from escaped rest and spread copies", async () => {
const root = await createTestTempDirectory("rea-copy-boundary-cli-");
const sources = [
`export default function make() {
function mutate(value) { value.changed = true; }
const objectRest = { only: { value: "TOKEN" } };
const { only, ...restObject } = objectRest;
mutate(restObject);
const arrayRest = [{ value: "TOKEN" }];
const [head, ...restArray] = arrayRest;
mutate(restArray);
const objectSpread = { child: { value: "TOKEN" } };
const spreadObject = { ...objectSpread, child: {} };
mutate(spreadObject.child);
const arraySpread = [{ value: "TOKEN" }];
const spreadArray = [{}, ...arraySpread];
mutate(spreadArray[0]);
return {
kind: "copy-boundary",
objectRest: objectRest.only.value,
arrayRest: arrayRest[0].value,
objectSpread: objectSpread.child.value,
arraySpread: arraySpread[0].value,
};
}`,
`export default function make() {
return {
kind: "copy-boundary",
objectRest: "UPDATED", arrayRest: "UPDATED",
objectSpread: "UPDATED", arraySpread: "UPDATED",
};
}`,
];
const [left, right] = await analyzeComparisonSources(root, sources);
const inputPath = join(root, "comparison.json");
await writeFile(
inputPath,
JSON.stringify({
left,
right,
left_module_path: "parser.mjs",
left_export_name: "default",
right_module_path: "parser.mjs",
right_export_name: "default",
}),
);
const compared = await runCli([
"compare-javascript-export-shapes",
inputPath,
"--json",
]);
expect(compared).toMatchObject({
normalized_result: {
summary: { added: 0, removed: 0, changed: 4, unknown: 0 },
coverage: { status: "complete-within-inputs" },
changes: expect.arrayContaining(
["/arrayRest", "/arraySpread", "/objectRest", "/objectSpread"].map(
(path) =>
expect.objectContaining({
status: "changed",
path,
left: { availability: "literal", value: "TOKEN" },
right: { availability: "literal", value: "UPDATED" },
}),
),
),
},
});
}, 20_000);
});
describe("application workflow CLI and MCP capture lifetimes", () => {
it("compares captured primitives and source values after aliases are replaced", async () => {
const parent = await createTestTempDirectory("rea-capture-lifetime-cli-");
const root = join(parent, "fixtures # capture");
await mkdir(root);
const fields = [
"rebound",
"rest",
"snapshot",
"destructured",
"objectSnapshot",
"arraySnapshot",
"unusedFallback",
"nestedSnapshot",
"declaratorSnapshot",
];
const sources = [
`export default function make() {
function mutate(value) { value.value = "MUTATED"; }
const original = {value: "TOKEN"};
let alias = original;
alias = {};
mutate(alias);
const child = {value: "TOKEN"};
let {...rest} = {child};
rest = {child: {}};
rest.child.value = "MUTATED";
const source = {value: "TOKEN"};
const snapshot = source.value;
const {value: destructured} = source;
const objectSnapshot = {value: source.value};
const arraySnapshot = [source.value];
mutate(source);
const fallback = {value: "TOKEN"};
const container = {child: {}};
const {child: selected = fallback} = container;
mutate(selected);
const nestedSource = {value: "TOKEN"};
const nestedSnapshot = nestedSource.value;
const ignored = mutate(nestedSource);
const declaratorSource = {value: "TOKEN"},
declaratorSnapshot = declaratorSource.value,
declaratorIgnored = mutate(declaratorSource);
return {
kind: "capture-boundary",
rebound: original.value, rest: child.value,
snapshot, destructured,
objectSnapshot: objectSnapshot.value, arraySnapshot: arraySnapshot[0],
unusedFallback: fallback.value, nestedSnapshot, declaratorSnapshot,
};
}`,
`export default function make() { return ${JSON.stringify({
kind: "capture-boundary",
...Object.fromEntries(fields.map((field) => [field, "UPDATED"])),
})}; }`,
];
// Retain a complete CLI analysis-to-comparison journey alongside MCP parity.
const [left, right] = await Promise.all(
(await writeComparisonSources(root, sources)).map((applicationRoot) =>
runCli(["analyze-javascript-application", applicationRoot, "--json"]),
),
);
// Execute the authored fixtures independently of static analysis.
for (const [index, value] of ["TOKEN", "UPDATED"].entries()) {
const { stdout } = await execute(process.execPath, [
"--input-type=module",
"--eval",
"const module = await import(process.argv[1]); console.log(JSON.stringify(module.default()));",
// ESM import() takes a URL specifier; a Windows drive path parses as a
// `c:` URL scheme and is rejected.
pathToFileURL(join(root, String(index), "parser.mjs")).href,
]);
expect(JSON.parse(stdout)).toEqual({
kind: "capture-boundary",
...Object.fromEntries(fields.map((field) => [field, value])),
});
}
const input = {
left,
right,
left_module_path: "parser.mjs",
left_export_name: "default",
right_module_path: "parser.mjs",
right_export_name: "default",
};
const inputPath = join(root, "comparison.json");
await writeFile(inputPath, JSON.stringify(input));
const expected = {
normalized_result: {
summary: { added: 0, removed: 0, changed: fields.length, unknown: 0 },
coverage: { status: "complete-within-inputs" },
changes: expect.arrayContaining(
fields.map((field) =>
expect.objectContaining({
path: `/${field}`,
status: "changed",
left: { availability: "literal", value: "TOKEN" },
right: { availability: "literal", value: "UPDATED" },
}),
),
),
},
};
expect(
await runCli(["compare-javascript-export-shapes", inputPath, "--json"]),
).toMatchObject(expected);
expect(await compareThroughStdioMcp(input)).toMatchObject(expected);
}, 20_000);
});
describe("application workflow CLI export Evidence", () => {
it("preserves uncertainty after a helper can mutate an awaited return object", async () => {
const root = await createTestTempDirectory("rea-awaited-export-shape-cli-");
const sources = [
`export default async function make() {
const result = { kind: "record" };
function mutate(value) { value.extra = 1; }
mutate(await result);
return result;
}`,
'export default async function make() { return { kind: "record", extra: 1 }; }',
];
const [left, right] = await analyzeComparisonSources(root, sources);
const compared = await runCliWithRequestFile(
root,
"compare-javascript-export-shapes",
{
left,
right,
left_module_path: "parser.mjs",
left_export_name: "default",
right_module_path: "parser.mjs",
right_export_name: "default",
},
);
expect(compared).toMatchObject({
operation: "compare_javascript_export_shapes",
normalized_result: {
summary: { added: 0, removed: 0, changed: 0 },
coverage: { status: "partial" },
changes: expect.arrayContaining([
expect.objectContaining({ status: "unknown" }),
]),
},
});
}, 20_000);
it("compares exact export shapes from file-backed Evidence", async () => {
const root = await createTestTempDirectory("rea-export-shape-cli-");
const leftRoot = join(root, "left");
const rightRoot = join(root, "right");
await Promise.all([mkdir(leftRoot), mkdir(rightRoot)]);
await Promise.all([
copyFile(
join(process.cwd(), "tests/fixtures/replay/parser.mjs"),
join(leftRoot, "parser.mjs"),
),
copyFile(
join(process.cwd(), "tests/fixtures/replay/parser-v2.mjs"),
join(rightRoot, "parser.mjs"),
),
]);
const [left, right] = await Promise.all([
analyzeJavaScriptApplication({
input_path: leftRoot,
}),
analyzeJavaScriptApplication({
input_path: rightRoot,
}),
]);
if (!left.ok) throw left.error;
if (!right.ok) throw right.error;
const inputPath = join(root, "comparison.json");
await writeFile(
inputPath,
JSON.stringify({
left: left.value,
right: right.value,
left_module_path: "parser.mjs",
left_export_name: "default",
right_module_path: "parser.mjs",
right_export_name: "default",
}),
);
const compared = await runCli([
"compare-javascript-export-shapes",
inputPath,
"--json",
]);
expect(compared).toMatchObject({
operation: "compare_javascript_export_shapes",
predicate_type: "rea.javascript-export-shape-comparison",
normalized_result: {
summary: { added: 1, removed: 0, changed: 0, unknown: 0 },
changes: [
{
status: "added",
path: "/depth",
presence: { left: "absent", right: "present" },
right: { availability: "literal", value: 1 },
},
],
},
});
}, 20_000);
});
describe("application workflow CLI export property presence", () => {
it("reports tagged search property presence through compare-javascript-export-shapes", async () => {
const root = await createTestTempDirectory(
"rea-export-shape-presence-cli-",
);
const leftRoot = join(root, "left");
const rightRoot = join(root, "right");
await Promise.all([mkdir(leftRoot), mkdir(rightRoot)]);
await Promise.all([
writeFile(
join(leftRoot, "search.js"),
[
"export function search(items, q) {",
" const matches = items.filter((item) => item.includes(q));",
' return { kind: "results", matches, count: matches.length };',
"}",
"",
].join("\n"),
),
writeFile(
join(rightRoot, "search.js"),
[
"export function search(items, q) {",
" const matches = items.filter((item) => item.includes(q));",
' return { kind: "results", matches, total: matches.length, query: String(q) };',
"}",
"",
].join("\n"),
),
]);
const [left, right] = await Promise.all([
analyzeJavaScriptApplication({ input_path: leftRoot }),
analyzeJavaScriptApplication({ input_path: rightRoot }),
]);
if (!left.ok) throw left.error;
if (!right.ok) throw right.error;
const inputPath = join(root, "comparison.json");
await writeFile(
inputPath,
JSON.stringify({
left: left.value,
right: right.value,
left_module_path: "search.js",
left_export_name: "search",
right_module_path: "search.js",
right_export_name: "search",
}),
);
const compared = await runCli([
"compare-javascript-export-shapes",
inputPath,
"--json",
]);
expect(compared).toMatchObject({
operation: "compare_javascript_export_shapes",
normalized_result: {
summary: { added: 2, removed: 1 },
property_inventories: expect.arrayContaining([
expect.objectContaining({
side: "left",
paired: true,
properties: expect.arrayContaining(["/count", "/kind", "/matches"]),
}),
expect.objectContaining({
side: "right",
paired: true,
properties: expect.arrayContaining([
"/kind",
"/matches",
"/query",
"/total",
]),
}),
]),
changes: expect.arrayContaining([
expect.objectContaining({
status: "removed",
path: "/count",
presence: { left: "present", right: "absent" },
}),
expect.objectContaining({
status: "added",
path: "/total",
presence: { left: "absent", right: "present" },
}),
expect.objectContaining({
status: "added",
path: "/query",
presence: { left: "absent", right: "present" },
}),
]),
},
});
}, 20_000);
});
describe("application workflow CLI validation", () => {
it("returns safe actionable JSON validation details", async () => {
const malformed = await runCli([
"trace-application-feature",
"{not-json",
"--json",
]);
expect(malformed).toMatchObject({
code: "invalid_request",
retryable: true,
details: {
issues: [{ path: [], reason: "invalid_format", expected: "JSON" }],
},
});
expect(JSON.stringify(malformed)).not.toContain("not-json");
const missing = await runCli(["trace-application-feature", "{}", "--json"]);
expect(missing).toMatchObject({
code: "invalid_request",
details: {
issues: expect.arrayContaining([
expect.objectContaining({
path: ["seed"],
reason: "missing_argument",
}),
]),
},
});
}, 20_000);
});
const compareThroughStdioMcp = async (
input: Record<string, unknown>,
): Promise<unknown> => {
const client = await connectedMcpClient();
const response = await client.callTool({
name: "compare_javascript_export_shapes",
arguments: input,
});
expect(response.isError).not.toBe(true);
return response.structuredContent;
};
const writeComparisonSources = (
root: string,
sources: readonly string[],
): Promise<string[]> =>
Promise.all(
sources.map(async (source, index) => {
const applicationRoot = join(root, String(index));
await mkdir(applicationRoot);
await writeFile(join(applicationRoot, "parser.mjs"), source);
return applicationRoot;
}),
);
// Produce comparison inputs through the shared stateless stdio workflow;
// each consumer still executes the real CLI with its own authored sources.
const analyzeComparisonSources = async (
root: string,
sources: readonly string[],
): Promise<unknown[]> => {
const client = await connectedMcpClient();
return Promise.all(
(await writeComparisonSources(root, sources)).map(
async (applicationRoot) => {
const response = await client.callTool({
name: "analyze_javascript_application",
arguments: { input_path: applicationRoot },
});
expect(response.isError).not.toBe(true);
return response.structuredContent;
},
),
);
};
it("retains bundled isolated-world contextBridge APIs through dedicated and generic CLI routes", async () => {
const root = await createTestTempDirectory(
"rea-context-bridge-isolated-cli-",
);
await writeFile(
join(root, "preload.js"),
`(() => {
const modules = {
2(module) { module.exports = require("electron"); },
5(module, exports, load) {
const electron = load(2);
electron.contextBridge.exposeInIsolatedWorld(1001, "isolatedApi", { ping: true });
},
};
function load(id) {
const module = { exports: {} };
modules[id].call(module.exports, module, module.exports, load);
return module.exports;
}
load(5);
})();`,
);
for (const arguments_ of [
["analyze-javascript-application", root, "--json"],
["analyze", root, "--format", "json"],
]) {
const result = javascriptApplicationAnalysisResultSchema.parse(
parseEvidence(await runCli(arguments_)).normalized_result,
);
expect(result.summary.context_bridge_apis).toBe(1);
const bridge = result.graph.nodes.find(
({ kind }) => kind === "context-bridge-api",
);
expect(bridge?.observations).toContainEqual(
expect.objectContaining({
properties: expect.objectContaining({
world: "isolated",
world_id: { status: "literal", value: 1001, expression: null },
api_key: "isolatedApi",
api_status: "object-literal",
members: ["ping"],
unknown_members: 0,
// The proven Webpack bootstrap resolves `load(2)` directly.
resolution_basis: "direct-syntax",
}),
evidence: expect.objectContaining({
location: expect.objectContaining({
available: true,
value: expect.objectContaining({ source: "preload.js" }),
}),
}),
}),
);
}
});
const runCli = async (
arguments_: readonly string[],
environment: Readonly<Record<string, string>> = {},
): Promise<unknown> => {
try {
const { stdout } = await execute(
process.execPath,
["scripts/rea.mjs", ...arguments_],
{
cwd: process.cwd(),
env: { ...process.env, ...environment },
maxBuffer: 16 * 1_024 * 1_024,
},
);
return JSON.parse(stdout);
} catch (cause: unknown) {
if (
typeof cause === "object" &&
cause !== null &&
"stdout" in cause &&
typeof cause.stdout === "string"
)
return JSON.parse(cause.stdout);
throw cause;
}
};
/**
* Pass a request carrying complete Evidence through the CLI's file-backed JSON
* input. Serialized Evidence exceeds the Windows command-line limit when it is
* passed as one argument, so spawn fails with ENAMETOOLONG before the workflow
* starts. Inline JSON transport keeps its own coverage in the parity tests.
*/
const runCliWithRequestFile = async (
root: string,
command: string,
request: unknown,
): Promise<unknown> => {
const requestPath = join(root, "request.json");
await writeFile(requestPath, JSON.stringify(request));
return runCli([command, requestPath, "--json"]);
};