rea/scripts/verify-jeb.mjs

154 lines
5.2 KiB
JavaScript

import { execFile } from "node:child_process";
import { mkdtemp, writeFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { promisify } from "node:util";
import { JebAnalysisService } from "../dist/application/jeb/JebAnalysisService.js";
import { createJebAnalysisProvider } from "../dist/composition/jeb.js";
const run = promisify(execFile);
const options = parseArguments(process.argv.slice(2));
if (options.help) {
process.stdout.write(
"Usage: npm run verify:jeb [-- --item-address ADDRESS --marker TEXT]\n" +
"Requires a running JEB client serving MCP at REA_JEB_MCP_URL\n" +
"(default http://127.0.0.1:8425/mcp) with a project already open.\n" +
"JEB 5.48 headless instances open their target at start (--infile or\n" +
"the documented programmatic launcher); open_jeb_project is verified\n" +
"only when the engine advertises it. Without --item-address, a tiny\n" +
"Java fixture is compiled and its known method is decompiled.\n",
);
process.exit(0);
}
const provider = createJebAnalysisProvider(process.env);
const service = new JebAnalysisService(provider);
const fixtureDirectory = await mkdtemp(join(tmpdir(), "rea-jeb-"));
try {
const client = await service.execute("inspect_jeb_client", {});
if (!client.ok) throw new Error(client.error.message);
const engine = client.value.normalized_result.engine;
if (engine.version === null)
throw new Error("The JEB client did not report its version");
// JEB 5.48 headless clients do not advertise open_project; a project opened
// at engine start is the supported target-selection route there. Attempt the
// tool and record the engine's exact refusal when it is unavailable.
let openRefusal = null;
const target = await resolveTarget(options, fixtureDirectory);
const opened = await service.execute("open_jeb_project", {
path: target.path,
});
if (opened.ok) {
const artifact = opened.value.normalized_result.input_files.find(
(file) => typeof file === "object" && file !== null,
);
if (
artifact === undefined ||
!/^[0-9a-f]{64}$/u.test(artifact.contents_sha256_hash)
)
throw new Error("The engine did not report an input artifact digest");
} else {
openRefusal = opened.error.message;
}
const units = await service.execute("list_jeb_units", { count: 100 });
if (!units.ok) throw new Error(units.error.message);
if (units.value.normalized_result.units.length === 0)
throw new Error("The engine reported no project units");
const decompiled = await service.execute("decompile_jeb_item", {
item_address: target.itemAddress,
item_kind: "method",
});
if (!decompiled.ok) throw new Error(decompiled.error.message);
const text = decompiled.value.normalized_result.text;
if (!text.includes(target.marker))
throw new Error(
"Decompiled pseudo-code did not contain the fixture marker",
);
const report = {
ok: true,
provider: "jeb",
host_platform: process.platform,
engine_version: engine.version,
engine_gui: engine.gui_client,
endpoint: engine.endpoint,
open_project_observed: openRefusal === null,
...(openRefusal === null ? {} : { open_project_refusal: openRefusal }),
unit_count: units.value.normalized_result.units.length,
decompiled_marker_observed: true,
};
process.stdout.write(`${JSON.stringify(report)}\n`);
} catch (cause) {
process.stderr.write(
`JEB real-provider verification failed: ${cause instanceof Error ? cause.message : String(cause)}\n`,
);
process.exitCode = 1;
} finally {
await provider.close().catch(() => undefined);
await rm(fixtureDirectory, { recursive: true, force: true }).catch(
() => undefined,
);
}
async function resolveTarget(selected, directory) {
if (selected.itemAddress !== undefined)
return {
path: "/engine-side/target",
itemAddress: selected.itemAddress,
marker: selected.marker ?? "",
};
await writeFile(
join(directory, "Hello.java"),
[
"public class Hello {",
" public static String greet(String name) {",
' return "Hello, " + name + "!";',
" }",
"}",
"",
].join("\n"),
);
try {
await run("javac", [join(directory, "Hello.java")]);
} catch {
throw new Error(
"javac is required to build the default verification fixture",
);
}
return {
path: join(directory, "Hello.class"),
itemAddress: "LHello;->greet(Ljava/lang/String;)Ljava/lang/String;",
marker: "Hello, ",
};
}
function parseArguments(args) {
if (args.length === 1 && args[0] === "--help") return { help: true };
const values = new Map();
for (let index = 0; index < args.length; index += 1) {
const flag = args[index];
const value = args[index + 1];
if (
(flag !== "--item-address" && flag !== "--marker") ||
value === undefined
)
throw new Error(
"Expected no arguments, or --item-address ADDRESS [--marker TEXT]",
);
values.set(flag, value);
index += 1;
}
return {
help: false,
...(values.has("--item-address")
? { itemAddress: values.get("--item-address") }
: {}),
...(values.has("--marker") ? { marker: values.get("--marker") } : {}),
};
}