Merge upstream/main into fix/release-exposure-audit

Resolve the docs/index.md guide table conflict by taking main's table (Linux kernel modules and wxapkg links) and keeping this branch's Release exposure link.
This commit is contained in:
Matt Van Horn committed 2026-11-04 12:23:12 -08:00
commit 803cd0982b
601 files changed
+28500 -3060

No files matched your search

+2 -1
View File
@@ -3,6 +3,7 @@ export interface ReadinessPullRequest {
readonly state: "open" | "closed";
readonly draft: boolean;
readonly body: string | null;
readonly user: { readonly id: number } | null;
readonly head: { readonly sha: string };
readonly base: { readonly ref: string };
}
@@ -53,7 +54,7 @@ export interface ReadinessGitHub {
>;
}
export function evaluateReadiness(
pull: Pick<ReadinessPullRequest, "body" | "draft">,
pull: Pick<ReadinessPullRequest, "body" | "draft" | "user">,
template: string,
): ReadinessResult;
export function runReadinessCheck(input: {
+14
View File
@@ -2,6 +2,14 @@ import { readFile } from "node:fs/promises";
const result = (state, description) => ({ state, description });
// GitHub account IDs remain stable when a login changes.
const EXEMPT_AUTHOR_IDS = new Set([
76467478, // morluto
208281322, // kaoru0822-kitauji
161784452, // N0zoM1z0
342580529, // gensokyo-dev
]);
const OPEN_HEADS_QUERY = `query ReadinessOpenHeads($owner: String!, $name: String!, $after: String) {
repository(owner: $owner, name: $name) {
pullRequests(first: 100, after: $after, states: OPEN, orderBy: {field: CREATED_AT, direction: ASC}) {
@@ -105,6 +113,11 @@ export function evaluateReadiness(pull, template) {
"pending",
"Draft PR: complete Author readiness before requesting review.",
);
if (EXEMPT_AUTHOR_IDS.has(pull.user?.id))
return result(
"success",
"PR author is exempt from the readiness declaration.",
);
const expected = readinessLines(template).flatMap((line) => {
const match = /^- \[ \] (.+)$/u.exec(line);
return match ? [match[1]] : [];
@@ -210,6 +223,7 @@ export async function runReadinessCheck({ github, context, core }) {
latest.head.sha !== pull.head.sha ||
latest.body !== pull.body ||
latest.draft !== pull.draft ||
latest.user?.id !== pull.user?.id ||
latest.base.ref !== pull.base.ref
)
verdict = result(
+8 -1
View File
@@ -76,6 +76,7 @@ const PATH_SCOPES = {
"src/frida/frida-bindings.d.ts",
"src/objdump/**",
"src/cli/reverseEngineeringCommands.ts",
"src/process/ownedDebuggerCleanupError.ts",
"src/process/ownedDebuggerSession.ts",
"src/rizin/**",
"src/contracts/*ToolContracts.ts",
@@ -103,6 +104,7 @@ const PATH_SCOPES = {
"tests/boundary/providers/RizinInferiorTty.test.ts",
"tests/boundary/providers/RizinTestInferiorTty.ts",
"tests/boundary/process/ownedDebuggerSession.test.ts",
"tests/boundary/cli/debuggerOneShotExit.test.ts",
"tests/boundary/cli/gdbStartupCleanupCli.test.ts",
"tests/boundary/cli/reverseEngineeringCommands.test.ts",
"tests/boundary/mcp/cutter*.test.ts",
@@ -163,6 +165,9 @@ const PATH_SCOPES = {
"tests/boundary/mcp/nativeDemangleMcp.test.ts",
"tests/boundary/mcp/nativeMachoSummaryMcp.test.ts",
"tests/boundary/providers/native/nativeSignatureTargetBinding.test.ts",
"tests/boundary/providers/native/nativeUiOffscreenWindow.test.ts",
"tests/boundary/process/nativeUiVerifierLane.test.ts",
"tests/conformance/native/ui-offscreen.swift",
"tests/boundary/cli/nativeSignatureCli.test.ts",
"tests/boundary/cli/nativeMachoArm64_32Cli.test.ts",
"tests/boundary/cli/nativeMachoEncryptionCli.test.ts",
@@ -699,7 +704,9 @@ export const CI_LANES = {
agentenv: sameJobs(PATH_SCOPES.agentenv, ["agentenv"]),
nativeaot: sameJobs(PATH_SCOPES.nativeaot, ["nativeaot"]),
ghidra_bridge: sameJobs(PATH_SCOPES.ghidra_bridge, ["ghidra_bridge"]),
website: sameJobs(PATH_SCOPES.website, ["website"]),
website: sameJobs(PATH_SCOPES.website, ["website"], {
earlyInputs: [".agents/skills/reverse-engineer-anything/**"],
}),
};
// An input owner omitted from the registry must never silently lose a lane.
for (const [name, paths] of Object.entries(PATH_SCOPES))
@@ -3,19 +3,19 @@
"snapshot_hash": "65817c30a78bb44c3dc3771876b6010a",
"engines": {
"x86_64": {
"dart_version": null,
"dart_version": "2.14.0-151.0.dev (dev) (Tue May 25 10:58:39 2021 -0700)",
"toolchain_lines": [
"Android (5900059 based on r365631c) clang version 9.0.8 (https://android.googlesource.com/toolchain/llvm-project 207d7abc1a2abf3ef8d4301736d6a7ebc224a290) (based on LLVM 9.0.8svn)"
]
},
"armeabi-v7a": {
"dart_version": null,
"dart_version": "2.14.0-151.0.dev (dev) (Tue May 25 10:58:39 2021 -0700)",
"toolchain_lines": [
"Android (5900059 based on r365631c) clang version 9.0.8 (https://android.googlesource.com/toolchain/llvm-project 207d7abc1a2abf3ef8d4301736d6a7ebc224a290) (based on LLVM 9.0.8svn)"
]
},
"arm64-v8a": {
"dart_version": null,
"dart_version": "2.14.0-151.0.dev (dev) (Tue May 25 10:58:39 2021 -0700)",
"toolchain_lines": [
"Android (5900059 based on r365631c) clang version 9.0.8 (https://android.googlesource.com/toolchain/llvm-project 207d7abc1a2abf3ef8d4301736d6a7ebc224a290) (based on LLVM 9.0.8svn)"
]
+35 -17
View File
@@ -1,18 +1,23 @@
import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import { createHash } from "node:crypto";
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { parseEvidence } from "../../dist/domain/evidence.js";
import { webSourceLocationResultSchema } from "../../dist/domain/webSourceLocation.js";
import { PrivateRuntimeRoot } from "../../dist/process/PrivateRuntimeRoot.js";
import { startBrowserSourceMapSite } from "../fixtures/browser-source-map-site.mjs";
import { mcpTextValue } from "./mcp-verifier-results.mjs";
import { withVerifierCleanup } from "./verifier-cleanup.mjs";
import { createPackageVerifierCommands } from "./verify-package-commands.mjs";
import { importVerifierSource } from "./verifier-source-loader.mjs";
const { BROWSER_DEVTOOLS_STARTUP_TIMEOUT_MS } = await importVerifierSource(
new URL("./browser-process-startup.mts", import.meta.url).href,
import.meta.url,
);
/** Verify real Chrome capture → export → original compiler point through packaged CLI/MCP. */
export async function verifyBrowserSourceMap(
@@ -21,21 +26,35 @@ export async function verifyBrowserSourceMap(
) {
let site;
let root;
let commands;
let transport;
let client;
const closeResource = async (owner, resource) => {
if (owner === undefined) return;
try {
await owner.close();
} catch (cause) {
commands?.adopt(owner, resource);
throw cause;
}
};
return withVerifierCleanup(async () => {
site = await startBrowserSourceMapSite();
root = await mkdtemp(join(tmpdir(), "rea-browser-source-map-"));
root = await PrivateRuntimeRoot.create({
prefix: "rea-browser-source-map-",
});
commands = await createPackageVerifierCommands({ workspace: root });
const env = {
...process.env,
REA_LOG_LEVEL: "silent",
HOPPER_LAUNCHER_PATH: "/rea-unconfigured-provider/hopper",
};
const cli = async (...args) => {
const { stdout } = await promisify(execFile)(
const { stdout } = await commands.exec(
process.execPath,
[entrypoint, ...args, "--json"],
{ env, timeout: 45000, maxBuffer: 16 * 1024 * 1024 },
// Allow the shared cold-start budget plus capture, cleanup and delivery.
{ env, timeout: 120_000, maxBuffer: 16 * 1024 * 1024 },
);
return parseEvidence(JSON.parse(stdout));
};
@@ -50,6 +69,7 @@ export async function verifyBrowserSourceMap(
"capture-browser-scenario",
JSON.stringify({
browser: { mode: "launch", executable_path: executable },
command_timeout_ms: BROWSER_DEVTOOLS_STARTUP_TIMEOUT_MS,
start_url: { url: site.origin },
actions: [
{
@@ -66,12 +86,12 @@ export async function verifyBrowserSourceMap(
capture: { network: { response_body: true } },
}),
);
const capturePath = join(root, "capture.json");
const capturePath = join(root.path, "capture.json");
await writeFile(capturePath, JSON.stringify(capture));
const exported = await cli(
"export-web-scripts",
capturePath,
join(root, "export"),
join(root.path, "export"),
);
const manifest = exported.normalized_result;
const manifestBytes = await readFile(manifest.manifest.path);
@@ -86,7 +106,7 @@ export async function verifyBrowserSourceMap(
scriptIndex >= 0,
"Actual browser capture omitted compiled source",
);
const mapPath = join(root, "app.js.map");
const mapPath = join(root.path, "app.js.map");
await writeFile(mapPath, site.map);
const before = site.mapRequests();
await client.connect(transport);
@@ -132,13 +152,11 @@ export async function verifyBrowserSourceMap(
public_point_cases: 4,
};
}, [
() => client?.close(),
() => transport?.close(),
() => site?.close(),
() =>
root === undefined
? undefined
: rm(root, { recursive: true, force: true }),
() => closeResource(client, "source-map MCP client"),
() => closeResource(transport, "source-map stdio transport"),
() => closeResource(site, "source-map fixture site"),
() => commands?.close(),
() => (commands === undefined ? root?.close() : undefined),
]);
}
function assertPoint(evidence, { site, input, capture, identity }) {
+40
View File
@@ -114,6 +114,46 @@ export const verifyKunaReferences = async ({
);
}
assert.equal(trace.value.truncated, false);
const summary = await call("trace_feature", {
query: name,
detail: "summary",
});
assert.equal(
summary.value.parent_evidence_id,
trace.structured.evidence_id,
);
assert.equal(
summary.value.summary.references,
trace.value.references.length,
);
assert.equal(
summary.value.summary.unknowns,
trace.value.residual_unknowns.length,
);
for (const [collection, values] of [
["matches", trace.value.matches],
["references", trace.value.references],
["unknowns", trace.value.residual_unknowns],
]) {
const view = await call("inspect_analysis_view", {
source: {
kind: "retained-evidence",
evidence_id: trace.structured.evidence_id,
},
view: {
kind: "native-feature-trace",
collection,
offset: 0,
limit: 1,
},
});
assert.equal(view.value.coverage.total, values.length);
assert.deepEqual(
view.value.items.rows.map(({ value }) => value),
values.slice(0, 1),
);
assert.equal(view.value.items.collection, collection);
}
}
composed = {
graph_ms: graph.ms,
+55 -21
View File
@@ -17,6 +17,7 @@ import {
requireMcpOutput,
} from "./mcp-verifier-results.mjs";
import { verifyGhidraBoundaries } from "./real-ghidra-boundaries.mjs";
import { verifyGhidraProjectIdentity } from "./real-ghidra-project-identity.mjs";
const defaultEntrypoint = fileURLToPath(new URL("../rea.mjs", import.meta.url));
const environment = () => ({
@@ -214,6 +215,19 @@ async function verifyNativeValueAdapters(
);
assert.deepEqual(mcpEvidence.normalized_result, mcpGraph);
assert.deepEqual(mcpGraph, cliGraph);
const warmReply = await client.callTool(
{
name: "trace_native_values",
arguments: { procedure, max_depth: 2, max_functions: 8, limit: 3 },
},
{ timeout: 180000 },
);
const warmEvidence = parseEvidence(
requireMcpOutput(warmReply, "trace_native_values"),
);
const warmGraph = nativeValueTraceSchema.parse(
warmEvidence.normalized_result,
);
const analyzed = await client.callTool(
{
name: "analyze_function",
@@ -253,20 +267,9 @@ async function verifyNativeValueAdapters(
functionEvidence.analysis_profile,
);
assert.deepEqual(functionProfile, upstreamProfile);
const comparison = await client.callTool(
{
name: "compare_functions",
arguments: {
left: analyzed.structuredContent,
right: analyzed.structuredContent,
},
},
{ timeout: 180000 },
);
assert.notEqual(comparison.isError, true, JSON.stringify(comparison));
assert.notEqual(comparison.structuredContent, undefined);
const comparisonEvidence = parseEvidence(
requireMcpOutput(comparison, "compare_functions"),
const comparisonEvidence = await compareRetainedFunctionEvidence(
client,
functionEvidence,
);
assert.equal(
comparisonEvidence.normalized_result.status,
@@ -286,6 +289,25 @@ async function verifyNativeValueAdapters(
"MCP omitted structured Evidence bundle",
);
const bundle = requireMcpEvidenceBundle(bundleReply);
for (const [evidence, graph] of [
[mcpEvidence, mcpGraph],
[warmEvidence, warmGraph],
]) {
for (const source of [
...graph.procedures,
...graph.nodes,
...graph.edges,
]) {
assert.ok(evidence.evidence_links.includes(source.evidence_id));
assert.equal(
bundle.records.find(
(record) => record.evidence_id === source.evidence_id,
)?.operation,
"analyze_function",
`Missing source Evidence ${source.evidence_id}`,
);
}
}
for (const evidence of [
mcpEvidence,
functionEvidence,
@@ -328,6 +350,11 @@ async function verifyNativeValueAdapters(
await client.close();
await transport.close();
}
const projectIdentity = await verifyGhidraProjectIdentity({
target,
entrypoint,
env,
});
return {
mocked: false,
cli: true,
@@ -335,6 +362,7 @@ async function verifyNativeValueAdapters(
inline_evidence: true,
direct_function_comparison: true,
boundaries,
project_identity: projectIdentity,
nodes: cliGraph.total_nodes,
edges: cliGraph.total_edges,
decompilations: cliGraph.decompilations,
@@ -395,21 +423,27 @@ async function verifyUnresolvedCallEvidence(client) {
assert.equal(trace.goal_status, "unknown");
assert.equal(trace.truncated, true);
assert.ok(trace.residual_unknowns.length > 0);
const compared = await compareRetainedFunctionEvidence(client, evidence);
assert.equal(compared.normalized_result.status, "unknown");
}
/** Compare an already admitted function record without retransmitting its payload. */
export async function compareRetainedFunctionEvidence(client, evidence) {
const reference = {
kind: "retained-evidence",
evidence_id: evidence.evidence_id,
};
const compared = await client.callTool(
{
name: "compare_functions",
arguments: {
left: analyzed.structuredContent,
right: analyzed.structuredContent,
left: reference,
right: reference,
},
},
{ timeout: 180000 },
);
assert.notEqual(compared.isError, true, JSON.stringify(compared));
assert.notEqual(compared.structuredContent, undefined);
assert.equal(
parseEvidence(requireMcpOutput(compared, "compare_functions"))
.normalized_result.status,
"unknown",
);
return parseEvidence(requireMcpOutput(compared, "compare_functions"));
}
+13
View File
@@ -504,6 +504,9 @@ export async function verifyGhidraBoundaries(
// Prime both inventories before editing so stale names cannot hide in caches.
await call("list_names");
await call("list_procedures");
const traceInput = { procedure: address, max_depth: 0, max_functions: 1 };
const traceBefore = await evidence("trace_native_values", traceInput);
const sourceBefore = traceBefore.normalized_result.procedures[0].evidence_id;
const changes = {
procedure: address,
name: "dead",
@@ -517,6 +520,16 @@ export async function verifyGhidraBoundaries(
comment: changes.comment,
inline_comment: changes.inline_comment,
});
const traceAfter = await evidence("trace_native_values", traceInput);
const sourceAfter = traceAfter.normalized_result.procedures[0].evidence_id;
assert.notEqual(sourceBefore, sourceAfter);
const sourceProcedure = (evidenceId) =>
call("inspect_analysis_view", {
source: { kind: "retained-evidence", evidence_id: evidenceId },
view: { kind: "native", facet: "procedure", offset: 0, limit: 1 },
});
assert.equal((await sourceProcedure(sourceBefore)).item.name, name);
assert.equal((await sourceProcedure(sourceAfter)).item.name, changes.name);
const regexPattern = "^(a|aa)*b$";
const regexFailure = await client.callTool(
{
@@ -0,0 +1,249 @@
import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import { createHash } from "node:crypto";
import {
appendFile,
copyFile,
mkdtemp,
readFile,
readdir,
rm,
writeFile,
} from "node:fs/promises";
import { basename, join } from "node:path";
import { promisify } from "node:util";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { requireMcpToolError } from "./mcp-verifier-results.mjs";
const exec = promisify(execFile);
/** Verify selected-project identity refusal through real CLI and MCP adapters. */
export async function verifyGhidraProjectIdentity({ target, entrypoint, env }) {
if (process.platform === "win32")
return {
status: "not-applicable",
reason: "Existing Ghidra projects are not admitted on Windows.",
};
assert.ok(env.GHIDRA_INSTALL_DIR, "Ghidra installation is required");
const root = await mkdtemp(join(env.TMPDIR, "rea-project-identity-"));
const targetPath = target.path;
const different = join(root, "different-target");
const headless = join(env.GHIDRA_INSTALL_DIR, "support", "analyzeHeadless");
const fixtureEnv = {
...env,
HOME: root,
XDG_CONFIG_HOME: join(root, "config"),
XDG_CACHE_HOME: join(root, "cache"),
XDG_DATA_HOME: join(root, "data"),
JDK_JAVA_OPTIONS: `-Duser.home="${root}"`,
};
const program = basename(targetPath);
const known = join(root, "known.gpr");
const unknown = join(root, "unknown.gpr");
const runtimeRootsBefore = (await readdir(env.TMPDIR))
.filter((name) => name.startsWith("rea-ghidra-"))
.sort();
const sha = async (path) =>
createHash("sha256")
.update(await readFile(path))
.digest("hex");
try {
await copyFile(targetPath, different);
await appendFile(different, "\nREA_IDENTITY_VERIFIER\n");
assert.notEqual(await sha(different), await sha(targetPath));
await writeFile(
join(root, "ClearImportDigest.java"),
`import ghidra.app.script.GhidraScript;
public class ClearImportDigest extends GhidraScript {
@Override public void run() {
currentProgram.setExecutableSHA256(null);
println("REA_DIGEST_AFTER=" + currentProgram.getExecutableSHA256());
}
}\n`,
);
await exec(
headless,
[root, "known", "-import", targetPath, "-noanalysis"],
{ env: fixtureEnv, timeout: 180000, maxBuffer: 8 * 1024 * 1024 },
);
const cleared = await exec(
headless,
[
root,
"unknown",
"-import",
targetPath,
"-noanalysis",
"-scriptPath",
root,
"-postScript",
"ClearImportDigest.java",
],
{ env: fixtureEnv, timeout: 180000, maxBuffer: 8 * 1024 * 1024 },
);
assert.match(cleared.stdout, /REA_DIGEST_AFTER=unknown/u);
const before = await projectState([known, unknown, targetPath, different]);
const knownEnv = {
...env,
REA_GHIDRA_PROJECT_PATH: known,
REA_GHIDRA_PROGRAM_NAME: program,
};
const unknownEnv = {
...env,
REA_GHIDRA_PROJECT_PATH: unknown,
REA_GHIDRA_PROGRAM_NAME: program,
};
const mismatch = await selectedProjectMcp({
entrypoint,
env: knownEnv,
first: different,
matching: targetPath,
expected: "import_identity_mismatch",
});
const unavailable = await selectedProjectMcp({
entrypoint,
env: unknownEnv,
first: targetPath,
expected: "import_identity_unavailable",
});
for (const [label, selectedEnv, input, expected] of [
["mismatch", knownEnv, different, "import_identity_mismatch"],
["unavailable", unknownEnv, targetPath, "import_identity_unavailable"],
]) {
let failed;
try {
await exec(
process.execPath,
[
entrypoint,
"decompile",
input,
"0x0",
"--provider",
"ghidra",
"--format",
"json",
],
{ env: selectedEnv, timeout: 180000, maxBuffer: 8 * 1024 * 1024 },
);
} catch (cause) {
assert.equal(cause.code, 1, `${label} CLI exit`);
failed = JSON.parse(cause.stdout);
}
assert.ok(failed, `${label} CLI must refuse`);
assert.equal(failed.code, "configuration_invalid");
assert.equal(failed.details.diagnostics.remote_code, expected);
}
assert.deepEqual(
await projectState([known, unknown, targetPath, different]),
before,
);
assert.deepEqual(
(await readdir(env.TMPDIR))
.filter((name) => name.startsWith("rea-ghidra-"))
.sort(),
runtimeRootsBefore,
"Selected-project checks left a Ghidra runtime directory",
);
return {
status: "verified",
mismatch,
unavailable,
cli_refusals: true,
source_files_unchanged: true,
};
} finally {
await rm(root, { recursive: true, force: true });
}
}
async function selectedProjectMcp({
entrypoint,
env,
first,
matching,
expected,
}) {
const client = new Client({
name: "rea-project-identity-verifier",
version: "1",
});
const transport = new StdioClientTransport({
command: process.execPath,
args: [entrypoint, "mcp"],
env,
stderr: "pipe",
});
try {
await client.connect(transport);
const tools = new Map(
(await client.listTools()).tools.map((tool) => [tool.name, tool]),
);
const call = (name, arguments_) =>
client.callTool(
{ name, arguments: arguments_ },
{ timeout: 180000, toolDefinition: tools.get(name) },
);
assert.notEqual(
(await call("open_binary", { path: first, provider_id: "ghidra" }))
.isError,
true,
);
const rejected = requireMcpToolError(await call("list_documents", {}));
assert.equal(
rejected.code,
"configuration_invalid",
JSON.stringify({
code: rejected.code,
message: rejected.message,
failure_kind: rejected.details?.diagnostics?.failure_kind,
failure_message: rejected.details?.diagnostics?.failure_message,
stdout: rejected.details?.diagnostics?.stdout?.text?.slice(-2500),
stderr: rejected.details?.diagnostics?.stderr?.text?.slice(-1000),
}),
);
assert.equal(rejected.details.diagnostics.remote_code, expected);
if (matching !== undefined) {
assert.notEqual(
(await call("open_binary", { path: matching, provider_id: "ghidra" }))
.isError,
true,
);
assert.notEqual((await call("list_documents", {})).isError, true);
}
assert.notEqual((await call("close_binary", {})).isError, true);
return {
refused: expected,
matching_target_reopened: matching !== undefined,
};
} finally {
await client.close();
await transport.close();
}
}
async function projectState(paths) {
const files = [];
const walk = async (dir) => {
for (const entry of await readdir(dir, { withFileTypes: true })) {
const path = join(dir, entry.name);
if (entry.isDirectory()) await walk(path);
else if (entry.isFile()) files.push(path);
}
};
for (const path of paths) {
files.push(path);
if (path.endsWith(".gpr")) await walk(`${path.slice(0, -4)}.rep`);
}
files.sort();
return Promise.all(
files.map(async (path) => [
path,
createHash("sha256")
.update(await readFile(path))
.digest("hex"),
]),
);
}
+2
View File
@@ -1,5 +1,7 @@
export function applyReleasePleasePatch(directory?: string): Promise<void>;
export function describeReleaseProposalError(error: unknown): string;
export function releaseProposalSearchDepth(
run: (command: string, args: string[]) => Promise<string>,
sourceSha: string,
+9
View File
@@ -13,6 +13,15 @@ const patch = fileURLToPath(
new URL("../../patches/release-please+17.3.0.patch", import.meta.url),
);
export function describeReleaseProposalError(error) {
if (!(error instanceof Error)) return String(error);
const description = error.message || error.name || "Unknown error";
const status = error.status;
return Number.isInteger(status) && status >= 400 && status <= 599
? `${description} (HTTP ${status})`
: description;
}
export async function applyReleasePleasePatch(
directory = dirname(require.resolve("release-please/package.json")),
) {
+3
View File
@@ -7,6 +7,7 @@ import xml.etree.ElementTree as ET
from zipfile import ZIP_DEFLATED, ZipFile, ZipInfo
from website import PageMetadata, page_url
from website_agents import prepare_discovery, prepare_markdown
EXAMPLE_FILES = (
@@ -81,6 +82,8 @@ def prepare_assets(site):
prepare_sharing_image(site)
prepare_sitemap(site)
prepare_notes_example(site)
prepare_discovery(Path(__file__).resolve().parent.parent, site)
prepare_markdown(site)
if __name__ == "__main__":
+2 -9
View File
@@ -70,15 +70,8 @@ if (!(await compiledRuntimeExists(runtimeFiles))) {
guardClosedStdout();
// Doctor runs outside Incur, so handle its global timing flag here. Keep a
// token consumed by --format as a value, even when it looks like a flag.
const outputValues = new Set(
builtinArguments
.splitIncurBuiltinArguments(doctorArguments)
.outputValues.map(({ index }) => index),
);
const doctorCliArguments = doctorArguments.filter(
(argument, index) => argument !== "--timings" || outputValues.has(index),
);
const timingRequested = doctorCliArguments.length !== doctorArguments.length;
const { arguments: doctorCliArguments, enabled: timingRequested } =
builtinArguments.splitCliTimingArguments(doctorArguments);
const started = performance.now();
try {
const { runProductionMcpDoctorCli } =
+9
View File
@@ -15,12 +15,16 @@ const [
{ parseConfig },
{ silentLogger },
{ PROMPT_CONTRACTS },
{ TOOL_CONTRACTS },
{ getEvidenceInputContract },
] = await Promise.all([
read("server/createServer.js"),
read("composition/binary.js"),
read("config/parseConfig.js"),
read("logger.js"),
read("contracts/promptContracts.js"),
read("contracts/toolContracts.js"),
read("domain/evidenceInputContract.js"),
]);
const recordCatalog = async (profile) => {
// Catalog generation is independent of the host account and user configuration.
@@ -71,6 +75,11 @@ const recordCatalog = async (profile) => {
instructions: client.getInstructions(),
identity: server.catalogIdentity,
tools,
evidenceReferenceAlternatives: TOOL_CONTRACTS.filter(
(contract) =>
getEvidenceInputContract(contract.inputSchema)
.hasEvidenceReferenceAlternative,
).map((contract) => contract.name),
prompts,
};
const encoded = JSON.stringify(catalog);
+2 -1
View File
@@ -4,6 +4,7 @@ import { appendFile } from "node:fs/promises";
import { parseArgs, promisify } from "node:util";
import {
applyReleasePleasePatch,
describeReleaseProposalError,
releaseProposalSearchDepth,
} from "./lib/release-proposal.mjs";
@@ -63,7 +64,7 @@ async function main() {
await main().catch((error) => {
process.stderr.write(
`Release proposal failed: ${error instanceof Error ? error.message : String(error)}\n`,
`Release proposal failed: ${describeReleaseProposalError(error)}\n`,
);
process.exitCode = 1;
});
+109
View File
@@ -0,0 +1,109 @@
"""Check the public build artifacts and Markdown content contract."""
import hashlib
import json
from pathlib import Path
import tempfile
import unittest
from zipfile import ZipFile
from bs4 import BeautifulSoup
import yaml
from website_agents import prepare_discovery, prepare_markdown
ROOT = Path(__file__).resolve().parent.parent
class AgentPublishingChecks(unittest.TestCase):
def test_published_skill_digest_covers_every_reference_and_catalogs_resolve(self):
site = ROOT / "website/_site"
index = json.loads((site / ".well-known/agent-skills/index.json").read_text())
self.assertEqual(index["$schema"], "https://schemas.agentskills.io/discovery/0.2.0/schema.json")
skill, = index["skills"]
self.assertEqual(skill["name"], "reverse-engineer-anything")
self.assertEqual(skill["type"], "archive")
artifact = site / skill["url"].removeprefix("https://rea.tools/")
self.assertEqual(skill["digest"], "sha256:" + hashlib.sha256(artifact.read_bytes()).hexdigest())
source = ROOT / ".agents/skills/reverse-engineer-anything"
with ZipFile(artifact) as archive:
expected = {f"{source.name}/{p.relative_to(source).as_posix()}": p.read_bytes()
for p in source.rglob("*.md")}
self.assertEqual(set(archive.namelist()), set(expected))
for name, data in expected.items():
self.assertEqual(archive.read(name), data)
manifest = archive.read(f"{source.name}/SKILL.md").decode()
metadata = yaml.safe_load(manifest[4:].split("\n---\n", 1)[0])
self.assertEqual(skill["description"], metadata["description"])
catalog = json.loads((site / ".well-known/ai-catalog.json").read_text())
self.assertEqual(catalog["specVersion"], "1.0")
entry, = catalog["entries"]
self.assertEqual(entry["identifier"], "urn:air:rea.tools:skill:reverse-engineer-anything")
self.assertEqual(entry["url"], skill["url"])
self.assertNotIn("data", entry)
ard = json.loads((site / ".well-known/ard.json").read_text())
resource, = ard["entries"]
self.assertEqual(resource["url"], skill["url"])
self.assertTrue(2 <= len(resource["representativeQueries"]) <= 5)
def test_reference_changes_invalidate_digest_and_yaml_scalars_are_parsed(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
source = root / ".agents/skills/reverse-engineer-anything"
source.mkdir(parents=True)
(source / "SKILL.md").write_text(
'---\nname: reverse-engineer-anything\ndescription: >-\n Inspect programs:\n preserve evidence.\n---\n# REA\n'
)
(source / "references").mkdir()
reference = source / "references/evidence.md"
reference.write_text("First revision")
site = root / "site"
def build():
prepare_discovery(root, site)
return json.loads((site / ".well-known/agent-skills/index.json").read_text())["skills"][0]
before = build()
self.assertEqual(before["description"], "Inspect programs: preserve evidence.")
self.assertEqual(build(), before)
reference.write_text("Second revision")
self.assertNotEqual(build()["digest"], before["digest"])
def test_markdown_preserves_content_and_resources_without_publishing_excluded_pages(self):
with tempfile.TemporaryDirectory() as directory:
site = Path(directory)
page = site / "zh-CN/guides/test/index.html"
page.parent.mkdir(parents=True)
page.write_text('''<html><head><title>REA: test</title></head><body>
<nav>Outside navigation</nav><main><aside>Shortened display paths.</aside>
<h1>Inspect a program</h1><nav>Contents navigation</nav>
<ol><li><span class="flow-label">Read</span><span class="flow-node"><code>input.exe</code></span><span class="flow-note">Keep evidence.</span></li></ol>
<p><a href="../other/?q=a%2Fb#code">Another guide</a></p>
<pre><code>const value = a &lt; b ? "x_y" : "&amp;";\n keepIndent();</code></pre>
<details><summary>Evidence</summary><p>Hidden evidence remains available.</p></details>
<img src="../../../assets/diagram.svg" alt="Investigation diagram">
<button>Copy code</button><script>doNotPublish()</script></main></body></html>''')
fixture = site / "examples/notes-web/index.html"
fixture.parent.mkdir(parents=True)
fixture.write_text("<main>Target application</main>")
fallback = site / "zh-CN/fallback/index.html"
fallback.parent.mkdir(parents=True)
fallback.write_text('<head><meta name="robots" content="noindex, follow"></head><main>English fallback notice.</main>')
prepare_markdown(site)
markdown = page.with_suffix(".md").read_text()
self.assertIn('# Inspect a program', markdown)
self.assertIn('const value = a < b ? "x_y" : "&";\n keepIndent();', markdown)
self.assertIn("Hidden evidence remains available.", markdown)
self.assertIn("Shortened display paths.", markdown)
self.assertNotIn("Read`input.exe`", markdown)
self.assertIn("https://rea.tools/zh-CN/guides/other/?q=a%2Fb#code", markdown)
self.assertIn("https://rea.tools/assets/diagram.svg", markdown)
for text in ("Outside navigation", "Contents navigation", "Copy code", "doNotPublish"):
self.assertNotIn(text, markdown)
self.assertFalse(fixture.with_suffix(".md").exists())
self.assertFalse(fallback.with_suffix(".md").exists())
html = BeautifulSoup(page.read_text(), "html.parser")
self.assertEqual(html.find("link", rel="alternate")["href"], "https://rea.tools/zh-CN/guides/test/index.md")
if __name__ == "__main__":
unittest.main()
+21 -16
View File
@@ -38,15 +38,16 @@ if (
!isAbsolute(strace ?? "")
)
throw new Error(
"verify:binary:layout requires Linux x64, absolute REA_PWNTOOLS_PYTHON (pwntools 4.15.0/pyelftools 0.33/Unicorn 2.1.2) and REA_VERIFY_STRACE_COMMAND. gcc, ld, strip and the i686/ARM/MIPS/MIPSel linux-gnu cross gcc and readelf must be available. No target is executed.",
"verify:binary:layout requires Linux x64, absolute REA_PWNTOOLS_PYTHON (pwntools 4.15.0/pyelftools 0.33/Unicorn 2.1.2) and REA_VERIFY_STRACE_COMMAND. gcc, ld, strip and the AArch64/i686/ARM/MIPS/MIPSel linux-gnu cross gcc and readelf must be available. No target is executed.",
);
const run = createVerifierRun();
const execute = promisify(execFile);
const ELF32_TOOLCHAINS = [
["i686-linux-gnu", "EM_386", "little"],
["arm-linux-gnueabi", "EM_ARM", "little"],
["mips-linux-gnu", "EM_MIPS", "big"],
["mipsel-linux-gnu", "EM_MIPS", "little"],
const CROSS_TOOLCHAINS = [
["aarch64-linux-gnu", "EM_AARCH64", 64, "little"],
["i686-linux-gnu", "EM_386", 32, "little"],
["arm-linux-gnueabi", "EM_ARM", 32, "little"],
["mips-linux-gnu", "EM_MIPS", 32, "big"],
["mipsel-linux-gnu", "EM_MIPS", 32, "little"],
];
for (const command of [
"gcc",
@@ -54,7 +55,7 @@ for (const command of [
"strip",
"/usr/bin/prlimit",
strace,
...ELF32_TOOLCHAINS.flatMap(([prefix]) => [
...CROSS_TOOLCHAINS.flatMap(([prefix]) => [
`${prefix}-gcc`,
`${prefix}-readelf`,
]),
@@ -347,14 +348,15 @@ try {
cases++;
}
}
// ELF32 profiles: cross-compiled, never executed, checked against GNU readelf.
// Cross profiles: cross-compiled, never executed, checked against GNU readelf.
for (const [
prefix,
machine,
bits,
byteOrder,
kind,
flags,
] of ELF32_TOOLCHAINS.flatMap((toolchain) => [
] of CROSS_TOOLCHAINS.flatMap((toolchain) => [
[...toolchain, "executable", ["-fPIE", "-pie"]],
[...toolchain, "relocatable", ["-c", "-fPIC"]],
])) {
@@ -423,16 +425,18 @@ try {
`readelf relocation count: ${name}`,
);
for (const [entryIndex, [, offset, info]] of rows.entries()) {
// ELF32 r_info uses the low byte for type and the upper 24 bits for
// the symbol index. readelf has already decoded the file's byte order.
// ELF32 r_info packs an 8-bit type under a 24-bit symbol index;
// ELF64 packs a 32-bit type under a 32-bit index. readelf has already
// decoded the file's byte order.
const decodedInfo = BigInt(`0x${info}`);
const typeBits = bits === 32 ? 8n : 32n;
expectedRelocations.push({
section_index: section.index,
entry_index: entryIndex,
reported_offset: BigInt(`0x${offset}`),
type: Number(decodedInfo & 0xffn),
type: Number(decodedInfo & ((1n << typeBits) - 1n)),
symbol_table_index: section.link,
symbol_index: Number(decodedInfo >> 8n),
symbol_index: Number(decodedInfo >> typeBits),
});
}
}
@@ -445,7 +449,7 @@ try {
const value = await inspect(mode, path);
assert.deepEqual(value.architecture, {
machine,
bits: 32,
bits,
byte_order: byteOrder,
});
assert.equal(
@@ -1312,7 +1316,8 @@ try {
}
}
}
unsupported.writeUInt16LE(183, 18);
// EM_RISCV: AArch64 (183) is now an admitted profile.
unsupported.writeUInt16LE(243, 18);
const core = Buffer.from(object);
core.writeUInt16LE(4, 16);
for (const [name, bytes, category] of [
@@ -1337,7 +1342,7 @@ try {
"invalid_input",
],
["undersized-program-header", undersizedProgramHeader, "invalid_input"],
["arm64", unsupported, "unsupported_target"],
["riscv64", unsupported, "unsupported_target"],
["core", core, "unsupported_target"],
]) {
const path = join(root.path, name);
+7 -1
View File
@@ -245,7 +245,13 @@ try {
scope,
{ environment: process.env },
);
if (mismatch.ok || !mismatch.error.message.includes("target-mismatch"))
// A selection failure carries its helper code in the issue, not in its message.
if (
mismatch.ok ||
!mismatch.error.issues?.some(({ message }) =>
message.startsWith("target-mismatch:"),
)
)
throw new Error("Real helper did not reject changed target bytes");
report = {
ok: true,
@@ -161,6 +161,38 @@ try {
fromNumericOffset.normalized_result,
fromOffset.normalized_result,
);
const missingProcedure = "rea_missing_procedure_fixture";
const missingName = await client.callTool(
{
name: "procedure_pseudo_code",
arguments: { procedure: missingProcedure },
},
{ timeout: 240000 },
);
assert.equal(missingName.isError, true);
const missingNameError = JSON.parse(
missingName.content.find((item) => item.type === "text").text,
).error;
assert.equal(missingNameError.code, "invalid_request");
assert.match(missingNameError.message, /rea_missing_procedure_fixture/u);
assert.match(missingNameError.remediation.action, /list_procedures/u);
assert.match(missingNameError.remediation.action, /does not prove/u);
assert.equal(missingNameError.recovery, undefined);
const unmappedOffset = await client.callTool(
{
name: "procedure_pseudo_code",
arguments: { procedure: "file-offset:0x1fffffffffffff" },
},
{ timeout: 240000 },
);
assert.equal(unmappedOffset.isError, true);
const unmappedError = JSON.parse(
unmappedOffset.content.find((item) => item.type === "text").text,
).error;
assert.equal(unmappedError.code, "invalid_request");
assert.match(unmappedError.message, /no loaded source mapping/u);
assert.match(unmappedError.remediation.action, /inspect_native_load_image/u);
assert.equal(unmappedError.recovery, undefined);
const missingAddress = "ram:0xffffffff";
const rejected = await client.callTool(
{ name: "procedure_pseudo_code", arguments: { procedure: missingAddress } },
+62
View File
@@ -276,6 +276,67 @@ try {
assert.equal(updated.annotations.inline_comment, changes.inline_comment);
assert.equal(updated.dossier.procedure.name, changes.name);
assert.ok(updated.dossier.pseudocode.includes(changes.name));
// Exercise the documented recovery through the public MCP catalog on real
// Ghidra, rather than assuming the provider exposes direct comment methods.
const contextCall = async (name, args) => {
const definition = tools.find((tool) => tool.name === name);
assert.ok(definition?.outputSchema, `${name} must be advertised`);
const response = await client.callTool(
{ name, arguments: args },
{ timeout: 240000, toolDefinition: definition },
);
return requireMcpOutput(response, name);
};
const context = (
await contextCall("inspect_address_context", {
address: fixture.entry,
})
).result;
assert.equal(context.procedure.state, "available");
assert.equal(context.procedure.value.found, true);
for (const facet of [context.comment, context.inline_comment]) {
assert.equal(facet.state, "unavailable");
assert.match(
facet.remediation,
/analyze_function.*parent_evidence_id.*inspect_analysis_view/u,
);
}
assert.match(context.bookmarks.remediation, /binary_session/u);
const summary = parseEvidence(
await contextCall("analyze_function", {
procedure: context.procedure.value.procedure.address,
}),
).normalized_result;
const comments = parseEvidence(
await contextCall("inspect_analysis_view", {
source: {
kind: "retained-evidence",
evidence_id: summary.parent_evidence_id,
},
view: { kind: "native", facet: "comments" },
}),
).normalized_result;
for (const [kind, text] of [
["comment", changes.comment],
["inline", changes.inline_comment],
])
assert.ok(
comments.item.some(
(item) =>
item.address === context.procedure.value.query_address &&
item.kind === kind &&
item.text === text,
),
);
const outside = (
await contextCall("inspect_address_context", {
address: unlistedAddress,
})
).result;
assert.equal(outside.procedure.state, "available");
assert.equal(outside.procedure.value.found, false);
assert.equal(outside.comment.state, "unavailable");
assert.match(outside.comment.remediation, /binary_session/u);
assert.equal(
await call("procedure_address", { procedure: changes.name }),
fixture.entry,
@@ -386,6 +447,7 @@ try {
fresh_decompiler: true,
clear_comments: true,
cli_mcp_parity: true,
address_context_comment_recovery: true,
},
context_input_errors_preserved: true,
original_unchanged: true,
+29 -1
View File
@@ -619,7 +619,35 @@ export async function verifyNativeTypeLayout(client, typeNames) {
)
)
throw new Error("Nested union or enum layout drifted");
return { layout, payload, state };
const bitsSymbol = typeNames.find((item) =>
matchesSymbol(item.value, "rea_ghidra_inventory_bits"),
);
if (bitsSymbol === undefined)
throw new Error("Bit-field fixture symbol unavailable");
const bits = await functionCall(client, "inspect_native_data_type", {
document: null,
address: bitsSymbol.address,
});
const low = bits.fields.find((field) => field.name === "low");
if (
bits.kind !== "struct" ||
low?.bit_size !== 3 ||
low.bit_field?.declared_bit_size !== 3 ||
low.bit_field.component_type_lookup !== "not-found" ||
low.bit_field.base_type_lookup !== "equivalent"
)
throw new Error(`Bit-field identity unavailable: ${JSON.stringify(bits)}`);
const component = await functionCall(client, "inspect_native_data_type", {
document: null,
type: low.type_id,
});
const base = await functionCall(client, "inspect_native_data_type", {
document: null,
type: low.bit_field.base_type_id,
});
if (component.status !== "unavailable" || base.status !== "available")
throw new Error("Bit-field component or base lookup drifted");
return { layout, payload, state, bits, base };
}
export async function verifyRelativeSwitch(client, procedures, entrySize) {
+34 -2
View File
@@ -46,8 +46,40 @@ async function expectRemoteFailure(client, operation, parameters, code) {
return { operation, code };
}
async function verifyBridgeRejections(client) {
async function verifyBridgeRejections(client, names) {
const global = names.find((item) =>
matchesSymbol(item.value, "rea_ghidra_inventory_global"),
);
if (global === undefined)
throw new Error("Ghidra initialized global probe is unavailable");
const mapping = await call(client, "address_to_file_offset", {
document: null,
address: global.address,
});
if (!Number.isSafeInteger(mapping.file_offset))
throw new Error("Ghidra initialized global has no exact file offset");
return {
unknownProcedure: await expectRemoteFailure(
client,
"procedure_address",
{ document: null, procedure: "rea_missing_procedure_fixture" },
"procedure_not_found",
),
mappedDataOffset: await expectRemoteFailure(
client,
"procedure_pseudo_code",
{
document: null,
procedure: `file-offset:0x${mapping.file_offset.toString(16)}`,
},
"procedure_not_found",
),
unmappedFileOffset: await expectRemoteFailure(
client,
"procedure_pseudo_code",
{ document: null, procedure: "file-offset:0x1fffffffffffff" },
"file_offset_unmapped",
),
unknownDocument: await expectRemoteFailure(
client,
"list_segments",
@@ -86,7 +118,7 @@ export async function verifyInventoryOperations({
});
const rejections =
variant === "debug" ? await verifyBridgeRejections(client) : null;
variant === "debug" ? await verifyBridgeRejections(client, names) : null;
const stringSearch = await call(client, "search_strings", {
pattern: String.raw`^REA_GHIDRA_(?:INVENTORY_ENTRY|LEAF_VALUE)$`,
mode: "regex",
+119 -1
View File
@@ -6,12 +6,13 @@ import {
mkdtemp,
mkdir,
readFile,
readdir,
realpath,
rm,
writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { basename, join, relative } from "node:path";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";
import { Client } from "@modelcontextprotocol/client";
@@ -41,8 +42,10 @@ import {
import {
requireMcpToolError,
requireMcpOperationResult,
requireMcpOutput,
} from "./lib/mcp-verifier-results.mjs";
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
import { runOwnedCommand } from "../dist/process/OwnedCommand.js";
// Optional cross-target lane. No tool acquisition and no target execution.
const mode = process.argv[2];
@@ -90,6 +93,114 @@ for (const setting of ["GHIDRA_HEADLESS_MAXMEM", "GHIDRA_MAXMEM"]) {
if (process.env[setting] !== undefined) env[setting] = process.env[setting];
}
/** Observe an existing project, and verify REA never alters the caller's copy. */
async function verifyExistingMipsProject(fixture, byteOrder) {
const projectDir = join(workspace, `${byteOrder}-existing-project`);
await mkdir(projectDir);
assert.ok(installation.analyzeHeadlessPath);
await runOwnedCommand(
{
command: installation.analyzeHeadlessPath,
arguments: [
projectDir,
"ExistingMips",
"-import",
fixture.path,
"-noanalysis",
],
env,
runId: run.run_id,
expectedCommand: null,
},
{ timeoutMs: 180000, diagnosticBytes: 1024 * 1024 },
);
const projectPath = join(projectDir, "ExistingMips.gpr");
const digestTree = async () => {
const paths = [projectPath];
const walk = async (dir) => {
for (const entry of await readdir(dir, { withFileTypes: true })) {
const path = join(dir, entry.name);
if (entry.isDirectory()) await walk(path);
else if (entry.isFile()) paths.push(path);
}
};
await walk(join(projectDir, "ExistingMips.rep"));
return Promise.all(
paths.sort().map(async (path) => [
relative(projectDir, path),
createHash("sha256")
.update(await readFile(path))
.digest("hex"),
]),
);
};
const before = await digestTree();
const transport = new StdioClientTransport({
command: process.execPath,
args: [entrypoint, "mcp"],
env: {
...env,
REA_GHIDRA_PROJECT_PATH: projectPath,
REA_GHIDRA_PROGRAM_NAME: basename(fixture.path),
},
stderr: "pipe",
});
const client = new Client({ name: "rea-existing-mips-proof", version: "1" });
try {
await client.connect(transport);
const opened = await client.callTool(
{
name: "open_binary",
arguments: { path: fixture.path, provider_id: "ghidra" },
},
{ timeout: 180000 },
);
requireMcpOperationResult(opened, "open_binary");
const result = await client.callTool(
{ name: "list_procedures", arguments: {} },
{ timeout: 180000 },
);
const observation = parseEvidence(
requireMcpOutput(result, "list_procedures"),
);
assert.ok(Array.isArray(observation.normalized_result));
const parameters = observation.analysis_profile?.parameters;
assert.equal(parameters?.import_mode, "existing-project-private-copy-v1");
assert.equal(parameters?.mips_elf?.byte_order, byteOrder);
assert.equal(parameters?.mips_support_lane, undefined);
assert.deepEqual(parameters?.original_analysis, {
completeness: "unknown",
settings: "unknown",
});
requireMcpOperationResult(
await client.callTool({ name: "close_binary", arguments: {} }),
"close_binary",
);
} finally {
try {
await client.close();
} finally {
await transport.close();
}
}
assert.deepEqual(
await digestTree(),
before,
"REA changed the source project",
);
assert.equal(
createHash("sha256")
.update(await readFile(fixture.path))
.digest("hex"),
fixture.sha256,
);
return {
byte_order: byteOrder,
project_unchanged: true,
target_unchanged: true,
};
}
/**
* Typed float signatures must use soft-float o32 storage, and callers must see
* the source constants through it rather than FPU inputs.
@@ -320,6 +431,10 @@ try {
);
}
assert.equal(resolved.value.sha256, fixture.sha256);
const existingProject =
!psp && floatAbi === "hard"
? await verifyExistingMipsProject(fixture, byteOrder)
: undefined;
const snapshotPath = join(
workspace,
`${byteOrder}-${floatAbi}.snapshot.json`,
@@ -682,6 +797,9 @@ try {
}
reports.push({
...fixture,
...(existingProject === undefined
? {}
: { existing_project: existingProject }),
independent_reader: independent,
effective_language: image.observations.language_id,
effective_compiler_spec: image.observations.compiler_spec_id,
+34
View File
@@ -110,6 +110,39 @@ try {
// the plain loader would have chosen e500 for this header.
const image = await call("inspect_native_load_image");
assertPowerPcLoadedImage(image.observations, fixture.sha256);
const names = await call("list_names");
const bitsName = names.find((item) => item.value === "rea_ppc_bits");
assert.ok(bitsName, "Big-endian bit-field fixture is missing");
const bitsBytes = await call("read_bytes", {
address: bitsName.address,
length: 4,
});
assert.equal(bitsBytes.bytes_hex, "22000000");
const bits = await call("inspect_native_data_type", {
address: bitsName.address,
});
assert.equal(bits.kind, "struct");
const bitFields = bits.fields.filter((field) => field.bit_field);
assert.deepEqual(
bitFields.map((field) => [
field.name,
field.bit_size,
field.bit_offset,
]),
[
["first", 3, 5],
["second", 5, 0],
],
);
for (const field of bitFields) {
assert.equal(field.bit_field.component_type_lookup, "not-found");
assert.equal(field.bit_field.base_type_lookup, "equivalent");
assert.equal(field.bit_field.declared_bit_size, field.bit_size);
const base = await call("inspect_native_data_type", {
type: field.bit_field.base_type_id,
});
assert.equal(base.status, "available");
}
const procedures = await call("list_procedures");
const entry = procedures.find((p) => p.value === "rea_ppc_entry");
const leaf = procedures.find((p) => p.value === "rea_ppc_leaf");
@@ -245,6 +278,7 @@ try {
"fixed instruction bytes and immediate",
"conditional branch destination",
"global word and marker bytes",
"big-endian bit-field storage and base type handoff",
"CLI/MCP and snapshot identity",
],
procedures: procedures.length,
+425 -6
View File
@@ -1,7 +1,14 @@
import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import { createHash } from "node:crypto";
import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
import {
mkdtemp,
mkdir,
readFile,
readdir,
rm,
writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
@@ -11,12 +18,18 @@ import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { parseEvidence } from "../dist/domain/evidence.js";
import { cleanupOwnedProcessGroup } from "../dist/process/ProcessOwnership.js";
import {
ProviderProcessSupervisor,
spawnOwnedProviderProcess,
} from "../dist/process/ProviderProcess.js";
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
import {
requireMcpLifecycleResult,
requireMcpOutput,
} from "./lib/mcp-verifier-results.mjs";
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
if (process.argv.length !== 2)
throw new Error("Usage: node scripts/verify-real-ghidra-raw.mjs");
@@ -39,6 +52,7 @@ assert.equal(installation.status, "available", JSON.stringify(installation));
const run = createVerifierRun();
const workspace = await mkdtemp(join(tmpdir(), "rea-raw-proof-"));
let fixtureCleaned = true;
const runtime = join(workspace, "runtime");
await mkdir(runtime);
const bytes = Buffer.from("1eff2fe1", "hex"); // ARM little-endian bx lr.
@@ -53,6 +67,17 @@ const env = {
REA_ANALYSIS_PROVIDER: "ghidra",
REA_PROCESS_RUN_ID: run.run_id,
GHIDRA_INSTALL_DIR: process.env.GHIDRA_INSTALL_DIR,
...(process.env.REA_MCP_INPUT_SCHEMA_PROFILE === undefined
? {}
: {
REA_MCP_INPUT_SCHEMA_PROFILE: process.env.REA_MCP_INPUT_SCHEMA_PROFILE,
}),
...(process.env.REA_MCP_OUTPUT_SCHEMA_PROFILE === undefined
? {}
: {
REA_MCP_OUTPUT_SCHEMA_PROFILE:
process.env.REA_MCP_OUTPUT_SCHEMA_PROFILE,
}),
...(process.env.JAVA_HOME === undefined
? {}
: { JAVA_HOME: process.env.JAVA_HOME }),
@@ -78,8 +103,13 @@ transport.stderr?.on("data", (chunk) => {
stderr = (stderr + chunk.toString()).slice(-65536);
});
let report;
let searchDefinition;
try {
await client.connect(transport);
searchDefinition = (await client.listTools()).tools.find(
(tool) => tool.name === "search_bytes",
);
assert.ok(searchDefinition?.outputSchema);
const profiles = [];
const sessions = [];
for (const entryPoint of ["0x08000000", "0x8000000", "0x09000000"]) {
@@ -300,6 +330,198 @@ try {
assert.equal(wrongCursor.isError, true);
await call("close_binary");
assert.deepEqual(await readFile(repeatedPath), repeatedBytes);
// Two adjacent virtual regions backed by disjoint file ranges. The same
// signature crosses their boundary and overlaps at every possible start.
const splitPath = join(workspace, "split.elf");
const splitBytes = splitSearchElf(0);
const splitSha256 = createHash("sha256").update(splitBytes).digest("hex");
await writeFile(splitPath, splitBytes);
await call("open_binary", { path: splitPath, provider_id: "ghidra" });
let cursorReopened = false;
for (const pattern of ["aa aa aa", "aa ?? aa"]) {
let cursor;
const addresses = [];
const pages = [];
do {
const evidence = await call(
"search_bytes",
{
pattern,
start: "0x401000",
end: "0x401007",
limit: 2,
...(cursor === undefined ? {} : { cursor }),
},
splitSha256,
"elf",
);
const result = evidence.normalized_result;
addresses.push(...result.matches.map((match) => match.address));
pages.push({
matches: result.matches.map(({ address, segment, file_offset }) => ({
address,
segment,
file_offset,
})),
blocks: result.searched.blocks,
});
cursor = result.next_cursor ?? undefined;
if (cursor !== undefined && !cursorReopened) {
const snapshot = join(workspace, "split-snapshot.json");
await call("close_binary", { snapshot_path: snapshot });
await call("open_binary", {
path: splitPath,
provider_id: "ghidra",
snapshot_path: snapshot,
});
cursorReopened = true;
}
} while (cursor !== undefined);
assert.deepEqual(addresses, [
"0x401000",
"0x401001",
"0x401002",
"0x401003",
"0x401004",
"0x401005",
]);
assert.deepEqual(pages, [
{
matches: [
{ address: "0x401000", segment: "segment_0", file_offset: 0x1000 },
{ address: "0x401001", segment: "segment_0", file_offset: 0x1001 },
],
blocks: [
{
segment: "segment_0",
start: "0x401000",
end: "0x401003",
bytes: 4,
},
{
segment: "segment_1",
start: "0x401004",
end: "0x401004",
bytes: 1,
},
],
},
{
matches: [
{ address: "0x401002", segment: "segment_0", file_offset: 0x1002 },
{ address: "0x401003", segment: "segment_0", file_offset: 0x1003 },
],
blocks: [
{
segment: "segment_0",
start: "0x401002",
end: "0x401003",
bytes: 2,
},
{
segment: "segment_1",
start: "0x401004",
end: "0x401006",
bytes: 3,
},
],
},
{
matches: [
{ address: "0x401004", segment: "segment_1", file_offset: 0x3000 },
{ address: "0x401005", segment: "segment_1", file_offset: 0x3001 },
],
blocks: [
{
segment: "segment_1",
start: "0x401004",
end: "0x401007",
bytes: 4,
},
],
},
]);
const bounded = await call(
"search_bytes",
{
pattern,
start: "0x401000",
end: "0x401005",
limit: 20,
},
splitSha256,
"elf",
);
assert.deepEqual(
bounded.normalized_result.matches.map((match) => match.address),
addresses.slice(0, 4),
);
}
assert.equal(cursorReopened, true);
const fileStart = await call(
"address_to_file_offset",
{ address: "0x401000" },
splitSha256,
"elf",
);
const fileSecond = await call(
"address_to_file_offset",
{ address: "0x401004" },
splitSha256,
"elf",
);
assert.equal(fileStart.normalized_result.file_offset, 0x1000);
assert.equal(fileSecond.normalized_result.file_offset, 0x3000);
await call("close_binary");
assert.deepEqual(await readFile(splitPath), splitBytes);
await verifyCrossBlockProject(splitPath, splitSha256);
const gapPath = join(workspace, "gap.elf");
const gapBytes = splitSearchElf(1);
const gapSha256 = createHash("sha256").update(gapBytes).digest("hex");
await writeFile(gapPath, gapBytes);
await call("open_binary", { path: gapPath, provider_id: "ghidra" });
const gap = await call(
"search_bytes",
{
pattern: "aa aa aa aa ?? aa aa aa aa",
start: "0x401000",
end: "0x401008",
},
gapSha256,
"elf",
);
assert.deepEqual(gap.normalized_result.matches, []);
await call("close_binary");
assert.deepEqual(await readFile(gapPath), gapBytes);
const zeroFillPath = join(workspace, "initialized-zerofill.elf");
const zeroFillBytes = splitSearchElf(1, true);
const zeroFillSha256 = createHash("sha256")
.update(zeroFillBytes)
.digest("hex");
await writeFile(zeroFillPath, zeroFillBytes);
await call("open_binary", { path: zeroFillPath, provider_id: "ghidra" });
const zeroFill = await call(
"search_bytes",
{
pattern: "aa aa aa aa ?? aa aa aa aa",
start: "0x401000",
end: "0x401008",
},
zeroFillSha256,
"elf",
);
// This Ghidra loader initializes the ELF zero-fill byte in this fixture.
assert.deepEqual(
zeroFill.normalized_result.matches.map((match) => match.address),
["0x401000"],
);
assert.deepEqual(
zeroFill.normalized_result.searched.skipped_uninitialized,
[],
);
await call("close_binary");
assert.deepEqual(await readFile(zeroFillPath), zeroFillBytes);
report = {
ok: true,
mocked: false,
@@ -321,6 +543,7 @@ try {
original_unchanged: true,
x86_targets: x86Targets,
byte_search: {
cross_block_paging_and_gap: true,
matches: 1005,
page_size: 1000,
first_search_elapsed_ms: searchElapsedMs,
@@ -336,7 +559,7 @@ try {
} finally {
await client.close();
await transport.close();
await rm(workspace, { recursive: true, force: true });
if (fixtureCleaned) await rm(workspace, { recursive: true, force: true });
}
const verifierRun = await completeVerifierRun(run);
assert.equal(verifierRun.process_lineage.status, "verified");
@@ -349,15 +572,211 @@ process.stdout.write(
}) + "\n",
);
async function call(name, args = {}, targetSha256 = sha256) {
async function call(
name,
args = {},
targetSha256 = sha256,
targetFormat = "raw-binary",
) {
const result = await client.callTool(
{ name, arguments: args },
{ timeout: 240000 },
{
timeout: 240000,
...(name === "search_bytes" ? { toolDefinition: searchDefinition } : {}),
},
);
if (["open_binary", "close_binary", "binary_session"].includes(name))
return requireMcpLifecycleResult(result, name);
const evidence = parseEvidence(requireMcpOutput(result, name));
const output = requireMcpOutput(result, name);
const evidence = parseEvidence(output);
assert.equal(evidence.subject.digest.sha256, targetSha256);
assert.equal(evidence.subject.format, "raw-binary");
assert.equal(evidence.subject.format, targetFormat);
return evidence;
}
// Minimal static ELF64 with adjacent (or one-byte-separated) virtual segments
// and noncontiguous file backing. The target is never executed.
function splitSearchElf(gap, zeroFill = false) {
const result = Buffer.alloc(0x3004);
result.set([0x7f, 0x45, 0x4c, 0x46, 2, 1, 1, 0]);
result.writeUInt16LE(2, 16); // ET_EXEC
result.writeUInt16LE(62, 18); // EM_X86_64
result.writeUInt32LE(1, 20);
result.writeBigUInt64LE(0x401000n, 24);
result.writeBigUInt64LE(64n, 32);
result.writeUInt16LE(64, 52);
result.writeUInt16LE(56, 54);
result.writeUInt16LE(2, 56);
const segment = (offset, fileOffset, address, memorySize = 4) => {
result.writeUInt32LE(1, offset); // PT_LOAD
result.writeUInt32LE(4, offset + 4); // PF_R
result.writeBigUInt64LE(BigInt(fileOffset), offset + 8);
result.writeBigUInt64LE(BigInt(address), offset + 16);
result.writeBigUInt64LE(BigInt(address), offset + 24);
result.writeBigUInt64LE(4n, offset + 32);
result.writeBigUInt64LE(BigInt(memorySize), offset + 40);
result.writeBigUInt64LE(1n, offset + 48);
result.fill(0xaa, fileOffset, fileOffset + 4);
};
segment(64, 0x1000, 0x401000, zeroFill ? 5 : 4);
segment(120, 0x3000, 0x401004 + gap);
return result;
}
// Add test-only memory to an owned project, then let REA inspect its private copy.
async function verifyCrossBlockProject(target, targetSha256) {
const projectDirectory = join(workspace, "boundary-project");
await mkdir(projectDirectory);
const scriptDirectory = fileURLToPath(
new URL("../tests/conformance/ghidra", import.meta.url),
);
const launch = await spawnOwnedProviderProcess({
command: installation.analyzeHeadlessPath,
arguments: [
projectDirectory,
"Boundary",
"-import",
target,
"-noanalysis",
"-scriptPath",
scriptDirectory,
"-postScript",
"ReaCrossBlockFixture.java",
],
env,
runId: run.run_id,
expectedCommand: null,
});
fixtureCleaned = false;
const supervisor = new ProviderProcessSupervisor({
...launch,
ownsProcessLifetime: true,
cleanup: () => cleanupOwnedProcessGroup(launch.ownership),
});
await withVerifierCleanup(async () => {
assert.ok(await supervisor.waitForExit(180000), "Fixture setup timed out");
assert.ok(
await supervisor.waitForOutputClose(1000),
"Fixture output did not close",
);
const snapshot = supervisor.snapshot();
const output = `${snapshot.stdout.text}\n${snapshot.stderr.text}`;
assert.equal(snapshot.exitCode, 0, output);
assert.doesNotMatch(output, /REPORT SCRIPT ERROR/u);
}, [
async () => {
const stopped = await supervisor.stop();
fixtureCleaned = stopped.status === "verified-cleanup";
assert.ok(
fixtureCleaned,
`Owned cleanup incomplete; retained ${workspace}: ${JSON.stringify(stopped)}`,
);
},
]);
const beforeProject = await fingerprintFiles(projectDirectory);
const projectTransport = new StdioClientTransport({
command: process.execPath,
args: [entrypoint, "mcp"],
env: {
...env,
REA_GHIDRA_PROJECT_PATH: join(projectDirectory, "Boundary.gpr"),
},
stderr: "pipe",
});
const projectClient = new Client({
name: "rea-cross-block-project-proof",
version: "1",
});
try {
await projectClient.connect(projectTransport);
const definition = (await projectClient.listTools()).tools.find(
(tool) => tool.name === "search_bytes",
);
assert.ok(definition?.outputSchema);
const projectCall = async (name, args) => {
const reply = await projectClient.callTool(
{ name, arguments: args },
{
timeout: 240000,
...(name === "search_bytes" ? { toolDefinition: definition } : {}),
},
);
if (name === "open_binary" || name === "close_binary")
return requireMcpLifecycleResult(reply, name);
const output = requireMcpOutput(reply, name);
const evidence = parseEvidence(output);
assert.equal(evidence.subject.digest.sha256, targetSha256);
return evidence.normalized_result;
};
await projectCall("open_binary", { path: target, provider_id: "ghidra" });
const pattern = "de ad ?? ?? be ef";
const adjacent = await projectCall("search_bytes", {
pattern,
start: "0x60000000",
end: "0x60000005",
});
assert.deepEqual(
adjacent.matches.map((match) => match.address),
["0x60000000"],
);
const gap = await projectCall("search_bytes", {
pattern,
start: "0x60000100",
end: "0x60000105",
});
assert.deepEqual(gap.matches, []);
assert.ok(
gap.searched.skipped_uninitialized.some(
(block) => block.start === "0x60000102" && block.end === "0x60000103",
),
);
const all = await projectCall("search_bytes", { pattern });
assert.ok(all.matches.some((match) => match.address === "0x60000000"));
assert.ok(
!all.matches.some((match) => match.address.startsWith("overlay_test:")),
);
assert.ok(
all.searched.skipped_not_loaded.some(
(block) => block.segment === "overlay_test",
),
);
const overlay = await projectCall("search_bytes", {
pattern,
start: "overlay_test:0x60000000",
end: "overlay_test:0x60000005",
});
assert.deepEqual(
overlay.matches.map((match) => match.address),
["overlay_test:0x60000000"],
);
await projectCall("close_binary", {});
} finally {
await projectClient.close();
await projectTransport.close();
}
assert.deepEqual(await fingerprintFiles(projectDirectory), beforeProject);
}
async function fingerprintFiles(directory) {
const result = [];
const visit = async (relative) => {
const entries = await readdir(join(directory, relative), {
withFileTypes: true,
});
entries.sort((a, b) => a.name.localeCompare(b.name));
for (const entry of entries) {
const name = join(relative, entry.name);
if (entry.isDirectory()) await visit(name);
else if (entry.isFile())
result.push([
name,
createHash("sha256")
.update(await readFile(join(directory, name)))
.digest("hex"),
]);
else throw new Error(`Unexpected project entry: ${name}`);
}
};
await visit("");
return result;
}
+99 -3
View File
@@ -28,7 +28,10 @@ import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { inspectKunaInstallation } from "../dist/kuna/KunaInstallation.js";
import { ArtifactResourceScope } from "../dist/artifacts/ArtifactResourceScope.js";
import { AnalysisCleanupLease } from "../dist/artifacts/AnalysisCleanupLease.js";
import { requireMcpOperationResult } from "./lib/mcp-verifier-results.mjs";
import {
requireMcpOperationResult,
requireMcpToolError,
} from "./lib/mcp-verifier-results.mjs";
import { verifyKunaReferences } from "./lib/kuna-reference-verifier.mjs";
import {
nativeKunaStrings,
@@ -172,7 +175,7 @@ const sampleTree = async (root) => {
};
/** One MCP server with a selected provider, sampled while it runs. */
const openServer = async (provider) => {
const openServer = async (provider, kunaSettings = {}) => {
const owner = Symbol(provider.id);
pendingServers.add(owner);
const transport = new StdioClientTransport({
@@ -185,6 +188,7 @@ const openServer = async (provider) => {
REA_PROCESS_RUN_ID: run.run_id,
REA_KUNA_COMMAND: kuna,
REA_KUNA_CACHE_DIR: cacheRoot,
...kunaSettings,
...(process.env.GHIDRA_INSTALL_DIR === undefined
? {}
: { GHIDRA_INSTALL_DIR: process.env.GHIDRA_INSTALL_DIR }),
@@ -229,6 +233,13 @@ const openServer = async (provider) => {
structured: result.structuredContent,
};
};
const callError = async (name, arguments_ = {}) => {
const result = await client.callTool(
{ name, arguments: arguments_ },
{ timeout: 3_600_000 },
);
return requireMcpToolError(result);
};
const close = async (priorFailure) => {
clearInterval(timer);
await sample();
@@ -270,7 +281,59 @@ const openServer = async (provider) => {
// A rejected connection/target still owns its transport and workspace.
await close(cause);
}
return { call, close };
return { call, callError, close };
};
/** Verify a forced inventory timeout can be retried without changing jobs. */
const verifyProjectTimeoutRecovery = async (path, address) => {
const before = createHash("sha256")
.update(await readFile(path))
.digest("hex");
const settings = {
REA_KUNA_JOBS: "2",
REA_KUNA_TIMEOUT_MS: "60000",
REA_KUNA_PROJECT_TIMEOUT_MS: "1",
};
const timed = await openServer({ path, id: "kuna" }, settings);
let timedFailure;
try {
const failure = await timed.callError("procedure_assembly", {
procedure: address,
});
assert.equal(failure.code, "capability_unavailable");
assert.match(failure.message, /kuna functions/u);
assert.match(failure.remediation.action, /REA_KUNA_PROJECT_TIMEOUT_MS/u);
assert.doesNotMatch(failure.remediation.action, /REA_KUNA_JOBS/u);
} catch (cause) {
timedFailure = cause;
} finally {
await timed.close(timedFailure);
}
const retry = await openServer(
{ path, id: "kuna" },
{ ...settings, REA_KUNA_PROJECT_TIMEOUT_MS: "60000" },
);
let retryFailure;
try {
const assembly = await retry.call("procedure_assembly", {
procedure: address,
});
assert.equal(typeof assembly.value, "string");
assert.ok(assembly.value.length > 0);
assert.equal(assembly.structured.analysis_profile.parameters.jobs, 2);
} catch (cause) {
retryFailure = cause;
} finally {
await retry.close(retryFailure);
}
assert.equal(
createHash("sha256")
.update(await readFile(path))
.digest("hex"),
before,
"Kuna timeout recovery changed the target",
);
return { forced_deadline_ms: 1, retry_deadline_ms: 60000, jobs: 2 };
};
/** Count structuring and diagnostic markers in provider pseudocode. */
@@ -448,6 +511,30 @@ const verifyKuna = async (
references,
});
}
if (functions.length > 0) {
const trace = await cold.call("trace_native_values", {
procedure: functions[0][1],
max_depth: 0,
max_functions: 1,
});
const bundle = await cold.call("get_evidence_bundle", {
detail: "complete",
});
for (const source of [
...trace.value.procedures,
...trace.value.nodes,
...trace.value.edges,
]) {
assert.ok(trace.structured.evidence_links.includes(source.evidence_id));
assert.equal(
bundle.value.records.find(
(record) => record.evidence_id === source.evidence_id,
)?.operation,
"analyze_function",
`Missing source Evidence ${source.evidence_id}`,
);
}
}
} catch (cause) {
coldFailure = cause;
} finally {
@@ -535,6 +622,7 @@ const verifyVariant = async ({ flavor, directory, symbols }, variant) => {
};
const results = [];
let timeoutRecovery;
const failures = [];
let inspectionResourcesClean = true;
try {
@@ -596,6 +684,11 @@ try {
}),
});
timeoutRecovery = await verifyProjectTimeoutRecovery(
built,
fixtureFunctions.get("rea_checksum"),
);
// Caller-built matrix.
for (const pair of callerPairs)
for (const variant of [pair.file, `${pair.file}.stripped`])
@@ -650,6 +743,9 @@ const report = {
platform: `${process.platform}/${process.arch}`,
verifier_run: lifecycle,
results,
...(timeoutRecovery === undefined
? {}
: { timeout_recovery: timeoutRecovery }),
limitations: [
"Targets are analyzed statically; they are never executed.",
"Memory is the sampled peak resident set of the MCP server process tree; CPU is its sampled cumulative time. A short-lived peak between 200 ms samples is not observed.",
+2 -1
View File
@@ -327,8 +327,9 @@ def main():
refresh_contributors(root)
elif args.command == "check":
sync_pages(root, check=True)
for name in ("prepare-website.py", "verify-website.py", "test-website.py"):
for name in ("prepare-website.py", "verify-website.py", "test-website.py", "test-website-agents.py"):
run_script(root, name)
subprocess.run(["node", "--test", "website/worker.test.mjs"], cwd=root, check=True)
elif args.command == "links":
run_script(root, "audit-website-links.py", ["--report", args.report] if args.report else [])
else:
+116
View File
@@ -0,0 +1,116 @@
"""Build public agent discovery and Markdown from their maintained sources."""
import hashlib
import json
from urllib.parse import urljoin
from zipfile import ZIP_DEFLATED, ZipFile, ZipInfo
from bs4 import BeautifulSoup
from markdownify import MarkdownConverter
import yaml
from website import NOTES_FIXTURES, PageMetadata, SITE_ORIGIN, page_url
def write_json(path, value):
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
def prepare_discovery(root, site):
"""Publish the complete skill, including its relative reference documents."""
source = root / ".agents/skills/reverse-engineer-anything"
skill = (source / "SKILL.md").read_text(encoding="utf-8")
if not skill.startswith("---\n") or "\n---\n" not in skill[4:]:
raise ValueError("Public REA skill needs YAML frontmatter")
metadata = yaml.safe_load(skill[4:].split("\n---\n", 1)[0])
if not isinstance(metadata, dict):
raise ValueError("Public REA skill needs YAML metadata")
name, description = metadata["name"], metadata["description"]
if name != source.name or not isinstance(description, str) or not description.strip():
raise ValueError("Invalid public REA skill metadata")
discovery = site / ".well-known/agent-skills"
discovery.mkdir(parents=True, exist_ok=True)
artifact = discovery / f"{name}.zip"
with ZipFile(artifact, "w") as archive:
for path in [source / "SKILL.md", *sorted(source.glob("references/**/*.md"))]:
relative = path.relative_to(source)
entry = ZipInfo(f"{name}/{relative.as_posix()}", (1980, 1, 1, 0, 0, 0))
entry.create_system = 3
entry.external_attr = 0o100644 << 16
archive.writestr(entry, path.read_bytes(), compress_type=ZIP_DEFLATED)
artifact_url = f"{SITE_ORIGIN}/.well-known/agent-skills/{artifact.name}"
write_json(discovery / "index.json", {
"$schema": "https://schemas.agentskills.io/discovery/0.2.0/schema.json",
"skills": [{
"name": name, "type": "archive", "description": description,
"url": artifact_url,
"digest": "sha256:" + hashlib.sha256(artifact.read_bytes()).hexdigest(),
}],
})
entry = {
"identifier": f"urn:air:rea.tools:skill:{name}",
"displayName": "REA reverse engineering skill",
"description": description,
"type": "application/zip",
"url": artifact_url,
}
write_json(site / ".well-known/ai-catalog.json", {
"specVersion": "1.0",
"host": {"displayName": "REA", "documentationUrl": f"{SITE_ORIGIN}/get-started/"},
"entries": [entry],
})
write_json(site / ".well-known/ard.json", {"entries": [{
**entry,
"representativeQueries": [
"Explain how a feature works in a shipped executable using REA.",
"Trace an Electron application's behavior back to its packaged JavaScript.",
"Build an AgentEnv bundle from a web application capture.",
],
}]})
print("Prepared agent skill archive, discovery index, AI Catalog and ARD manifest.")
def prepare_markdown(site):
"""Convert rendered reading pages, keeping code and resolving resource URLs."""
converter = MarkdownConverter(heading_style="ATX", bullets="-", escape_underscores=False)
count = 0
for path in sorted(site.rglob("*.html")):
if path.relative_to(site).as_posix() in NOTES_FIXTURES:
continue
source = path.read_text(encoding="utf-8")
metadata = PageMetadata()
metadata.feed(source)
# Drafts and English fallbacks keep their HTML indexing policy intact.
if metadata.noindex:
continue
soup = BeautifulSoup(source, "html.parser")
main = soup.find("main")
if main is None:
continue
# Preserve fallback notices and closed disclosures; remove interface chrome.
for node in main.select('script, style, nav, button, svg, .home-page-nav, [aria-hidden="true"]'):
if node.parent is not None:
node.decompose()
# These spans are separate lines in the shared flow component's CSS.
for node in main.select("span.flow-label, span.flow-node, span.flow-note"):
node.name = "div"
canonical = page_url(site, path)
for node in main.select("[href], [src]"):
for attribute in ("href", "src"):
if node.has_attr(attribute):
node[attribute] = urljoin(canonical, node[attribute])
title = metadata.titles[0] if metadata.titles else "REA"
header = yaml.safe_dump({"title": title, "url": canonical}, allow_unicode=True, sort_keys=False)
markdown = converter.convert_soup(main).strip()
path.with_suffix(".md").write_text(f"---\n{header}---\n\n{markdown}\n", encoding="utf-8")
alternate = page_url(site, path.with_suffix(".md"))
links = (
f'<link rel="alternate" type="text/markdown" href="{alternate}" />\n'
f'<link rel="ard" type="application/json" href="{SITE_ORIGIN}/.well-known/ard.json" />\n'
f'<link rel="ai-catalog" type="application/ai-catalog+json" href="{SITE_ORIGIN}/.well-known/ai-catalog.json" />\n'
f'<link rel="related" type="application/json" title="Agent skills index" href="{SITE_ORIGIN}/.well-known/agent-skills/index.json" />\n'
)
path.write_text(source.replace("</head>", links + "</head>", 1), encoding="utf-8")
count += 1
print(f"Prepared Markdown for {count} reading pages.")