mirror of
https://github.com/morluto/rea.git
synced 2026-11-13 17:47:56 -10:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e169583f6 | ||
|
|
497fd2acd9 | ||
|
|
73b207ac64 | ||
|
|
5b7f5d2298 | ||
|
|
940d7156f6 | ||
|
|
06d2413c41 |
No files matched your search
@@ -357,11 +357,17 @@ The process restarts when the environment changes, after 32 commands, or when
|
||||
the worker reports a resident set of at least 1 GiB. A fatal worker failure
|
||||
or a broken session frame retires that process and waits for it to exit before
|
||||
the next command starts.
|
||||
`cli.run` still starts a fresh process, including stdin and explicit deadlines.
|
||||
`runFileCli` uses the file session for commands that have neither. Go binary,
|
||||
IL2CPP metadata, and dylib-resolution commands use that session. File teardown
|
||||
stops the shared CLI process, including startup still in flight, and does not
|
||||
open another after close.
|
||||
In a `cliTest` file, `cli.run` uses that shared process when it reports the
|
||||
command exactly as a fresh `rea` process would. Both run the same one-shot
|
||||
owner (`src/cliOneShot.ts`), and the shared process presents the launcher's
|
||||
entrypoint. A command starts a fresh process instead when it supplies stdin or
|
||||
an explicit deadline, enters MCP or the MCP doctor (which the launcher routes
|
||||
first), sets `NODE_OPTIONS`, or enables logging, which writes outside the
|
||||
per-command capture. Pass `freshProcess: true` for process-level facts such as
|
||||
startup, signals, or exit, and keep one fresh check beside reused cases where
|
||||
dispatch itself is under test. `createTestCli` drivers always start a fresh
|
||||
process. File teardown stops the shared CLI process, including startup still in
|
||||
flight, and does not open another after close.
|
||||
`exportedFunctionBindingsCli.test.ts` uses the shared compiled CLI session for
|
||||
its file and compares its JSON command results with the MCP session. It selects
|
||||
20-second startup and command deadlines; other callers retain their existing
|
||||
@@ -1091,11 +1097,32 @@ existing four-minute timing collection plus the bounded diagnostic stages.
|
||||
|
||||
Fast built shards target under ten minutes of uncontended shard-job execution.
|
||||
That figure is the `test-shard` job. The job waits for classification and the
|
||||
build, and the portable-test merge runs after the slowest shard. Runner queue is
|
||||
separate and grows when more shard jobs compete for runners. The npm download
|
||||
cache stays the setup cache rather than a shared `node_modules` artifact.
|
||||
build, and the portable-test merge runs after the slowest shard. On 2026-10-20,
|
||||
run `39451548945` measured those surrounding jobs with runner queue excluded:
|
||||
classification 15 seconds, build 49 seconds, and the portable-test merge 29
|
||||
seconds. Shard 1/5 of that run spent 22 seconds before built Vitest and 3
|
||||
seconds after it, including `npm ci` at 9 seconds. `node_modules` is about 674
|
||||
MiB, so publishing it as a shared artifact would transfer more data than that
|
||||
install saves. The npm download cache stays the setup cache.
|
||||
|
||||
File timings from that run put the built suite at 7944 seconds of module time.
|
||||
The sequencer keeps each file's full duration on one worker. Extra workers
|
||||
overlap other files in the same sequence group, and `process-boundary` runs
|
||||
after that group. Scheduling the unsplit inventory onto five shards peaks at
|
||||
820 seconds of tests. That run's slowest built shard job lasted 925 seconds
|
||||
(shard 4/5), and shard 1/5 spent 874 seconds in built Vitest, so hosted job
|
||||
time sits above the file schedule. Six shards of the same inventory peak at
|
||||
684 seconds of tests. Eight shards, after the 578-second loader file is split
|
||||
by case count (54 cases stay in that file and 46 move into each of three
|
||||
files), peak at 513 seconds of tests. With the measured 25-second shard setup,
|
||||
that shard job is about 538 seconds. Adding the measured classification, build,
|
||||
and merge makes an uncontended portable critical path of about 631 seconds,
|
||||
still before the gap between this file schedule and hosted Vitest. Runner queue
|
||||
is separate and grows when more shard jobs compete for runners.
|
||||
`process-boundary` stays serial because process-tree sampling shares the host.
|
||||
Hosted CI jobs use four workers; other hosts stay at two
|
||||
Raising the worker cap from two to four on this four-core host cut a six-file
|
||||
acceptance sample from 96–101 seconds to 71 seconds and did not remove the
|
||||
single-file floor. Hosted CI jobs use four workers; other hosts stay at two
|
||||
because they are shared with other checks. Full qualification keeps six covered
|
||||
shards; its longer instrumentation is outside this fast budget.
|
||||
|
||||
|
||||
@@ -96,10 +96,6 @@ Persistent command Evidence retains the caller-selected and resolved startup pat
|
||||
|
||||
Session status retains up to 64 recent protocol frames. Each history entry keeps a complete-character UTF-8 prefix of at most 16 KiB, plus a truncation marker when needed; `recent_output_truncated` reports omitted history. This history budget also accounts for replacement characters from invalid UTF-8. It does not shorten command Evidence, whose output budget defaults to 16 MiB. Rizin debug startup waits 30000 ms with no runtime override. Each command frame defaults to 120000 ms; its deadline, command-output budget, session capacity and pending-command queue use the [shared debugger launch settings](#debugger-launch-settings). The 16777216-byte frame cap and 4096-frame command cap remain fixed. Evidence names the cap that stopped the command. The one-shot `command_timeout_ms` setting does not change a debug session.
|
||||
|
||||
### Stopped debugger sessions
|
||||
|
||||
A Rizin or GDB session stops accepting commands when REA stops it, for example after an output or frame limit, a command deadline, cancellation or a protocol failure, or when the debugger process exits. REA keeps the stopped session and the first reason it observed until `close_rizin_debug_session` or `close_gdb_session` releases it, including after a retried cleanup succeeds. `rizin_debug_session_status` and `gdb_session_status` then report `state: "closed"` with that reason in `closed_reason`. A later `rizin_debug_command` or `gdb_console` on the session fails with `capability_unavailable`; `details.reason` carries the same reason, and the remediation is to close the session and start a new one. A `session_id` that this server never returned, or that close already released, fails with `invalid_request` instead.
|
||||
|
||||
### Debugger launch settings
|
||||
|
||||
GDB and Rizin share these settings in the environment that launches REA:
|
||||
@@ -140,8 +136,6 @@ rea debug-with-rizin --inferior-tty /dev/pts/7 ./program dr
|
||||
rea execute-rizin-command ./program iI
|
||||
```
|
||||
|
||||
Rizin exits with status 0 even when its shell refuses a statement, so the exit code does not show whether the command ran. Rizin 0.6 through 0.9.1 log one of four stderr lines for such a refusal: `ERROR: Command '…' does not exist.`, ``ERROR: Wrong number of arguments passed to `…`…``, `ERROR: core: Error while executing command: …` (an invalid statement), or `ERROR: core: Error while parsing command: …`. Rizin then skips the refused statement and every later one in the command. When the completed process's stderr contains one of these lines, REA reports `completion_status: "rejected"` and adds a limitation naming the refusal. stdout then holds only the output of earlier statements, so empty stdout is not an empty result. Other `ERROR:` lines, such as `ERROR: Cannot find function at …`, come from commands that ran; REA keeps them in stderr and reports `complete`. A command that lowers `log.level` or redirects stderr hides refusals from REA.
|
||||
|
||||
Captured stdout and stderr use the same `max_output_bytes` (CLI: `--max-output-bytes`) admission and recovery described above, default 16 MiB. One-shot commands independently use `command_timeout_ms`. Raising output admission cannot recover an earlier capture; command effects and cleanup must be checked before another invocation.
|
||||
|
||||
### Decoder and map inputs
|
||||
|
||||
@@ -842,8 +842,14 @@ branches are not producers. A tier with no qualifying artifact leaves history
|
||||
unset, and Vitest uses its own assignment. A later schedule that reuses
|
||||
qualification without uploading timings leaves the previous artifact in place.
|
||||
|
||||
Each tier keeps its own history because stale weights from another tier, or
|
||||
from before a large test change, unbalance the shards.
|
||||
On 2026-10-21 the newest full history was run
|
||||
`39545047294`, recorded before compiled-CLI session reuse. Fast run
|
||||
`39552964873` packed its eight built shards from that history, and those shard
|
||||
jobs executed for 319–591 seconds. Replaying that run's file durations through
|
||||
the sequencer's two-worker schedule balances every shard at about 404 seconds
|
||||
of file time. The same replay with the stale history weights leaves the slowest
|
||||
shard near 547 seconds and the fastest near 293. That spread is file-schedule
|
||||
time, not runner queue, which is why each tier keeps its own history.
|
||||
CI assigns discovered files by historical duration rather than dividing only
|
||||
by file count. A file with no recorded duration, such as a newly added or
|
||||
renamed test, is weighted at the median recorded duration until a published
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"entry": [
|
||||
"src/main.ts",
|
||||
"src/cli.ts",
|
||||
"src/cliOneShot.ts",
|
||||
"src/cliOutput.ts",
|
||||
"src/cliBuiltinArguments.ts",
|
||||
"src/cli/cliResultOutput.ts",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"entry": [
|
||||
"src/main.ts!",
|
||||
"src/cli.ts!",
|
||||
"src/cliOneShot.ts!",
|
||||
"src/cliOutput.ts!",
|
||||
"src/cliBuiltinArguments.ts!",
|
||||
"src/cli/cliResultOutput.ts!",
|
||||
|
||||
@@ -24,7 +24,7 @@ export const bundleRuntime = async (root) => {
|
||||
"cli-runtime",
|
||||
{
|
||||
cli: "cli.js",
|
||||
cliOutput: "cliOutput.js",
|
||||
cliOneShot: "cliOneShot.js",
|
||||
cliBuiltinArguments: "cliBuiltinArguments.js",
|
||||
cliResultOutput: "cli/cliResultOutput.js",
|
||||
mcpDoctor: "mcpDoctor.js",
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ if (await pathExists(join(root, "src"))) {
|
||||
"dist/main.js",
|
||||
"dist/mcp/main.js",
|
||||
"dist/cli-runtime/cli.js",
|
||||
"dist/cli-runtime/cliOutput.js",
|
||||
"dist/cli-runtime/cliOneShot.js",
|
||||
"dist/cli-runtime/cliBuiltinArguments.js",
|
||||
"dist/cli-runtime/cliResultOutput.js",
|
||||
"dist/cli-runtime/mcpDoctor.js",
|
||||
|
||||
+54
-142
@@ -64,7 +64,7 @@ const runtimeFiles = [
|
||||
? ["dist/mcp/main.js", "dist/cli-runtime/mcpDoctor.js"]
|
||||
: [
|
||||
"dist/cli-runtime/cli.js",
|
||||
"dist/cli-runtime/cliOutput.js",
|
||||
"dist/cli-runtime/cliOneShot.js",
|
||||
"dist/cli-runtime/cliResultOutput.js",
|
||||
]),
|
||||
];
|
||||
@@ -104,154 +104,66 @@ if (!(await compiledRuntimeExists(runtimeFiles))) {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const {
|
||||
cliConfigurationError,
|
||||
renderCliOutputArgumentError,
|
||||
renderEmptyFilteredCliOutput,
|
||||
renderVersionReport,
|
||||
parseCliOutputArguments,
|
||||
} = await import("../dist/cli-runtime/cliOutput.js");
|
||||
const { runOneShotCli } = await import("../dist/cli-runtime/cliOneShot.js");
|
||||
guardClosedStdout();
|
||||
const cliArgs = isShellCompletion
|
||||
? args
|
||||
: builtinArguments.normalizeCliArguments(args);
|
||||
// Completion words are transport data, including partial output flags.
|
||||
const outputOptions = parseCliOutputArguments(
|
||||
isShellCompletion ? [] : cliArgs,
|
||||
);
|
||||
const outputArguments = outputOptions.validation;
|
||||
// `--version` with an explicit format reports the runtime as a document;
|
||||
// plain `--version` stays Incur's bare version string.
|
||||
const versionOutput = renderVersionReport(
|
||||
outputOptions,
|
||||
{
|
||||
packageName: packageJson.name,
|
||||
packageVersion: packageJson.version,
|
||||
versions: process.versions,
|
||||
platform: process.platform,
|
||||
architecture: process.arch,
|
||||
},
|
||||
process.stdout.isTTY === true,
|
||||
);
|
||||
const configurationError = cliConfigurationError(process.env);
|
||||
let dispatchFailure;
|
||||
let closeCliResources;
|
||||
// Incur can exit from the callback below before this function returns.
|
||||
// That callback owns the status, including a closed-pipe exit of 141.
|
||||
let exiting = false;
|
||||
try {
|
||||
// Doctor is the readiness report for this failure. Other commands still stop
|
||||
// here so they do not run with an unusable log level.
|
||||
if (
|
||||
configurationError !== undefined &&
|
||||
(isShellCompletion ||
|
||||
builtinArguments.splitIncurBuiltinArguments(cliArgs).command[0] !==
|
||||
"doctor")
|
||||
) {
|
||||
process.stderr.write(`${configurationError}\n`);
|
||||
process.exitCode = 1;
|
||||
} else if (!outputArguments.ok) {
|
||||
writeStdout(renderCliOutputArgumentError(outputArguments));
|
||||
process.exitCode = 1;
|
||||
} else if (versionOutput !== undefined) {
|
||||
writeStdout(versionOutput);
|
||||
} else if (
|
||||
!isShellCompletion &&
|
||||
builtinArguments.isProjectScopedAmpRegistration(cliArgs)
|
||||
) {
|
||||
// Amp has global settings only. Share the parsed scope fact with the
|
||||
// registration workflow and refuse before loading the command runtime.
|
||||
//
|
||||
// A caller that asked for a structured document gets the refusal in
|
||||
// that document, through the same pre-execution error renderer the
|
||||
// output-argument branch uses; anything else keeps the plain message.
|
||||
const refusalMessage =
|
||||
"Amp does not support project-scoped registration. Run without --no-global to register Amp globally, or select a client with project settings. No client settings were changed.";
|
||||
const refusalFormat = outputOptions.format;
|
||||
if (
|
||||
refusalFormat === "json" ||
|
||||
refusalFormat === "jsonl" ||
|
||||
refusalFormat === "yaml"
|
||||
) {
|
||||
writeStdout(
|
||||
renderCliOutputArgumentError({
|
||||
ok: false,
|
||||
format: refusalFormat,
|
||||
code: "AMP_PROJECT_SCOPE_UNSUPPORTED",
|
||||
message: refusalMessage,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
process.stderr.write(`${refusalMessage}\n`);
|
||||
}
|
||||
process.exitCode = 1;
|
||||
} else {
|
||||
const cliRuntime = await import("../dist/cli-runtime/cli.js");
|
||||
closeCliResources = cliRuntime.closeCliResources;
|
||||
const { createCli } = cliRuntime;
|
||||
const { createCliResultOutput } =
|
||||
await import("../dist/cli-runtime/cliResultOutput.js");
|
||||
let wroteOutput = false;
|
||||
const resultOutput = createCliResultOutput(outputOptions, process.stdout);
|
||||
try {
|
||||
await createCli(process.env, resultOutput).serve(cliArgs, {
|
||||
stdout: (output) => {
|
||||
if (resultOutput?.handled) {
|
||||
if (resultOutput.failed && !stdoutClosed)
|
||||
process.stderr.write(output);
|
||||
return;
|
||||
}
|
||||
if (output.length > 0) wroteOutput = true;
|
||||
if (resultOutput?.captureSdkOutput(output)) return;
|
||||
writeStdout(output);
|
||||
},
|
||||
// A command failure caused by the closed pipe keeps the pipe's status.
|
||||
// Worker shutdown is shared with the dispatch finally below. A failed
|
||||
// shutdown is reported here because this callback is what exits.
|
||||
exit: (code) => {
|
||||
exiting = true;
|
||||
void (async () => {
|
||||
let status = typeof code === "number" ? code : 0;
|
||||
try {
|
||||
await closeCliResources();
|
||||
} catch (cleanup) {
|
||||
process.stderr.write(
|
||||
`Failed to close CLI resources: ${cleanupFailureText(cleanup)}\n`,
|
||||
);
|
||||
if (status === 0) status = 1;
|
||||
}
|
||||
try {
|
||||
await resultOutput?.finishSdkOutput(status);
|
||||
} catch (cause) {
|
||||
if (!stdoutClosed)
|
||||
process.stderr.write(`${cleanupFailureText(cause)}\n`);
|
||||
if (status === 0) status = 1;
|
||||
}
|
||||
process.exit(stdoutClosed ? SIGPIPE_EXIT_STATUS : status);
|
||||
})();
|
||||
},
|
||||
});
|
||||
} catch (cause) {
|
||||
if (wroteOutput) throw cause;
|
||||
const recovered = await resultOutput
|
||||
?.recoverFormattingFailure(cause)
|
||||
.catch((writeFailure) => {
|
||||
// Recovery writes outside Incur's command boundary. A reader that
|
||||
// declined this document keeps the same quiet SIGPIPE outcome.
|
||||
if (!stdoutClosed) throw writeFailure;
|
||||
return true;
|
||||
});
|
||||
if (!recovered) throw cause;
|
||||
process.exitCode = stdoutClosed ? SIGPIPE_EXIT_STATUS : 1;
|
||||
}
|
||||
if (!wroteOutput && !resultOutput?.handled)
|
||||
writeStdout(renderEmptyFilteredCliOutput(outputOptions) ?? "");
|
||||
// An SDK failure exits through the callback, which owns its pending write.
|
||||
if (!exiting)
|
||||
await resultOutput?.finishSdkOutput(
|
||||
typeof process.exitCode === "number" ? process.exitCode : 0,
|
||||
);
|
||||
}
|
||||
await runOneShotCli(args, {
|
||||
environment: process.env,
|
||||
runtime: {
|
||||
packageName: packageJson.name,
|
||||
packageVersion: packageJson.version,
|
||||
versions: process.versions,
|
||||
platform: process.platform,
|
||||
architecture: process.arch,
|
||||
},
|
||||
stdoutIsTerminal: process.stdout.isTTY === true,
|
||||
resultDestination: process.stdout,
|
||||
closedStdoutStatus: SIGPIPE_EXIT_STATUS,
|
||||
writeStdout,
|
||||
writeStderr: (text) => {
|
||||
process.stderr.write(text);
|
||||
},
|
||||
stdoutClosed: () => stdoutClosed,
|
||||
exitCode: () =>
|
||||
typeof process.exitCode === "number" ? process.exitCode : 0,
|
||||
setExitCode: (code) => {
|
||||
process.exitCode = code;
|
||||
},
|
||||
ownCliResources: (close) => {
|
||||
closeCliResources = close;
|
||||
},
|
||||
exitRequested: () => exiting,
|
||||
// A command failure caused by the closed pipe keeps the pipe's status.
|
||||
// Worker shutdown is shared with the dispatch finally below. A failed
|
||||
// shutdown is reported here because this callback is what exits.
|
||||
exit: (code, finishOutput) => {
|
||||
exiting = true;
|
||||
void (async () => {
|
||||
let status = code;
|
||||
try {
|
||||
await closeCliResources();
|
||||
} catch (cleanup) {
|
||||
process.stderr.write(
|
||||
`Failed to close CLI resources: ${cleanupFailureText(cleanup)}\n`,
|
||||
);
|
||||
if (status === 0) status = 1;
|
||||
}
|
||||
try {
|
||||
await finishOutput(status);
|
||||
} catch (cause) {
|
||||
if (!stdoutClosed)
|
||||
process.stderr.write(`${cleanupFailureText(cause)}\n`);
|
||||
if (status === 0) status = 1;
|
||||
}
|
||||
process.exit(stdoutClosed ? SIGPIPE_EXIT_STATUS : status);
|
||||
})();
|
||||
},
|
||||
});
|
||||
} catch (cause) {
|
||||
if (cause?.code === "EPIPE") {
|
||||
stdoutClosed = true;
|
||||
|
||||
@@ -11,11 +11,6 @@ export interface ReverseEngineeringProviderCommand {
|
||||
readonly identity: EvidenceProvider;
|
||||
/** What the provider cannot attest about the arguments it built. */
|
||||
readonly limitations?: readonly string[];
|
||||
/**
|
||||
* Explain a provider refusal of the command that a completed process
|
||||
* reported only in its diagnostics, or return undefined.
|
||||
*/
|
||||
readonly rejection?: (stderr: string) => string | undefined;
|
||||
readonly environmentOverrides?: Readonly<NodeJS.ProcessEnv>;
|
||||
}
|
||||
|
||||
|
||||
@@ -599,7 +599,7 @@ export class ReverseEngineeringService {
|
||||
let stderrInvalidUtf8 = false;
|
||||
let cleanupIncomplete = false;
|
||||
let cleanupOnlyFailure = false;
|
||||
let completionStatus: "complete" | "rejected" | "unknown" = "complete";
|
||||
let completionStatus: "complete" | "unknown" = "complete";
|
||||
let outputLimitReached = false;
|
||||
let failure: AnalysisError | undefined;
|
||||
let cleanupOwner: ProviderProcessSupervisor | undefined;
|
||||
@@ -1076,15 +1076,6 @@ export class ReverseEngineeringService {
|
||||
: digestAfter !== digest;
|
||||
const outputBudgetSource =
|
||||
parameters.max_output_bytes === undefined ? "default" : "request";
|
||||
// Only a completed process's diagnostics can establish a refusal; partial
|
||||
// or failed captures keep their unknown completion.
|
||||
const rejection =
|
||||
completionStatus === "complete" &&
|
||||
(failure === undefined ||
|
||||
(failure instanceof AnalysisOutputError && failure.cleanupOnly))
|
||||
? profile.rejection?.(stderr)
|
||||
: undefined;
|
||||
if (rejection !== undefined) completionStatus = "rejected";
|
||||
const result = {
|
||||
stdout,
|
||||
stderr,
|
||||
@@ -1125,7 +1116,6 @@ export class ReverseEngineeringService {
|
||||
]
|
||||
: []),
|
||||
...(profile.limitations ?? []),
|
||||
...(rejection === undefined ? [] : [rejection]),
|
||||
...(stdoutInvalidUtf8 || stderrInvalidUtf8
|
||||
? [
|
||||
`Captured ${[
|
||||
|
||||
@@ -22,7 +22,7 @@ import { parseAnalysisInput } from "../application/AnalysisInput.js";
|
||||
import { androidJniSelectionSchema } from "../domain/android/androidJniBindings.js";
|
||||
import { androidJniDeliverySchema } from "../domain/android/androidJniQuery.js";
|
||||
import { androidInputSchemas } from "../domain/android/androidAnalysis.js";
|
||||
import { cliDecimalField } from "./options.js";
|
||||
import { cliIntegerArgument } from "./options.js";
|
||||
|
||||
/** Expose the same explicit APK requests through one-shot CLI commands. */
|
||||
export const registerAndroidCommands = (
|
||||
@@ -223,11 +223,11 @@ export const registerAndroidCommands = (
|
||||
),
|
||||
}),
|
||||
options: z.object({
|
||||
overloadIndex: cliDecimalField(
|
||||
androidInputSchemas.inspect_android_method.shape.overload_index.describe(
|
||||
overloadIndex: cliIntegerArgument(0)
|
||||
.optional()
|
||||
.describe(
|
||||
"Explicit zero-based index among same-name methods; required when ambiguous",
|
||||
),
|
||||
),
|
||||
mappingPath,
|
||||
...analysisEvidenceDeliveryOptions,
|
||||
}),
|
||||
@@ -255,11 +255,11 @@ export const registerAndroidCommands = (
|
||||
options: z.object({
|
||||
methodName:
|
||||
androidInputSchemas.trace_android_references.shape.method_name,
|
||||
overloadIndex: cliDecimalField(
|
||||
androidInputSchemas.trace_android_references.shape.overload_index.describe(
|
||||
overloadIndex: cliIntegerArgument(0)
|
||||
.optional()
|
||||
.describe(
|
||||
"Index among defined methods or observed external prototypes listed by an ambiguous query",
|
||||
),
|
||||
),
|
||||
}),
|
||||
run: ({ args, options }) =>
|
||||
execute(
|
||||
|
||||
@@ -8,8 +8,7 @@ import { logCliCommand } from "../cliLogging.js";
|
||||
import { finishAnalysisCommand } from "./finishAnalysisCommand.js";
|
||||
import { withCommandCancellation } from "./commandCancellation.js";
|
||||
import type { CliInstance } from "./types.js";
|
||||
import { cliDecimalField } from "./options.js";
|
||||
import { cutterCommandInputSchema } from "../contracts/cutterToolContracts.js";
|
||||
import { cliIntegerArgument } from "./options.js";
|
||||
|
||||
/** Register commands that control an already-running Cutter instance. */
|
||||
export const registerCutterCommands = (
|
||||
@@ -44,10 +43,8 @@ export const registerCutterCommands = (
|
||||
.string()
|
||||
.uuid()
|
||||
.describe("Session ID copied from list-cutter-sessions."),
|
||||
expectedGeneration: cliDecimalField(
|
||||
cutterCommandInputSchema.shape.expected_generation.describe(
|
||||
"Session generation returned by list-cutter-sessions.",
|
||||
),
|
||||
expectedGeneration: cliIntegerArgument(0).describe(
|
||||
"Session generation returned by list-cutter-sessions.",
|
||||
),
|
||||
command: z
|
||||
.string()
|
||||
|
||||
@@ -38,7 +38,7 @@ import { inspectWebNetworkCaptureInputSchema } from "../domain/webNetworkCapture
|
||||
import { withCommandCancellation } from "./commandCancellation.js";
|
||||
import { finishAnalysisCommand } from "./finishAnalysisCommand.js";
|
||||
import type { CliInstance } from "./types.js";
|
||||
import { cliDecimalField, publishArgvInput } from "./options.js";
|
||||
import { cliDecimalField } from "./options.js";
|
||||
|
||||
/** Same envelope label as the other JSON-input commands and `parseCliJsonInput`. */
|
||||
const cliError = (error: AnalysisError): JsonValue => ({
|
||||
@@ -689,13 +689,11 @@ export const registerEnvironmentCommands = (
|
||||
host: mcpHttpHostSchema
|
||||
.optional()
|
||||
.describe("Listener address; default MCP_HOST, else 127.0.0.1"),
|
||||
port: publishArgvInput(
|
||||
mcpHttpPortSchema
|
||||
.optional()
|
||||
.describe(
|
||||
"Listener port; default MCP_PORT, else 0 for an ephemeral port",
|
||||
),
|
||||
),
|
||||
port: mcpHttpPortSchema
|
||||
.optional()
|
||||
.describe(
|
||||
"Listener port; default MCP_PORT, else 0 for an ephemeral port",
|
||||
),
|
||||
}),
|
||||
run: ({ args, options }) =>
|
||||
withCommandCancellation((signal) =>
|
||||
|
||||
+11
-6
@@ -24,6 +24,13 @@ type DecimalWrapper =
|
||||
readonly kind: "optional" | "exactOptional" | "nullable" | "readonly";
|
||||
};
|
||||
|
||||
/** Parse decimal integer tokens before Number coercion can erase their syntax. */
|
||||
export const cliIntegerArgument = (min: number) =>
|
||||
z
|
||||
.string()
|
||||
.regex(DECIMAL_INTEGER_TOKEN, "Expected a decimal integer")
|
||||
.pipe(z.coerce.number<string>().safe().min(min));
|
||||
|
||||
const decimalToken = (domain: z.ZodNumber) =>
|
||||
z
|
||||
.string()
|
||||
@@ -35,12 +42,10 @@ const unwrapSchema = (field: { unwrap: () => unknown }): z.ZodType =>
|
||||
field.unwrap() as z.ZodType;
|
||||
|
||||
/**
|
||||
* Incur advertises Zod's output view. Publish the input view so an argv token
|
||||
* grammar that transforms text survives `--schema` and other SDK conversions.
|
||||
* Incur advertises Zod's output view. Publish the input view so the decimal
|
||||
* token guard survives `--schema` and other SDK conversions.
|
||||
*/
|
||||
export const publishArgvInput = <Field extends z.ZodType>(
|
||||
field: Field,
|
||||
): Field => {
|
||||
const publishDecimalInput = (field: z.ZodType): z.ZodType => {
|
||||
const { $schema: _dialect, ...inputSchema } = z.toJSONSchema(field, {
|
||||
io: "input",
|
||||
});
|
||||
@@ -144,7 +149,7 @@ export const cliDecimalField = <Field extends z.ZodType>(
|
||||
numeric.description === undefined
|
||||
? admitted
|
||||
: admitted.describe(numeric.description);
|
||||
return publishArgvInput(described) as z.ZodType<z.output<Field>>;
|
||||
return publishDecimalInput(described) as z.ZodType<z.output<Field>>;
|
||||
};
|
||||
|
||||
/** Adapt each numeric property of a shared shape; other properties stay unchanged. */
|
||||
|
||||
@@ -80,7 +80,7 @@ const cleanupOnlyCompleteEvidence = (
|
||||
return typeof normalized === "object" &&
|
||||
normalized !== null &&
|
||||
!Array.isArray(normalized) &&
|
||||
normalized.completion_status !== "unknown"
|
||||
normalized.completion_status === "complete"
|
||||
? parsed.data
|
||||
: undefined;
|
||||
};
|
||||
|
||||
+14
-28
@@ -1,6 +1,6 @@
|
||||
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
|
||||
import { z } from "incur";
|
||||
import { cliDecimalField } from "./options.js";
|
||||
import { cliDecimalField, cliIntegerArgument } from "./options.js";
|
||||
|
||||
import type { DirectAnalysis } from "../composition/directAnalysis.js";
|
||||
import { createHistoricalSourceSummary } from "../domain/referenceSourceSummary.js";
|
||||
@@ -21,10 +21,7 @@ import {
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import { projectInputIssues } from "../domain/inputIssueProjection.js";
|
||||
import { safeParseJson } from "../domain/safeJson.js";
|
||||
import {
|
||||
nativeUiObservationInputSchema,
|
||||
nativeUiScenarioInputSchema,
|
||||
} from "../domain/native/nativeUiObservation.js";
|
||||
import { nativeUiScenarioInputSchema } from "../domain/native/nativeUiObservation.js";
|
||||
import {
|
||||
nativeCommandTimeoutField,
|
||||
nativeUiDeadlineField,
|
||||
@@ -52,16 +49,11 @@ export const registerUtilityCommands = (
|
||||
registerReferenceSourceCommand(cli, logger, environment);
|
||||
};
|
||||
|
||||
// Decimal tokens only; shared fields own their bounds and defaults.
|
||||
const nativeUiScopeFields = nativeUiObservationInputSchema.shape;
|
||||
// Decimal tokens only; shared fields own their bounds and help.
|
||||
const nativeUiScopeOptions = {
|
||||
pid: cliDecimalField(
|
||||
nativeUiScopeFields.pid.describe("Existing application PID"),
|
||||
),
|
||||
windowId: cliDecimalField(
|
||||
nativeUiScopeFields.window_id.describe(
|
||||
"Exact selected application window ID",
|
||||
),
|
||||
pid: cliIntegerArgument(1).describe("Existing application PID"),
|
||||
windowId: cliIntegerArgument(1).describe(
|
||||
"Exact selected application window ID",
|
||||
),
|
||||
screenshot: z
|
||||
.boolean()
|
||||
@@ -71,11 +63,9 @@ const nativeUiScopeOptions = {
|
||||
.boolean()
|
||||
.default(true)
|
||||
.describe("Read the selected window accessibility tree"),
|
||||
maxNodes: cliDecimalField(
|
||||
nativeUiScopeFields.max_nodes.describe(
|
||||
"Maximum accessibility nodes per capture",
|
||||
),
|
||||
),
|
||||
maxNodes: cliIntegerArgument(1)
|
||||
.default(500)
|
||||
.describe("Maximum accessibility nodes per capture"),
|
||||
nativeUiDeadlineMs: cliDecimalField(nativeUiDeadlineField),
|
||||
};
|
||||
|
||||
@@ -374,15 +364,11 @@ const registerReferenceSourceCommand = (
|
||||
.describe(
|
||||
"Complete committed graph or compact historical-source summary",
|
||||
),
|
||||
outlineDepth: cliDecimalField(
|
||||
z
|
||||
.int()
|
||||
.min(0)
|
||||
.optional()
|
||||
.describe(
|
||||
"Summary outline path depth (default: 2; 0 omits entries); requires --detail summary",
|
||||
),
|
||||
),
|
||||
outlineDepth: cliIntegerArgument(0)
|
||||
.optional()
|
||||
.describe(
|
||||
"Summary outline path depth (default: 2; 0 omits entries); requires --detail summary",
|
||||
),
|
||||
})
|
||||
.superRefine((options, context) => {
|
||||
if (options.outlineDepth !== undefined && options.detail !== "summary")
|
||||
|
||||
@@ -6,7 +6,7 @@ import { logCliCommand } from "../cliLogging.js";
|
||||
import { webModuleTraceInputSchema } from "../domain/webModuleTrace.js";
|
||||
import type { Logger } from "pino";
|
||||
import type { CliInstance } from "./types.js";
|
||||
import { cliDecimalField } from "./options.js";
|
||||
import { cliIntegerArgument } from "./options.js";
|
||||
import { withCommandCancellation } from "./commandCancellation.js";
|
||||
import { finishAnalysisCommand } from "./finishAnalysisCommand.js";
|
||||
|
||||
@@ -25,10 +25,8 @@ export const registerWebModuleCommands = (
|
||||
"Trace one captured script's native imports to URLs and captured source candidates",
|
||||
args: z.object({
|
||||
manifestPath: webModuleTraceInputSchema.shape.manifest_path,
|
||||
scriptIndex: cliDecimalField(
|
||||
webModuleTraceInputSchema.shape.script_index.describe(
|
||||
"Zero-based manifest script index",
|
||||
),
|
||||
scriptIndex: cliIntegerArgument(0).describe(
|
||||
"Zero-based manifest script index",
|
||||
),
|
||||
}),
|
||||
options: z.object({
|
||||
|
||||
@@ -8,7 +8,7 @@ import { CLI_COMMANDS } from "../cliCommandNames.js";
|
||||
import { logCliCommand } from "../cliLogging.js";
|
||||
import type { Logger } from "pino";
|
||||
import type { CliInstance } from "./types.js";
|
||||
import { cliDecimalField } from "./options.js";
|
||||
import { cliDecimalField, cliIntegerArgument } from "./options.js";
|
||||
import { withCommandCancellation } from "./commandCancellation.js";
|
||||
import {
|
||||
analysisEvidenceDeliveryOptions,
|
||||
@@ -18,9 +18,6 @@ import { localPathStringSchema } from "../domain/localPath.js";
|
||||
import { absoluteCallerPath } from "../filesystem/callerPath.js";
|
||||
import { explicitSensitiveValueOptions } from "./explicitSensitiveArguments.js";
|
||||
|
||||
const recordOrdinal =
|
||||
inspectWebNetworkCaptureInputSchema.shape.record_ordinals.unwrap().element;
|
||||
|
||||
/** Expose offline historical capture inspection with the same selection contract as MCP. */
|
||||
export const registerWebNetworkCaptureCommands = (
|
||||
cli: CliInstance,
|
||||
@@ -54,7 +51,7 @@ export const registerWebNetworkCaptureCommands = (
|
||||
.optional()
|
||||
.describe("Fully qualified message type in the descriptor set"),
|
||||
record: z
|
||||
.array(cliDecimalField(recordOrdinal))
|
||||
.array(cliIntegerArgument(0))
|
||||
.optional()
|
||||
.describe("Zero-based producer ordinals; repeat to select several"),
|
||||
...explicitSensitiveValueOptions,
|
||||
|
||||
@@ -16,11 +16,10 @@ import { CLI_COMMANDS } from "../cliCommandNames.js";
|
||||
import { logCliCommand } from "../cliLogging.js";
|
||||
import type { Logger } from "pino";
|
||||
import type { CliInstance } from "./types.js";
|
||||
import { cliDecimalField } from "./options.js";
|
||||
import { cliDecimalField, cliIntegerArgument } from "./options.js";
|
||||
import { withCommandCancellation } from "./commandCancellation.js";
|
||||
|
||||
const sourceMapSelection = webSourceLocationInputSchema.shape.source_map;
|
||||
const generatedPosition = webSourceLocationInputSchema.shape.generated_position;
|
||||
|
||||
/** Expose the same explicit selected map/point contract through the CLI. */
|
||||
export const registerWebSourceLocationCommands = (
|
||||
@@ -34,20 +33,14 @@ export const registerWebSourceLocationCommands = (
|
||||
"Trace one retained script position through an explicitly selected local source map",
|
||||
args: z.object({
|
||||
manifestPath: webSourceLocationInputSchema.shape.manifest_path,
|
||||
scriptIndex: cliDecimalField(
|
||||
webSourceLocationInputSchema.shape.script_index.describe(
|
||||
"Zero-based manifest script index",
|
||||
),
|
||||
scriptIndex: cliIntegerArgument(0).describe(
|
||||
"Zero-based manifest script index",
|
||||
),
|
||||
sourceMapPath: sourceMapSelection.shape.path,
|
||||
sourceMapUrl: sourceMapSelection.shape.url,
|
||||
line: cliDecimalField(
|
||||
generatedPosition.shape.line.describe("One-based generated line"),
|
||||
),
|
||||
column: cliDecimalField(
|
||||
generatedPosition.shape.column.describe(
|
||||
"Zero-based UTF-16 generated column",
|
||||
),
|
||||
line: cliIntegerArgument(1).describe("One-based generated line"),
|
||||
column: cliIntegerArgument(0).describe(
|
||||
"Zero-based UTF-16 generated column",
|
||||
),
|
||||
}),
|
||||
options: z.object({
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
import type { Writable } from "node:stream";
|
||||
|
||||
import {
|
||||
cliCompletionShell,
|
||||
isProjectScopedAmpRegistration,
|
||||
normalizeCliArguments,
|
||||
splitIncurBuiltinArguments,
|
||||
} from "./cliBuiltinArguments.js";
|
||||
import {
|
||||
cliConfigurationError,
|
||||
parseCliOutputArguments,
|
||||
renderCliOutputArgumentError,
|
||||
renderEmptyFilteredCliOutput,
|
||||
renderVersionReport,
|
||||
} from "./cliOutput.js";
|
||||
import type { VersionReportHost } from "./domain/versionReport.js";
|
||||
|
||||
/**
|
||||
* Process facilities one command needs. The executable binds them to its own
|
||||
* streams and exit; a driver that serves several commands from one process
|
||||
* binds them to per-command capture.
|
||||
*/
|
||||
export interface OneShotCliHost {
|
||||
readonly environment: NodeJS.ProcessEnv;
|
||||
readonly runtime: VersionReportHost;
|
||||
readonly stdoutIsTerminal: boolean;
|
||||
/** Destination for result documents the command streams itself. */
|
||||
readonly resultDestination: Writable;
|
||||
/** Status reported after a recovered failure when the reader has closed stdout. */
|
||||
readonly closedStdoutStatus: number;
|
||||
writeStdout(text: string): void;
|
||||
writeStderr(text: string): void;
|
||||
stdoutClosed(): boolean;
|
||||
/** Status the process would report if it ended now. */
|
||||
exitCode(): number;
|
||||
setExitCode(code: number): void;
|
||||
/** Called once when the command runtime loads, before any command runs. */
|
||||
ownCliResources(close: () => Promise<void>): void;
|
||||
/**
|
||||
* The command asked to end the process with `code`. The host owns that
|
||||
* exit and must call `finishOutput` with the status it reports.
|
||||
*/
|
||||
exit(code: number, finishOutput: (status: number) => Promise<void>): void;
|
||||
/** Whether `exit` has run. Its host then owns the pending result write. */
|
||||
exitRequested(): boolean;
|
||||
}
|
||||
|
||||
const AMP_PROJECT_SCOPE_REFUSAL =
|
||||
"Amp does not support project-scoped registration. Run without --no-global to register Amp globally, or select a client with project settings. No client settings were changed.";
|
||||
|
||||
/**
|
||||
* Run one CLI command: admit its output arguments and configuration, answer
|
||||
* the documents that need no command runtime, then dispatch. Failures the
|
||||
* command did not report propagate to the host.
|
||||
*/
|
||||
export const runOneShotCli = async (
|
||||
arguments_: readonly string[],
|
||||
host: OneShotCliHost,
|
||||
): Promise<void> => {
|
||||
const isShellCompletion = cliCompletionShell(host.environment) !== undefined;
|
||||
const cliArguments = isShellCompletion
|
||||
? [...arguments_]
|
||||
: normalizeCliArguments([...arguments_]);
|
||||
// Completion words are transport data, including partial output flags.
|
||||
const outputOptions = parseCliOutputArguments(
|
||||
isShellCompletion ? [] : cliArguments,
|
||||
);
|
||||
const outputArguments = outputOptions.validation;
|
||||
// `--version` with an explicit format reports the runtime as a document;
|
||||
// plain `--version` stays Incur's bare version string.
|
||||
const versionOutput = renderVersionReport(
|
||||
outputOptions,
|
||||
host.runtime,
|
||||
host.stdoutIsTerminal,
|
||||
);
|
||||
const configurationError = cliConfigurationError(host.environment);
|
||||
// Doctor is the readiness report for this failure. Other commands still stop
|
||||
// here so they do not run with an unusable log level.
|
||||
if (
|
||||
configurationError !== undefined &&
|
||||
(isShellCompletion ||
|
||||
splitIncurBuiltinArguments(cliArguments).command[0] !== "doctor")
|
||||
) {
|
||||
host.writeStderr(`${configurationError}\n`);
|
||||
host.setExitCode(1);
|
||||
return;
|
||||
}
|
||||
if (!outputArguments.ok) {
|
||||
host.writeStdout(renderCliOutputArgumentError(outputArguments));
|
||||
host.setExitCode(1);
|
||||
return;
|
||||
}
|
||||
if (versionOutput !== undefined) {
|
||||
host.writeStdout(versionOutput);
|
||||
return;
|
||||
}
|
||||
if (!isShellCompletion && isProjectScopedAmpRegistration(cliArguments)) {
|
||||
// Amp has global settings only. Share the parsed scope fact with the
|
||||
// registration workflow and refuse before loading the command runtime.
|
||||
//
|
||||
// A caller that asked for a structured document gets the refusal in
|
||||
// that document, through the same pre-execution error renderer the
|
||||
// output-argument branch uses; anything else keeps the plain message.
|
||||
const refusalFormat = outputOptions.format;
|
||||
if (
|
||||
refusalFormat === "json" ||
|
||||
refusalFormat === "jsonl" ||
|
||||
refusalFormat === "yaml"
|
||||
) {
|
||||
host.writeStdout(
|
||||
renderCliOutputArgumentError({
|
||||
format: refusalFormat,
|
||||
code: "AMP_PROJECT_SCOPE_UNSUPPORTED",
|
||||
message: AMP_PROJECT_SCOPE_REFUSAL,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
host.writeStderr(`${AMP_PROJECT_SCOPE_REFUSAL}\n`);
|
||||
}
|
||||
host.setExitCode(1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Loaded only now: the documents above answer without the command catalog.
|
||||
const { closeCliResources, createCli } = await import("./cli.js");
|
||||
host.ownCliResources(closeCliResources);
|
||||
const { createCliResultOutput } = await import("./cli/cliResultOutput.js");
|
||||
let wroteOutput = false;
|
||||
const resultOutput = createCliResultOutput(
|
||||
outputOptions,
|
||||
host.resultDestination,
|
||||
);
|
||||
try {
|
||||
await createCli(host.environment, resultOutput).serve(cliArguments, {
|
||||
stdout: (output) => {
|
||||
if (resultOutput?.handled) {
|
||||
if (resultOutput.failed && !host.stdoutClosed())
|
||||
host.writeStderr(output);
|
||||
return;
|
||||
}
|
||||
if (output.length > 0) wroteOutput = true;
|
||||
if (resultOutput?.captureSdkOutput(output)) return;
|
||||
host.writeStdout(output);
|
||||
},
|
||||
exit: (code) => {
|
||||
host.exit(typeof code === "number" ? code : 0, async (status) =>
|
||||
resultOutput?.finishSdkOutput(status),
|
||||
);
|
||||
},
|
||||
});
|
||||
} catch (cause) {
|
||||
if (wroteOutput) throw cause;
|
||||
const recovered = await resultOutput
|
||||
?.recoverFormattingFailure(cause)
|
||||
.catch((writeFailure: unknown) => {
|
||||
// Recovery writes outside Incur's command boundary. A reader that
|
||||
// declined this document keeps the same quiet closed-pipe outcome.
|
||||
if (!host.stdoutClosed()) throw writeFailure;
|
||||
return true;
|
||||
});
|
||||
if (!recovered) throw cause;
|
||||
host.setExitCode(host.stdoutClosed() ? host.closedStdoutStatus : 1);
|
||||
}
|
||||
if (!wroteOutput && !resultOutput?.handled)
|
||||
host.writeStdout(renderEmptyFilteredCliOutput(outputOptions) ?? "");
|
||||
// A requested exit is finished by the host, which owns its pending write.
|
||||
if (!host.exitRequested())
|
||||
await resultOutput?.finishSdkOutput(host.exitCode());
|
||||
};
|
||||
+10
-1
@@ -366,9 +366,18 @@ export const renderEmptyFilteredCliOutput = ({
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/** A refusal reported as a structured document before any command runs. */
|
||||
export interface CliPreExecutionError {
|
||||
readonly format: StructuredOutputFormat;
|
||||
readonly code:
|
||||
| Exclude<CliOutputArgumentValidation, { readonly ok: true }>["code"]
|
||||
| "AMP_PROJECT_SCOPE_UNSUPPORTED";
|
||||
readonly message: string;
|
||||
}
|
||||
|
||||
/** Render one complete structured error before command execution. */
|
||||
export const renderCliOutputArgumentError = (
|
||||
error: Exclude<CliOutputArgumentValidation, { readonly ok: true }>,
|
||||
error: CliPreExecutionError,
|
||||
): string => {
|
||||
const value = {
|
||||
ok: false,
|
||||
|
||||
@@ -6,7 +6,6 @@ import { OBJDUMP_PROVIDER_IDENTITY } from "../objdump/ObjdumpProviderIdentity.js
|
||||
import {
|
||||
rizinCommand,
|
||||
rizinCommandLimitations,
|
||||
rizinCommandRejection,
|
||||
} from "../rizin/RizinCommand.js";
|
||||
import { RIZIN_PROVIDER_IDENTITY } from "../rizin/RizinProviderIdentity.js";
|
||||
import { resolveWindowsProviderExecutable } from "./resolveWindowsProviderExecutable.js";
|
||||
@@ -33,7 +32,6 @@ export const createReverseEngineeringCommandPort = (
|
||||
),
|
||||
arguments: rizinCommand(input),
|
||||
limitations: rizinCommandLimitations(input),
|
||||
rejection: rizinCommandRejection,
|
||||
identity: RIZIN_PROVIDER_IDENTITY,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -63,7 +63,7 @@ export const GDB_TOOL_CONTRACTS = [
|
||||
...toolContractMetadata("gdb_session_status"),
|
||||
kind: "native-provider",
|
||||
description:
|
||||
"Return the observed lifecycle state and bounded recent MI records and process diagnostics for one persistent REA GDB session; closed_reason says why a closed session stopped, and truncation and invalid-UTF-8 fields report discarded or lossy data.",
|
||||
"Return the observed lifecycle state and bounded recent MI records and process diagnostics for one persistent REA GDB session; truncation and invalid-UTF-8 fields report discarded or lossy data.",
|
||||
inputSchema: z.strictObject({ session_id: sessionId }),
|
||||
outputSchema: z.strictObject({
|
||||
session_id: sessionId,
|
||||
@@ -79,12 +79,6 @@ export const GDB_TOOL_CONTRACTS = [
|
||||
diagnostics_truncated: z.boolean(),
|
||||
inferior_tty: z.string().nullable(),
|
||||
inferior_tty_verified: z.boolean(),
|
||||
closed_reason: z
|
||||
.string()
|
||||
.exactOptional()
|
||||
.describe(
|
||||
"Present when state is closed: why the session stopped accepting commands. Close it and start a new session to continue.",
|
||||
),
|
||||
}),
|
||||
examples: [
|
||||
{
|
||||
|
||||
@@ -39,7 +39,7 @@ export const REVERSE_ENGINEERING_TOOL_CONTRACTS = [
|
||||
...toolContractMetadata("execute_rizin_command"),
|
||||
kind: "native-provider",
|
||||
description:
|
||||
"Run one caller-selected Rizin command on a local artifact. Commands/plugins can execute code, mutate files, access network and read outside the artifact; those external inputs are not Evidence-bound. Suppresses user settings/scripts but retains plugins. Raw stdout/stderr is authoritative, JSON is command-specific, and UTF-8 loss/truncation is explicit; output-limit termination leaves completion unknown. Rizin exits 0 even when its shell refuses a statement; completion_status rejected marks that refusal, and later statements did not run. Other command failures can still exit 0, so inspect stderr before treating empty output as absence. For raw images, asm_arch/asm_bits/asm_cpu select decoding and base_address maps offset 0; base_address also passes -n to skip loaders even on ELF, without REA confirming either. An admitted command retires shared binary analysis: close Ghidra explicitly first, then open_binary after effects settle. command_timeout_ms is per-phase and max_output_bytes bounds combined output (16 MiB default); both apply next call without restart and do not raise client receive limits or persistent debugger deadlines.",
|
||||
"Run one caller-selected Rizin command on a local artifact. Commands/plugins can execute code, mutate files, access network and read outside the artifact; those external inputs are not Evidence-bound. Suppresses user settings/scripts but retains plugins. completion_status reports process completion; Rizin can reject a command and exit with code 0. Inspect stdout/stderr before treating empty output as absence. Raw stdout/stderr is authoritative, JSON is command-specific, and UTF-8 loss/truncation is explicit; output-limit termination leaves completion unknown. For raw images, asm_arch/asm_bits/asm_cpu select decoding and base_address maps offset 0; base_address also passes -n to skip loaders even on ELF, without REA confirming either. An admitted command retires shared binary analysis: close Ghidra explicitly first, then open_binary after effects settle. command_timeout_ms is per-phase and max_output_bytes bounds combined output (16 MiB default); both apply next call without restart and do not raise client receive limits or persistent debugger deadlines.",
|
||||
inputSchema: rizinInputSchema,
|
||||
outputSchema: evidenceResultOf(commandObservationSchema),
|
||||
examples: [
|
||||
|
||||
@@ -72,7 +72,7 @@ export const RIZIN_DEBUG_TOOL_CONTRACTS = [
|
||||
...toolContractMetadata("rizin_debug_session_status"),
|
||||
kind: "native-provider",
|
||||
description:
|
||||
"Return observed lifecycle state and bounded recent NUL-framed output and process diagnostics for one Rizin debugger session; closed_reason says why a closed session stopped, truncation fields report discarded data, and output_invalid_utf8 reports whether invalid UTF-8 has been observed in session frame bytes; recent output may contain replacement characters.",
|
||||
"Return observed lifecycle state and bounded recent NUL-framed output and process diagnostics for one Rizin debugger session; truncation fields report discarded data, and output_invalid_utf8 reports whether invalid UTF-8 has been observed in session frame bytes; recent output may contain replacement characters.",
|
||||
inputSchema: z.strictObject({ session_id: sessionId }),
|
||||
outputSchema: z.strictObject({
|
||||
session_id: sessionId,
|
||||
@@ -82,12 +82,6 @@ export const RIZIN_DEBUG_TOOL_CONTRACTS = [
|
||||
recent_output_truncated: z.boolean(),
|
||||
diagnostics_truncated: z.boolean(),
|
||||
output_invalid_utf8: z.boolean(),
|
||||
closed_reason: z
|
||||
.string()
|
||||
.exactOptional()
|
||||
.describe(
|
||||
"Present when state is closed: why the session stopped accepting commands. Close it and start a new session to continue.",
|
||||
),
|
||||
}),
|
||||
examples: [
|
||||
{
|
||||
|
||||
@@ -9,11 +9,7 @@ export const commandObservationSchema = z.strictObject({
|
||||
output_budget_bytes: z.int().positive().exactOptional(),
|
||||
output_budget_source: z.enum(["default", "request"]).exactOptional(),
|
||||
output_truncated: z.boolean(),
|
||||
completion_status: z
|
||||
.enum(["complete", "rejected", "unknown"])
|
||||
.describe(
|
||||
"complete: the provider process completed. rejected: it completed, but the provider reported that it refused the command; limitations say what ran. unknown: the capture or process ended early.",
|
||||
),
|
||||
completion_status: z.enum(["complete", "unknown"]),
|
||||
artifact_sha256_after: z
|
||||
.string()
|
||||
.regex(/^[a-f0-9]{64}$/u)
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import {
|
||||
AnalysisCapabilityUnavailableError,
|
||||
AnalysisInputError,
|
||||
} from "./analysisErrorCore.js";
|
||||
|
||||
/** Tool names and wording for one persistent debugger's session errors. */
|
||||
export interface DebuggerSessionTools {
|
||||
readonly providerId: string;
|
||||
readonly name: string;
|
||||
readonly start: string;
|
||||
readonly close: string;
|
||||
}
|
||||
|
||||
/** A session_id this server does not hold, unlike a retained stopped session. */
|
||||
export const unknownDebuggerSessionError = (
|
||||
tools: DebuggerSessionTools,
|
||||
operation: string,
|
||||
): AnalysisInputError =>
|
||||
new AnalysisInputError(
|
||||
operation,
|
||||
{
|
||||
conformsToInputContract: true,
|
||||
userMessage: `No ${tools.name} debug session has this session_id in this REA server.`,
|
||||
remediationAction: `Use a session_id that ${tools.start} returned in this server process; ${tools.close} releases a session's ID. Call ${tools.start} to begin a new session.`,
|
||||
},
|
||||
[
|
||||
{
|
||||
path: ["session_id"],
|
||||
reason: "invalid_value",
|
||||
message: `No ${tools.name} debug session has this session_id in this REA server.`,
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
/** Refuse a command on a retained session that stopped, naming why it stopped. */
|
||||
export const stoppedDebuggerSessionError = (
|
||||
tools: DebuggerSessionTools,
|
||||
operation: string,
|
||||
reason: string,
|
||||
): AnalysisCapabilityUnavailableError =>
|
||||
new AnalysisCapabilityUnavailableError(tools.providerId, operation, reason, {
|
||||
userMessage: `The ${tools.name} debug session stopped and accepts no more commands. ${reason}`,
|
||||
remediationAction: `Call ${tools.close} with this session_id to release the stopped session, then call ${tools.start} to begin a new one. Earlier commands may have changed the target, so confirm their effects before repeating them.`,
|
||||
});
|
||||
|
||||
export const RIZIN_DEBUG_SESSION_TOOLS = {
|
||||
providerId: "rizin",
|
||||
name: "Rizin",
|
||||
start: "start_rizin_debug_session",
|
||||
close: "close_rizin_debug_session",
|
||||
} as const satisfies DebuggerSessionTools;
|
||||
|
||||
export const GDB_SESSION_TOOLS = {
|
||||
providerId: "gdb",
|
||||
name: "GDB",
|
||||
start: "start_gdb_session",
|
||||
close: "close_gdb_session",
|
||||
} as const satisfies DebuggerSessionTools;
|
||||
@@ -961,15 +961,22 @@ const createGlobalPathRead = (
|
||||
};
|
||||
byNamespace.set(namespace, effects);
|
||||
}
|
||||
const escapePrecedesRead = effects.escapes.some(({ node }) => {
|
||||
// Selecting this callee precedes exposing its own receiver. Other reads,
|
||||
// including arguments, need activation-aware ordering across repetitions.
|
||||
// The call that escapes a receiver contains the callee being identified.
|
||||
// That escape cannot have replaced the callee before the call selects it.
|
||||
const escapePrecedesRead = effects.escapes.some((effect) => {
|
||||
const escape = effect.node;
|
||||
if (
|
||||
(t.isCallExpression(node) || t.isOptionalCallExpression(node)) &&
|
||||
node.callee === site
|
||||
escape !== undefined &&
|
||||
escape !== site &&
|
||||
typeof escape.start === "number" &&
|
||||
typeof escape.end === "number" &&
|
||||
typeof site.start === "number" &&
|
||||
typeof site.end === "number" &&
|
||||
site.start >= escape.start &&
|
||||
site.end <= escape.end
|
||||
)
|
||||
return false;
|
||||
return semanticEffectTiming(site, node, state) !== "after";
|
||||
return semanticEffectTiming(site, escape, state) !== "after";
|
||||
});
|
||||
if (
|
||||
(escapesCovered && effects.escapes.length === 0) ||
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
import { expect, it } from "vitest";
|
||||
import { analyzeJavaScriptSemantics } from "./javascriptSemanticAnalysis.js";
|
||||
|
||||
const spawns = (source: string) =>
|
||||
analyzeJavaScriptSemantics(source).childProcessSpawns;
|
||||
|
||||
it.each(["spawn", "fork"])("rejects function-valued %s options", (method) => {
|
||||
expect(
|
||||
spawns(`
|
||||
import { ${method} } from "node:child_process";
|
||||
${method}("worker.js", () => {});
|
||||
${method}("worker.js", [], function options() {});
|
||||
${method}("worker.js", [], class Options {});
|
||||
`),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it("rejects a literal exec callback after explicit options", () => {
|
||||
expect(
|
||||
spawns(`
|
||||
import { exec } from "node:child_process";
|
||||
exec("node --version", {}, false);
|
||||
exec("node --version", null, {});
|
||||
`),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it("selects execFile callbacks with and without an argv array", () => {
|
||||
expect(
|
||||
spawns(`
|
||||
import { execFile } from "node:child_process";
|
||||
execFile("node", {}, { cwd: "." });
|
||||
execFile("node", [], {}, false);
|
||||
execFile("node", null, {}, "callback");
|
||||
`),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps callback overloads and nullish callbacks", () => {
|
||||
expect(
|
||||
spawns(`
|
||||
import { exec, execFile } from "node:child_process";
|
||||
exec("node --version", () => {}, false);
|
||||
exec("node --version", {}, null);
|
||||
execFile("node", () => {}, { cwd: false });
|
||||
execFile("node", [], () => {}, false);
|
||||
execFile("node", {}, null);
|
||||
execFile("node", [], {}, undefined);
|
||||
`),
|
||||
).toHaveLength(6);
|
||||
});
|
||||
|
||||
it("leaves uncertain callback slots and spread positions undecided", () => {
|
||||
expect(
|
||||
spawns(`
|
||||
import { exec, execFile, spawn } from "node:child_process";
|
||||
function run(options, callback, args) {
|
||||
exec("node --version", options, false);
|
||||
exec("node --version", {}, callback);
|
||||
execFile("node", options, false);
|
||||
execFile("node", [], options, false);
|
||||
execFile("node", ...args, {}, false);
|
||||
spawn("node", ...args, () => {});
|
||||
}
|
||||
`),
|
||||
).toHaveLength(6);
|
||||
});
|
||||
@@ -1,189 +0,0 @@
|
||||
import { expect, it } from "vitest";
|
||||
import { analyzeJavaScriptSemantics } from "./javascriptSemanticAnalysis.js";
|
||||
|
||||
const operations = (source: string) =>
|
||||
analyzeJavaScriptSemantics(source).timerOperations;
|
||||
|
||||
it.each(["setTimeout", "setInterval"])(
|
||||
"does not create a %s handle when delay conversion must throw",
|
||||
(method) => {
|
||||
const result = operations(`
|
||||
import { ${method}, clearTimeout } from "node:timers";
|
||||
const positive = ${method}(() => {}, 1n);
|
||||
const negative = ${method}(() => {}, -1n);
|
||||
const zero = ${method}(() => {}, 0n);
|
||||
const symbol = ${method}(() => {}, Symbol("delay"));
|
||||
const converted = ${method}(() => {}, BigInt("1"));
|
||||
clearTimeout(positive);
|
||||
`);
|
||||
expect(result.filter(({ kind }) => kind === "schedule")).toEqual([]);
|
||||
expect(result).toMatchObject([
|
||||
{ kind: "cancel", resolution: "unresolved", linkedTimerId: null },
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
it("keeps supported delays and the immediate callback's BigInt argument", () => {
|
||||
const result = operations(`
|
||||
import { setTimeout, setInterval, setImmediate } from "node:timers";
|
||||
setTimeout(() => {}, "2");
|
||||
setInterval(() => {}, null);
|
||||
setTimeout(() => {}, !1n);
|
||||
setTimeout(() => {}, void 1n);
|
||||
setImmediate(() => {}, 1n);
|
||||
`);
|
||||
expect(result.map(({ method }) => method)).toEqual([
|
||||
"setTimeout",
|
||||
"setInterval",
|
||||
"setTimeout",
|
||||
"setTimeout",
|
||||
"setImmediate",
|
||||
]);
|
||||
});
|
||||
|
||||
it("retains uncertain delays, shifted arguments and shadowed constructors", () => {
|
||||
const result = operations(`
|
||||
import { setTimeout } from "node:timers";
|
||||
function schedule(delay, args, Symbol, BigInt) {
|
||||
setTimeout(() => {}, delay);
|
||||
setTimeout(...args, 1n);
|
||||
setTimeout(() => {}, ...args);
|
||||
setTimeout(() => {}, Symbol("delay"));
|
||||
setTimeout(() => {}, BigInt("1"));
|
||||
}
|
||||
`);
|
||||
expect(result).toHaveLength(5);
|
||||
});
|
||||
|
||||
it.each([
|
||||
'eval("Symbol = () => 1; BigInt = () => 1");',
|
||||
'(0, eval)("Symbol = () => 1; BigInt = () => 1");',
|
||||
'Function("Symbol = () => 1; BigInt = () => 1")();',
|
||||
])(
|
||||
"retains delays after global code may replace constructors: %s",
|
||||
(effect) => {
|
||||
expect(
|
||||
operations(`
|
||||
import { setTimeout, setInterval } from "node:timers";
|
||||
${effect}
|
||||
setTimeout(() => {}, Symbol("delay"));
|
||||
setInterval(() => {}, BigInt("1"));
|
||||
`).map(({ method }) => method),
|
||||
).toEqual(["setTimeout", "setInterval"]);
|
||||
},
|
||||
);
|
||||
|
||||
it("keeps literal rejection independent of global code effects", () => {
|
||||
expect(
|
||||
operations(`
|
||||
import { setTimeout } from "node:timers";
|
||||
eval(code);
|
||||
setTimeout(() => {}, 1n);
|
||||
`),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it("ignores later global code and shadowed eval for constructor identity", () => {
|
||||
expect(
|
||||
operations(`
|
||||
import { setTimeout } from "node:timers";
|
||||
setTimeout(() => {}, Symbol("delay"));
|
||||
eval("Symbol = () => 1");
|
||||
`),
|
||||
).toEqual([]);
|
||||
expect(
|
||||
operations(`
|
||||
import { setTimeout } from "node:timers";
|
||||
function run(eval) {
|
||||
eval("Symbol = () => 1");
|
||||
setTimeout(() => {}, Symbol("delay"));
|
||||
}
|
||||
`),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["Symbol", "configure(globalThis);"],
|
||||
["BigInt", "configure(globalThis);"],
|
||||
["Symbol", "const host = globalThis; configure(host);"],
|
||||
["BigInt", "configure({ host: globalThis });"],
|
||||
])("retains %s delays and cancellation after %s", (constructor, effect) => {
|
||||
const result = operations(`
|
||||
import configure from "./configure.js";
|
||||
import { setTimeout, clearTimeout } from "node:timers";
|
||||
${effect}
|
||||
const handle = setTimeout(() => {}, ${constructor}("1"));
|
||||
clearTimeout(handle);
|
||||
`);
|
||||
expect(result).toMatchObject([
|
||||
{ kind: "schedule", resolution: "complete" },
|
||||
{ kind: "cancel", resolution: "complete" },
|
||||
]);
|
||||
expect(result[1]?.linkedTimerId).toBe(result[0]?.timerId);
|
||||
});
|
||||
|
||||
it("still rejects literal BigInt delays after the global object escapes", () => {
|
||||
expect(
|
||||
operations(`
|
||||
import configure from "./configure.js";
|
||||
import { setTimeout, setInterval } from "node:timers";
|
||||
configure(globalThis);
|
||||
setTimeout(() => {}, 1n);
|
||||
setInterval(() => {}, -1n);
|
||||
`),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it("does not let later or unrelated escapes change constructor identity", () => {
|
||||
expect(
|
||||
operations(`
|
||||
import configure from "./configure.js";
|
||||
import { setTimeout, setInterval } from "node:timers";
|
||||
configure({});
|
||||
setTimeout(() => {}, Symbol("1"));
|
||||
setInterval(() => {}, BigInt("1"));
|
||||
configure(globalThis);
|
||||
`),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it("selects a constructor before an argument can escape the global object", () => {
|
||||
expect(
|
||||
operations(`
|
||||
import configure from "./configure.js";
|
||||
import { setTimeout } from "node:timers";
|
||||
setTimeout(() => {}, Symbol(configure(globalThis)));
|
||||
`),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it.each(["Symbol", "BigInt"])(
|
||||
"retains %s identity uncertainty inside an escaped callback",
|
||||
(constructor) => {
|
||||
const result = operations(`
|
||||
import configure from "./configure.js";
|
||||
import { setTimeout, clearTimeout } from "node:timers";
|
||||
configure(globalThis, () => {
|
||||
const handle = setTimeout(() => {}, ${constructor}("1"));
|
||||
clearTimeout(handle);
|
||||
});
|
||||
`);
|
||||
expect(result).toMatchObject([
|
||||
{ kind: "schedule", resolution: "complete" },
|
||||
{ kind: "cancel", resolution: "complete" },
|
||||
]);
|
||||
expect(result[1]?.linkedTimerId).toBe(result[0]?.timerId);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
'for (let i=0;i<2;i++) configure(globalThis, i ? setTimeout(()=>{}, Symbol("1")) : null);',
|
||||
'function run(first) { configure(globalThis, first ? null : setTimeout(()=>{}, BigInt("1"))); } run(true); run(false);',
|
||||
'let first=true; while (next()) { configure(globalThis, first ? null : setTimeout(()=>{}, Symbol("1"))); first=false; }',
|
||||
])("retains constructor uncertainty across repeated escapes: %s", (source) => {
|
||||
expect(
|
||||
operations(
|
||||
`import configure from "./configure.js"; import {setTimeout} from "node:timers"; ${source}`,
|
||||
),
|
||||
).toMatchObject([{ kind: "schedule" }]);
|
||||
});
|
||||
@@ -19,14 +19,8 @@ import {
|
||||
} from "./javascriptSemanticState.js";
|
||||
import { semanticBindingAtRead } from "./javascriptSemanticMutationInitializers.js";
|
||||
import { traverseJavaScriptValueAst } from "./javascriptSemanticTraversal.js";
|
||||
import {
|
||||
createGlobalReferenceReader,
|
||||
isUnmodifiedGlobalReference,
|
||||
} from "./javascriptSemanticAmbientWrites.js";
|
||||
import {
|
||||
semanticOpaqueGlobalMayHaveChanged,
|
||||
semanticOpaqueNameMayHaveChanged,
|
||||
} from "./javascriptSemanticOpaqueEffects.js";
|
||||
import { isUnmodifiedGlobalReference } from "./javascriptSemanticAmbientWrites.js";
|
||||
import { semanticOpaqueNameMayHaveChanged } from "./javascriptSemanticOpaqueEffects.js";
|
||||
import { range } from "./javascriptStaticAnalysisHelpers.js";
|
||||
import {
|
||||
builtinDataEffectIdentity,
|
||||
@@ -115,10 +109,6 @@ interface TimerCollectionInput {
|
||||
readonly ownerCallableId: string | null;
|
||||
readonly state: JavaScriptSemanticAnalysisState;
|
||||
readonly output: TimerCandidate[];
|
||||
readonly delayConstructors: ReadonlyMap<
|
||||
string,
|
||||
ReturnType<typeof createGlobalReferenceReader>
|
||||
>;
|
||||
readonly ancestors: readonly t.Node[];
|
||||
}
|
||||
|
||||
@@ -130,10 +120,6 @@ export const collectJavaScriptSemanticAsyncEffects = (
|
||||
): AsyncEffectAnalysis => {
|
||||
const events: JavaScriptSemanticEventOperation[] = [];
|
||||
const timers: TimerCandidate[] = [];
|
||||
const delayConstructors = new Map([
|
||||
["Symbol", createGlobalReferenceReader(state, ["Symbol"])],
|
||||
["BigInt", createGlobalReferenceReader(state, ["BigInt"])],
|
||||
]);
|
||||
const callableIds = new Set(callables.map(({ callableId }) => callableId));
|
||||
const callableStack = createSemanticCallableIdStack(callableIds);
|
||||
const ancestors: t.Node[] = [];
|
||||
@@ -148,7 +134,6 @@ export const collectJavaScriptSemanticAsyncEffects = (
|
||||
ownerCallableId,
|
||||
state,
|
||||
output: timers,
|
||||
delayConstructors,
|
||||
ancestors,
|
||||
});
|
||||
}
|
||||
@@ -391,38 +376,6 @@ const schedulesWithNonFunctionCallback = (
|
||||
return t.isUnaryExpression(value);
|
||||
};
|
||||
|
||||
// Node converts timeout and interval delays to numbers before creating a handle.
|
||||
// BigInt and Symbol values throw there. A spread can shift the delay slot, and
|
||||
// shadowed or replaced constructors cannot establish the argument's type.
|
||||
const schedulesWithInvalidDelay = (
|
||||
node: t.CallExpression | t.OptionalCallExpression,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
delayConstructors: TimerCollectionInput["delayConstructors"],
|
||||
): boolean => {
|
||||
if (
|
||||
node.arguments.slice(0, 2).some((argument) => t.isSpreadElement(argument))
|
||||
)
|
||||
return false;
|
||||
const argument = node.arguments[1];
|
||||
if (argument === undefined) return false;
|
||||
const value = unwrapJavaScriptExpression(argument).node;
|
||||
if (t.isBigIntLiteral(value)) return true;
|
||||
if (
|
||||
t.isUnaryExpression(value) &&
|
||||
["+", "-", "~"].includes(value.operator) &&
|
||||
t.isBigIntLiteral(unwrapJavaScriptExpression(value.argument).node)
|
||||
)
|
||||
return true;
|
||||
return (
|
||||
t.isCallExpression(value) &&
|
||||
t.isIdentifier(value.callee) &&
|
||||
delayConstructors.get(value.callee.name)?.(value.callee).status ===
|
||||
"unmodified" &&
|
||||
!semanticOpaqueNameMayHaveChanged(value.callee, state) &&
|
||||
!semanticOpaqueGlobalMayHaveChanged(value.callee, state)
|
||||
);
|
||||
};
|
||||
|
||||
const collectTimer = (input: TimerCollectionInput): void => {
|
||||
const { node, ownerCallableId, state, output, ancestors } = input;
|
||||
const identity = timerMethod(node.callee, state);
|
||||
@@ -431,9 +384,7 @@ const collectTimer = (input: TimerCollectionInput): void => {
|
||||
if (
|
||||
TIMER_SCHEDULE_METHODS.some((candidate) => candidate === method) &&
|
||||
nodeBuiltinTimerMethod(node.callee, state) !== null &&
|
||||
(schedulesWithNonFunctionCallback(node, state) ||
|
||||
((method === "setTimeout" || method === "setInterval") &&
|
||||
schedulesWithInvalidDelay(node, state, input.delayConstructors)))
|
||||
schedulesWithNonFunctionCallback(node, input.state)
|
||||
)
|
||||
return;
|
||||
const kind = TIMER_SCHEDULE_METHODS.some((candidate) => candidate === method)
|
||||
|
||||
@@ -79,8 +79,7 @@ export const collectJavaScriptSemanticChildProcesses = (
|
||||
!forkRejectsStdioArray(candidate, context) &&
|
||||
!rejectsStdioLiteral(candidate, context) &&
|
||||
!rejectsProcessOptionLiteral(candidate, context) &&
|
||||
!rejectsOptionsShape(candidate, context, state) &&
|
||||
!rejectsCallback(candidate, context),
|
||||
!rejectsOptionsShape(candidate, context, state),
|
||||
);
|
||||
const childProcessSpawns = candidates.map((candidate) =>
|
||||
immutableSpawn(candidate, context, state),
|
||||
@@ -648,13 +647,6 @@ const rejectsOptionsShape = (
|
||||
if (options === undefined) return false;
|
||||
const value = unwrapJavaScriptExpression(options).node;
|
||||
if (context.isArray(value)) return true;
|
||||
if (
|
||||
candidate.method !== "execFile" &&
|
||||
(t.isFunctionExpression(value) ||
|
||||
t.isArrowFunctionExpression(value) ||
|
||||
t.isClassExpression(value))
|
||||
)
|
||||
return true;
|
||||
const literal = readExactJavaScriptLiteral(value);
|
||||
if (literal.found)
|
||||
return !(candidate.method !== "spawn" && literal.value === null);
|
||||
@@ -670,52 +662,6 @@ const rejectsOptionsShape = (
|
||||
return isUnpricedPrimitiveLiteral(value);
|
||||
};
|
||||
|
||||
/** Select only callback slots whose overload is established by source syntax. */
|
||||
const rejectsCallback = (
|
||||
candidate: SpawnCandidate,
|
||||
context: ChildSpawnContext,
|
||||
): boolean => {
|
||||
if (candidate.method !== "exec" && candidate.method !== "execFile")
|
||||
return false;
|
||||
const args = candidate.node.arguments;
|
||||
if (args.slice(0, 4).some((argument) => t.isSpreadElement(argument)))
|
||||
return false;
|
||||
const second = args[1] && unwrapJavaScriptExpression(args[1]).node;
|
||||
const third = args[2] && unwrapJavaScriptExpression(args[2]).node;
|
||||
let callback: t.Node | undefined;
|
||||
if (candidate.method === "exec") {
|
||||
// A function in slot 1 replaces slot 2, even when slot 2 is not callable.
|
||||
if (
|
||||
second !== undefined &&
|
||||
!context.isNullish(second) &&
|
||||
!rejectsSemanticEventListener(second)
|
||||
)
|
||||
return false;
|
||||
callback = third;
|
||||
} else if (t.isObjectExpression(second) || t.isRegExpLiteral(second)) {
|
||||
callback = third;
|
||||
} else if (
|
||||
second === undefined ||
|
||||
context.isArray(second) ||
|
||||
context.isNullish(second)
|
||||
) {
|
||||
// With argv present, a third-slot function replaces the fourth callback.
|
||||
if (
|
||||
third !== undefined &&
|
||||
!context.isNullish(third) &&
|
||||
!rejectsSemanticEventListener(third)
|
||||
)
|
||||
return false;
|
||||
callback = args[3];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
callback !== undefined &&
|
||||
!context.isNullish(callback) &&
|
||||
rejectsSemanticEventListener(callback)
|
||||
);
|
||||
};
|
||||
/**
|
||||
* The exact-value reader deliberately refuses to price a BigInt, a non-finite
|
||||
* numeric literal such as `1e400`, and a negated numeric literal such as `-0`.
|
||||
|
||||
@@ -13,11 +13,6 @@ import {
|
||||
AnalysisInputError,
|
||||
AnalysisTimeoutError,
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import {
|
||||
GDB_SESSION_TOOLS,
|
||||
stoppedDebuggerSessionError,
|
||||
unknownDebuggerSessionError,
|
||||
} from "../domain/debuggerSessionErrors.js";
|
||||
import {
|
||||
AnalysisError,
|
||||
type AnalysisErrorOptions,
|
||||
@@ -547,8 +542,6 @@ interface GdbSession {
|
||||
noInferiorVerified: boolean;
|
||||
/** Sticky admission seal once destructive cleanup starts, including failed stops. */
|
||||
cleanupStarted: boolean;
|
||||
/** close_gdb_session started cleanup, so a successful retry releases the ID. */
|
||||
closeRequested: boolean;
|
||||
stopAttempt: ReturnType<typeof stopOwnedDebuggerProcess> | undefined;
|
||||
readonly pending: Map<number, PendingCommand>;
|
||||
readonly cleanupFailures: Set<AnalysisError>;
|
||||
@@ -573,8 +566,17 @@ interface GdbSession {
|
||||
recentRecordsTruncated: boolean;
|
||||
}
|
||||
|
||||
const gdbConsoleUnavailable = (): AnalysisInputError =>
|
||||
new AnalysisInputError("gdb_console", undefined, [
|
||||
{
|
||||
path: ["session_id"],
|
||||
reason: "invalid_value",
|
||||
message: "GDB session is unavailable or closed.",
|
||||
},
|
||||
]);
|
||||
|
||||
const gdbCloseUnavailable = (): AnalysisInputError =>
|
||||
unknownDebuggerSessionError(GDB_SESSION_TOOLS, "close_gdb_session");
|
||||
new AnalysisInputError("close_gdb_session");
|
||||
|
||||
/** Owns persistent GDB/MI sessions independently from the active binary target. */
|
||||
export class GdbSessionManager {
|
||||
@@ -767,7 +769,7 @@ export class GdbSessionManager {
|
||||
"start_gdb_session",
|
||||
"memory",
|
||||
`GDB session capacity reached (${String(this.#maxSessions.value)} sessions).`,
|
||||
"Close a GDB session before starting another; a stopped session that gdb_session_status reports closed still holds its slot until close_gdb_session releases it.",
|
||||
"Close a GDB session before starting another.",
|
||||
this.#maxSessions,
|
||||
{
|
||||
max_sessions: this.#maxSessions.value,
|
||||
@@ -901,7 +903,6 @@ export class GdbSessionManager {
|
||||
// during startup, so no inferior can exist until execute() is called.
|
||||
noInferiorVerified: true,
|
||||
cleanupStarted: false,
|
||||
closeRequested: false,
|
||||
stopAttempt: undefined,
|
||||
pending: new Map(),
|
||||
cleanupFailures: new Set(),
|
||||
@@ -1255,13 +1256,15 @@ export class GdbSessionManager {
|
||||
>
|
||||
> {
|
||||
const session = this.#sessions.get(sessionId);
|
||||
if (session === undefined)
|
||||
return err(unknownDebuggerSessionError(GDB_SESSION_TOOLS, "gdb_console"));
|
||||
const stopped = this.#stopReason(session);
|
||||
if (stopped !== undefined)
|
||||
return err(
|
||||
stoppedDebuggerSessionError(GDB_SESSION_TOOLS, "gdb_console", stopped),
|
||||
);
|
||||
if (
|
||||
this.#closing ||
|
||||
session === undefined ||
|
||||
!session.ready ||
|
||||
session.cleanupStarted ||
|
||||
session.exited ||
|
||||
session.protocolError !== undefined
|
||||
)
|
||||
return err(gdbConsoleUnavailable());
|
||||
if (signal?.aborted)
|
||||
return err(cancelledWithCallerReason("gdb_console", signal));
|
||||
try {
|
||||
@@ -1270,16 +1273,14 @@ export class GdbSessionManager {
|
||||
async () => {
|
||||
// Retained cleanup ownership and a delayed exit event do not imply
|
||||
// command readiness. Recheck admission when this queued turn starts.
|
||||
// Every path that releases a session first records why it stopped.
|
||||
const stoppedWhileQueued = this.#stopReason(session);
|
||||
if (stoppedWhileQueued !== undefined)
|
||||
return err(
|
||||
stoppedDebuggerSessionError(
|
||||
GDB_SESSION_TOOLS,
|
||||
"gdb_console",
|
||||
stoppedWhileQueued,
|
||||
),
|
||||
);
|
||||
if (
|
||||
this.#closing ||
|
||||
this.#sessions.get(sessionId) !== session ||
|
||||
session.cleanupStarted ||
|
||||
session.exited ||
|
||||
session.protocolError !== undefined
|
||||
)
|
||||
return err(gdbConsoleUnavailable());
|
||||
session.noInferiorVerified = false;
|
||||
session.knownInferiorIdentities.clear();
|
||||
if (commandMayChangeInferiorOutput(command))
|
||||
@@ -1454,17 +1455,21 @@ export class GdbSessionManager {
|
||||
readonly diagnostics_truncated: boolean;
|
||||
readonly inferior_tty: string | null;
|
||||
readonly inferior_tty_verified: boolean;
|
||||
readonly closed_reason?: string;
|
||||
}
|
||||
| undefined {
|
||||
const session = this.#sessions.get(sessionId);
|
||||
if (session === undefined) return undefined;
|
||||
const diagnostics = session.supervisor.snapshot();
|
||||
const stopped = this.#stopReason(session);
|
||||
return {
|
||||
session_id: session.id,
|
||||
mi_version: session.miVersion,
|
||||
state: stopped === undefined ? "ready" : "closed",
|
||||
state:
|
||||
session.cleanupStarted ||
|
||||
session.exited ||
|
||||
session.protocolError !== undefined ||
|
||||
!session.ready
|
||||
? "closed"
|
||||
: "ready",
|
||||
recent_mi_records: [...session.lines],
|
||||
recent_mi_records_truncated: session.recentRecordsTruncated,
|
||||
mi_invalid_utf8: session.invalidUtf8Observed,
|
||||
@@ -1476,24 +1481,9 @@ export class GdbSessionManager {
|
||||
diagnostics_truncated: diagnostics.diagnosticTruncated === true,
|
||||
inferior_tty: session.inferiorTty ?? null,
|
||||
inferior_tty_verified: session.inferiorTtyVerified,
|
||||
...(stopped === undefined ? {} : { closed_reason: stopped }),
|
||||
};
|
||||
}
|
||||
|
||||
/** Why a retained session accepts no commands, or undefined while it is ready. */
|
||||
#stopReason(session: GdbSession): string | undefined {
|
||||
if (session.protocolError !== undefined) return session.protocolError;
|
||||
if (this.#closing)
|
||||
return "REA is shutting down and closing every GDB session.";
|
||||
if (session.closeRequested)
|
||||
return "close_gdb_session started releasing this session, and its owned cleanup has not completed.";
|
||||
if (session.exited)
|
||||
return `The GDB process exited (exit code ${String(session.exitCode)}, signal ${String(session.exitSignal)}); a command such as quit, or a crash, can end it.`;
|
||||
if (session.cleanupStarted || !session.ready)
|
||||
return "REA stopped this GDB session.";
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async close(
|
||||
sessionId: string,
|
||||
): Promise<
|
||||
@@ -1559,7 +1549,6 @@ export class GdbSessionManager {
|
||||
// Refusing inferior disposition above leaves console remediation
|
||||
// available; from here onward only owned cleanup may be retried.
|
||||
session.cleanupStarted = true;
|
||||
session.closeRequested = true;
|
||||
if (groupCount === 0) {
|
||||
session.noInferiorVerified = true;
|
||||
targetState = "none_observed";
|
||||
@@ -1599,7 +1588,6 @@ export class GdbSessionManager {
|
||||
async #finishSessionCleanup(
|
||||
session: GdbSession,
|
||||
): Promise<Result<{ readonly target_state: "unknown" }, AnalysisError>> {
|
||||
session.closeRequested = true;
|
||||
const stopped = await this.#stopDebuggerOnly(session);
|
||||
if (stopped.status === "incomplete")
|
||||
return err(
|
||||
@@ -1626,10 +1614,7 @@ export class GdbSessionManager {
|
||||
stopped.reason,
|
||||
),
|
||||
);
|
||||
// The caller still holds a published session's ID; keep it addressable
|
||||
// so commands and status report why it stopped until it is closed.
|
||||
if (!session.ready || session.closeRequested)
|
||||
this.#sessions.delete(session.id);
|
||||
this.#sessions.delete(session.id);
|
||||
}
|
||||
return ok(null);
|
||||
}
|
||||
@@ -2224,7 +2209,6 @@ export class GdbSessionManager {
|
||||
"The unrestricted GDB console can access local shell, filesystem, network, and target controls.",
|
||||
],
|
||||
});
|
||||
session.protocolError ??= `A GDB command exceeded its ${String(acceptedDeadline.value)} ms deadline; owned-session shutdown was requested because output correlation can no longer be guaranteed.`;
|
||||
void this.#stopDebuggerOnly(session).then((stopped) => {
|
||||
if (
|
||||
stopped.status === "incomplete" &&
|
||||
|
||||
@@ -25,7 +25,7 @@ export const rizinCommand = (input: RizinInput): readonly string[] => [
|
||||
export const rizinCommandLimitations = (
|
||||
input: RizinInput,
|
||||
): readonly string[] => [
|
||||
"completion_status covers process completion and Rizin shell refusals only. A statement that ran can still fail while Rizin returns exit_code 0, so inspect stderr before treating an empty response as absence.",
|
||||
"completion_status reports process completion. Command validity remains unverified: Rizin can reject a command while returning exit_code 0. Inspect stdout and stderr before treating an empty response as absence.",
|
||||
...(input.asm_arch === undefined &&
|
||||
input.asm_bits === undefined &&
|
||||
input.asm_cpu === undefined
|
||||
@@ -39,43 +39,3 @@ export const rizinCommandLimitations = (
|
||||
"With base_address, REA passed -n and -m so that Rizin maps file offset 0 at that address as a raw image; REA does not confirm the map, which oml shows. -n skips Rizin's format loaders, so expect no sections, symbols or entry point, and arch, bits and byte order stay at Rizin's defaults unless the call selects them.",
|
||||
]),
|
||||
];
|
||||
|
||||
/**
|
||||
* Rizin's shell logs exactly these lines when it refuses a statement
|
||||
* (librz/core/cmd/cmd.c, unchanged from 0.6 through 0.9.1). Rizin then skips
|
||||
* the remaining statements and still exits 0. Other ERROR lines come from
|
||||
* commands that ran, so they are diagnostics rather than refusals.
|
||||
*/
|
||||
const RIZIN_SHELL_REFUSALS: readonly {
|
||||
readonly line: RegExp;
|
||||
readonly refused: (match: RegExpExecArray) => string;
|
||||
}[] = [
|
||||
{
|
||||
line: /^ERROR: Command '(.*)' does not exist\.$/u,
|
||||
refused: (match) => `the unknown command ${JSON.stringify(match[1])}`,
|
||||
},
|
||||
{
|
||||
line: /^ERROR: Wrong number of arguments passed to `(.*)`, see its help with `.*\?`$/u,
|
||||
refused: (match) =>
|
||||
`a wrong argument count for ${JSON.stringify(match[1])}`,
|
||||
},
|
||||
{
|
||||
line: /^ERROR: core: Error while executing command: /u,
|
||||
refused: () => "an invalid statement",
|
||||
},
|
||||
{
|
||||
line: /^ERROR: core: Error while parsing command: /u,
|
||||
refused: () => "input that failed to parse",
|
||||
},
|
||||
];
|
||||
|
||||
/** Explain the first Rizin shell refusal in a completed process's stderr. */
|
||||
export const rizinCommandRejection = (stderr: string): string | undefined => {
|
||||
for (const text of stderr.split(/\r?\n/u))
|
||||
for (const { line, refused } of RIZIN_SHELL_REFUSALS) {
|
||||
const match = line.exec(text);
|
||||
if (match !== null)
|
||||
return `Rizin refused ${refused(match)} (ERROR line on stderr). It skips a refused statement and every later one, so stdout holds at most the output of earlier statements; empty stdout is not an empty result. Correct the statement, using <command>? for Rizin's help, and run it again.`;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
@@ -22,11 +22,6 @@ import {
|
||||
AnalysisInputError,
|
||||
AnalysisTimeoutError,
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import {
|
||||
RIZIN_DEBUG_SESSION_TOOLS,
|
||||
stoppedDebuggerSessionError,
|
||||
unknownDebuggerSessionError,
|
||||
} from "../domain/debuggerSessionErrors.js";
|
||||
import type {
|
||||
AnalysisError,
|
||||
AnalysisErrorOptions,
|
||||
@@ -229,18 +224,9 @@ interface RizinDebugSession {
|
||||
frameChunks: Buffer[];
|
||||
bufferedFrameBytes: number;
|
||||
ready: boolean;
|
||||
/** start returned this session's ID, so the caller must close it. */
|
||||
published: boolean;
|
||||
closeRequested: boolean;
|
||||
exited: boolean;
|
||||
commandTail: Promise<void>;
|
||||
protocolError: string | undefined;
|
||||
/**
|
||||
* Why the session stopped accepting commands. Cleanup synchronization
|
||||
* clears protocolError once the transport is correlated again, but the
|
||||
* session stays stopped, so the diagnosis is retained here.
|
||||
*/
|
||||
stopReason: string | undefined;
|
||||
cleanupSynchronization:
|
||||
| {
|
||||
readonly marker: string;
|
||||
@@ -378,7 +364,7 @@ export class RizinDebugSessionManager {
|
||||
"start_rizin_debug_session",
|
||||
"memory",
|
||||
`Rizin session capacity reached (${String(this.#maxSessions.value)} sessions).`,
|
||||
"Close a Rizin debug session before starting another; a stopped session that rizin_debug_session_status reports closed still holds its slot until close_rizin_debug_session releases it.",
|
||||
"Close a Rizin debug session before starting another.",
|
||||
this.#maxSessions,
|
||||
{
|
||||
max_sessions: this.#maxSessions.value,
|
||||
@@ -623,12 +609,9 @@ export class RizinDebugSessionManager {
|
||||
frameChunks: [],
|
||||
bufferedFrameBytes: 0,
|
||||
ready: false,
|
||||
published: false,
|
||||
closeRequested: false,
|
||||
exited: false,
|
||||
commandTail: Promise.resolve(),
|
||||
protocolError: undefined,
|
||||
stopReason: undefined,
|
||||
cleanupSynchronization: undefined,
|
||||
commandCaptureActive: false,
|
||||
commandOutputDropped: false,
|
||||
@@ -668,7 +651,6 @@ export class RizinDebugSessionManager {
|
||||
if (session.protocolError !== undefined)
|
||||
throw new Error(session.protocolError);
|
||||
session.ready = true;
|
||||
session.published = true;
|
||||
return ok({ session_id: id, backend: input.backend ?? null });
|
||||
} catch (caught) {
|
||||
const protocolFailure = session.protocolError;
|
||||
@@ -737,22 +719,15 @@ export class RizinDebugSessionManager {
|
||||
>
|
||||
> {
|
||||
const session = this.#sessions.get(sessionId);
|
||||
if (session === undefined)
|
||||
return err(
|
||||
unknownDebuggerSessionError(
|
||||
RIZIN_DEBUG_SESSION_TOOLS,
|
||||
"rizin_debug_command",
|
||||
),
|
||||
);
|
||||
const stopped = this.#stopReason(session);
|
||||
if (stopped !== undefined)
|
||||
return err(
|
||||
stoppedDebuggerSessionError(
|
||||
RIZIN_DEBUG_SESSION_TOOLS,
|
||||
"rizin_debug_command",
|
||||
stopped,
|
||||
),
|
||||
);
|
||||
if (
|
||||
session === undefined ||
|
||||
!session.ready ||
|
||||
session.exited ||
|
||||
session.protocolError !== undefined ||
|
||||
session.closing ||
|
||||
this.#closingAll
|
||||
)
|
||||
return err(new AnalysisInputError("rizin_debug_command"));
|
||||
if (signal?.aborted)
|
||||
return err(rizinCancellation("rizin_debug_command", signal));
|
||||
if (session.pendingCommandCount >= this.#maxPendingCommands.value)
|
||||
@@ -807,15 +782,14 @@ export class RizinDebugSessionManager {
|
||||
acquired = true;
|
||||
if (signal?.aborted)
|
||||
return err(rizinCancellation("rizin_debug_command", signal));
|
||||
const stoppedWhileQueued = this.#stopReason(session);
|
||||
if (stoppedWhileQueued !== undefined)
|
||||
return err(
|
||||
stoppedDebuggerSessionError(
|
||||
RIZIN_DEBUG_SESSION_TOOLS,
|
||||
"rizin_debug_command",
|
||||
stoppedWhileQueued,
|
||||
),
|
||||
);
|
||||
if (
|
||||
!session.ready ||
|
||||
session.exited ||
|
||||
session.closing ||
|
||||
this.#closingAll ||
|
||||
session.protocolError !== undefined
|
||||
)
|
||||
return err(new AnalysisInputError("rizin_debug_command"));
|
||||
if (hasUnclaimedRizinFrames(session)) {
|
||||
const message =
|
||||
"Unsolicited Rizin frames arrived before a command; command output can no longer be safely correlated.";
|
||||
@@ -1094,40 +1068,27 @@ export class RizinDebugSessionManager {
|
||||
readonly recent_output_truncated: boolean;
|
||||
readonly diagnostics_truncated: boolean;
|
||||
readonly output_invalid_utf8: boolean;
|
||||
readonly closed_reason?: string;
|
||||
}
|
||||
| undefined {
|
||||
const session = this.#sessions.get(sessionId);
|
||||
if (session === undefined) return undefined;
|
||||
const stopped = this.#stopReason(session);
|
||||
return {
|
||||
session_id: session.id,
|
||||
state: stopped === undefined ? "ready" : "closed",
|
||||
inferior_tty: session.inferiorTty,
|
||||
recent_output: [...session.history],
|
||||
recent_output_truncated: session.historyTruncated,
|
||||
diagnostics_truncated:
|
||||
session.supervisor.snapshot().diagnosticTruncated === true,
|
||||
output_invalid_utf8: session.invalidUtf8Observed,
|
||||
...(stopped === undefined ? {} : { closed_reason: stopped }),
|
||||
};
|
||||
}
|
||||
|
||||
/** Why a retained session accepts no commands, or undefined while it is ready. */
|
||||
#stopReason(session: RizinDebugSession): string | undefined {
|
||||
const diagnosed = session.stopReason ?? session.protocolError;
|
||||
if (diagnosed !== undefined) return diagnosed;
|
||||
if (this.#closingAll)
|
||||
return "REA is shutting down and closing every Rizin debug session.";
|
||||
if (session.closeRequested)
|
||||
return "close_rizin_debug_session started releasing this session, and its owned cleanup has not completed.";
|
||||
if (session.exited) {
|
||||
const { exitCode, signalCode } = session.launched.process;
|
||||
return `The Rizin process exited (exit code ${String(exitCode)}, signal ${String(signalCode)}); a command such as q, or a crash, can end it.`;
|
||||
}
|
||||
if (session.closing || !session.ready)
|
||||
return "REA stopped this Rizin debug session.";
|
||||
return undefined;
|
||||
return session === undefined
|
||||
? undefined
|
||||
: {
|
||||
session_id: session.id,
|
||||
state:
|
||||
session.exited ||
|
||||
session.protocolError !== undefined ||
|
||||
session.closing ||
|
||||
!session.ready
|
||||
? "closed"
|
||||
: "ready",
|
||||
inferior_tty: session.inferiorTty,
|
||||
recent_output: [...session.history],
|
||||
recent_output_truncated: session.historyTruncated,
|
||||
diagnostics_truncated:
|
||||
session.supervisor.snapshot().diagnosticTruncated === true,
|
||||
output_invalid_utf8: session.invalidUtf8Observed,
|
||||
};
|
||||
}
|
||||
|
||||
async close(
|
||||
@@ -1137,13 +1098,7 @@ export class RizinDebugSessionManager {
|
||||
return err(new AnalysisInputError("close_rizin_debug_session"));
|
||||
const session = this.#sessions.get(sessionId);
|
||||
if (session === undefined)
|
||||
return err(
|
||||
unknownDebuggerSessionError(
|
||||
RIZIN_DEBUG_SESSION_TOOLS,
|
||||
"close_rizin_debug_session",
|
||||
),
|
||||
);
|
||||
session.closeRequested = true;
|
||||
return err(new AnalysisInputError("close_rizin_debug_session"));
|
||||
session.closing = true;
|
||||
const stopped = await this.#stopDebuggerOnly(session);
|
||||
if (stopped.status === "incomplete")
|
||||
@@ -1186,10 +1141,7 @@ export class RizinDebugSessionManager {
|
||||
stopped.reason,
|
||||
),
|
||||
);
|
||||
// The caller still holds a published session's ID; keep it addressable
|
||||
// so commands and status report why it stopped until it is closed.
|
||||
if (!session.published || session.closeRequested)
|
||||
this.#sessions.delete(session.id);
|
||||
this.#sessions.delete(session.id);
|
||||
}
|
||||
return ok(null);
|
||||
}
|
||||
@@ -1564,7 +1516,6 @@ export class RizinDebugSessionManager {
|
||||
clearTimeout(timer);
|
||||
session.cleanupSynchronization = undefined;
|
||||
if (cause === undefined) {
|
||||
session.stopReason ??= session.protocolError;
|
||||
session.protocolError = undefined;
|
||||
session.commandOutputDropped = false;
|
||||
resolve();
|
||||
|
||||
@@ -2,10 +2,6 @@ import type { McpServer, ServerContext } from "@modelcontextprotocol/server";
|
||||
import type { Logger } from "pino";
|
||||
|
||||
import type { GdbSessionManager } from "../gdb/GdbSessionManager.js";
|
||||
import {
|
||||
GDB_SESSION_TOOLS,
|
||||
unknownDebuggerSessionError,
|
||||
} from "../domain/debuggerSessionErrors.js";
|
||||
import type { EvidenceWriter } from "../application/investigation/InvestigationRecordPort.js";
|
||||
import type { EvidenceLookup } from "../application/EvidenceInputResolver.js";
|
||||
import {
|
||||
@@ -14,6 +10,7 @@ import {
|
||||
} from "../application/analysisView/AnalysisViewService.js";
|
||||
import { toolContract } from "../contracts/toolContracts.js";
|
||||
import { err, ok } from "../domain/result.js";
|
||||
import { AnalysisInputError } from "../domain/analysisErrorCore.js";
|
||||
import {
|
||||
runOwnedCleanupWithExternalMutationAdmission,
|
||||
runWithExternalMutationAdmission,
|
||||
@@ -120,12 +117,7 @@ export const registerGdbTools = (
|
||||
const result = manager.status(input.session_id);
|
||||
if (result === undefined)
|
||||
return delivery.toCallToolResult(
|
||||
err(
|
||||
unknownDebuggerSessionError(
|
||||
GDB_SESSION_TOOLS,
|
||||
"gdb_session_status",
|
||||
),
|
||||
),
|
||||
err(invalidSession("gdb_session_status")),
|
||||
status,
|
||||
);
|
||||
return delivery.toCallToolResult(
|
||||
@@ -158,3 +150,6 @@ export const registerGdbTools = (
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const invalidSession = (operation: string): AnalysisInputError =>
|
||||
new AnalysisInputError(operation);
|
||||
@@ -3,10 +3,7 @@ import type { Logger } from "pino";
|
||||
|
||||
import type { EvidenceWriter } from "../application/investigation/InvestigationRecordPort.js";
|
||||
import { toolContract } from "../contracts/toolContracts.js";
|
||||
import {
|
||||
RIZIN_DEBUG_SESSION_TOOLS,
|
||||
unknownDebuggerSessionError,
|
||||
} from "../domain/debuggerSessionErrors.js";
|
||||
import { AnalysisInputError } from "../domain/analysisErrorCore.js";
|
||||
import type { RizinDebugSessionManager } from "../rizin/RizinDebugSessionManager.js";
|
||||
import {
|
||||
runOwnedCleanupWithExternalMutationAdmission,
|
||||
@@ -102,13 +99,7 @@ export const registerRizinDebugTools = (
|
||||
const result = manager.status(input.session_id);
|
||||
if (result === undefined)
|
||||
return delivery.toCallToolResult(
|
||||
{
|
||||
ok: false,
|
||||
error: unknownDebuggerSessionError(
|
||||
RIZIN_DEBUG_SESSION_TOOLS,
|
||||
status.name,
|
||||
),
|
||||
},
|
||||
{ ok: false, error: new AnalysisInputError(status.name) },
|
||||
status,
|
||||
);
|
||||
return delivery.toCallToolResult(
|
||||
|
||||
@@ -12,7 +12,7 @@ import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
||||
import { cliTest, type TestCli } from "../support/cli/cliFixture.js";
|
||||
|
||||
const inspectBoth = async (cli: TestCli, path: string) => {
|
||||
const output = await cli.sharedCliRun!({
|
||||
const output = await cli.run({
|
||||
arguments: [
|
||||
"inspect-apk-signature",
|
||||
path,
|
||||
|
||||
@@ -6,7 +6,7 @@ import { expect } from "vitest";
|
||||
import { toolContract } from "../../../src/contracts/toolContracts.js";
|
||||
import { connectLocalToolsMcp } from "../../fixtures/localToolsMcp.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { cliTest, runFileCli } from "../../support/cli/cliFixture.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
const SERIAL = "listing-numbers-device";
|
||||
const UNSAFE = "9007199254740993";
|
||||
@@ -224,9 +224,10 @@ process.stdout.write(require('node:fs').readFileSync(path));
|
||||
arguments: [operation.replaceAll("_", "-"), SERIAL, "--json"],
|
||||
environment,
|
||||
};
|
||||
const output = await (variant === "safe"
|
||||
? cli.run(invocation)
|
||||
: runFileCli(cli, invocation));
|
||||
const output = await cli.run({
|
||||
...invocation,
|
||||
freshProcess: variant === "safe",
|
||||
});
|
||||
expect(output.exitCode, output.stderr).toBe(0);
|
||||
const evidence = contract.outputSchema.parse(output.json);
|
||||
expect(evidence.parameters).toMatchObject({ serial: SERIAL });
|
||||
|
||||
@@ -9,7 +9,7 @@ import { adbDisplayPartialObservationSchema } from "../../../src/domain/adb/adbD
|
||||
import { connectLocalToolsMcp } from "../../fixtures/localToolsMcp.js";
|
||||
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { cliTest, runFileCli } from "../../support/cli/cliFixture.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
const SERIAL = "numeric-declarations-device";
|
||||
const PACKAGE = "com.example.numeric";
|
||||
@@ -224,9 +224,10 @@ cliTest.skipIf(process.platform === "win32")(
|
||||
arguments: ["inspect-adb-display", SERIAL, "--json"],
|
||||
environment: fixture.environment,
|
||||
};
|
||||
const output = await (scenario === displayScenarios[0]
|
||||
? cli.run(invocation)
|
||||
: runFileCli(cli, invocation));
|
||||
const output = await cli.run({
|
||||
...invocation,
|
||||
freshProcess: scenario === displayScenarios[0],
|
||||
});
|
||||
expect(output.exitCode, `${scenario.name}: ${output.stderr}`).toBe(0);
|
||||
const evidence = contract.outputSchema.parse(output.json);
|
||||
expect(evidence.parameters).toEqual({ serial: SERIAL });
|
||||
@@ -281,9 +282,10 @@ cliTest.skipIf(process.platform === "win32")(
|
||||
arguments: ["inspect-adb-display", SERIAL, "--json"],
|
||||
environment: fixture.environment,
|
||||
};
|
||||
const output = await (value === null
|
||||
? cli.run(invocation)
|
||||
: runFileCli(cli, invocation));
|
||||
const output = await cli.run({
|
||||
...invocation,
|
||||
freshProcess: value === null,
|
||||
});
|
||||
expect(output.exitCode, output.stderr).toBe(1);
|
||||
const projected = analysisErrorProjectionSchema.parse(output.json);
|
||||
expect(projected.code).toBe("unreadable_output");
|
||||
@@ -454,9 +456,10 @@ cliTest.skipIf(process.platform === "win32")(
|
||||
arguments: ["inspect-adb-package", SERIAL, PACKAGE, "--json"],
|
||||
environment: fixture.environment,
|
||||
};
|
||||
const output = await (scenario === packageScenarios[0]
|
||||
? cli.run(invocation)
|
||||
: runFileCli(cli, invocation));
|
||||
const output = await cli.run({
|
||||
...invocation,
|
||||
freshProcess: scenario === packageScenarios[0],
|
||||
});
|
||||
expect(output.exitCode, `${scenario.name}: ${output.stderr}`).toBe(0);
|
||||
const evidence = contract.outputSchema.parse(output.json);
|
||||
expect(evidence.parameters).toEqual({ serial: SERIAL, package: PACKAGE });
|
||||
|
||||
@@ -8,7 +8,7 @@ import { toolContract } from "../../../src/contracts/toolContracts.js";
|
||||
import { parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { connectLocalToolsMcp } from "../../fixtures/localToolsMcp.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { cliTest, runFileCli } from "../../support/cli/cliFixture.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
const PACKAGE_LINE = "package:/data/app/example/base.apk=com.example.app";
|
||||
|
||||
@@ -139,9 +139,10 @@ process.exit(91);
|
||||
arguments: [command, "emulator-5554", ...tail, "--json"],
|
||||
environment,
|
||||
};
|
||||
const output = await (count === 0
|
||||
? cli.run(invocation)
|
||||
: runFileCli(cli, invocation));
|
||||
const output = await cli.run({
|
||||
...invocation,
|
||||
freshProcess: count === 0,
|
||||
});
|
||||
expect(output.exitCode, output.stderr).toBe(0);
|
||||
const evidence = parseEvidence(output.json);
|
||||
expect(
|
||||
|
||||
@@ -131,7 +131,7 @@ const checkCliPublication = async (
|
||||
},
|
||||
) => {
|
||||
const { root, path, parent, summary, selected } = context;
|
||||
const run = cli.sharedCliRun?.bind(cli) ?? cli.run.bind(cli);
|
||||
const run = cli.run.bind(cli);
|
||||
const complete = await run({
|
||||
arguments: ["inspect-api-contract", path, "--json"],
|
||||
});
|
||||
@@ -395,7 +395,7 @@ cliTest(
|
||||
coverage: { total: 3 },
|
||||
source_coverage: { status: "partial" },
|
||||
});
|
||||
const run = cli.sharedCliRun?.bind(cli) ?? cli.run.bind(cli);
|
||||
const run = cli.run.bind(cli);
|
||||
const cliResult = await run({
|
||||
arguments: ["inspect-api-contract", path, "--json"],
|
||||
});
|
||||
@@ -442,7 +442,7 @@ cliTest(
|
||||
details: { partial_observation: snapshot },
|
||||
});
|
||||
expect(JSON.stringify(response)).not.toContain("u:p@h");
|
||||
const run = cli.sharedCliRun?.bind(cli) ?? cli.run.bind(cli);
|
||||
const run = cli.run.bind(cli);
|
||||
const result = await run({
|
||||
arguments: ["inspect-api-contract", path, "--json"],
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import { parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { javascriptApplicationAnalysisResultSchema } from "../../../src/domain/javascript/javascriptApplicationAnalysis.js";
|
||||
import { applicationVersionComparisonResultSchema } from "../../../src/domain/javascript/javascriptApplicationVersionComparisonSchemas.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { cliTest, runFileCli } from "../../support/cli/cliFixture.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
cliTest(
|
||||
"keeps unavailable source-selection scopes unknown in version comparisons",
|
||||
@@ -72,7 +72,7 @@ cliTest(
|
||||
) => {
|
||||
const inputPath = join(root, `${name}.json`);
|
||||
await writeFile(inputPath, JSON.stringify({ left, right }));
|
||||
const response = await runFileCli(cli, {
|
||||
const response = await cli.run({
|
||||
arguments: ["compare-application-versions", inputPath, "--json"],
|
||||
environment: { REA_LOG_LEVEL: "silent" },
|
||||
});
|
||||
@@ -492,7 +492,7 @@ cliTest(
|
||||
const compare = async (left: unknown, right: unknown) => {
|
||||
const inputPath = join(root, `comparison-${Math.random()}.json`);
|
||||
await writeFile(inputPath, JSON.stringify({ left, right }));
|
||||
const response = await runFileCli(cli, {
|
||||
const response = await cli.run({
|
||||
arguments: ["compare-application-versions", inputPath, "--json"],
|
||||
environment: { REA_LOG_LEVEL: "silent" },
|
||||
});
|
||||
|
||||
@@ -107,7 +107,7 @@ cliTest(
|
||||
const root = await createTestTempDirectory("rea-beam-views-");
|
||||
const path = join(root, "app.beam");
|
||||
await writeFile(path, erlangBeamFixture("views"));
|
||||
const run = cli.sharedCliRun?.bind(cli) ?? cli.run.bind(cli);
|
||||
const run = cli.run.bind(cli);
|
||||
const complete = await run({
|
||||
arguments: ["inspect-erlang-beam", path, "--json"],
|
||||
});
|
||||
@@ -312,7 +312,7 @@ cliTest(
|
||||
async ({ cli }) => {
|
||||
const root = await createTestTempDirectory("rea-beam-invalid-view-");
|
||||
const output = join(root, "unused.json");
|
||||
const run = cli.sharedCliRun?.bind(cli) ?? cli.run.bind(cli);
|
||||
const run = cli.run.bind(cli);
|
||||
const view = JSON.stringify({ kind: "beam", collection: "exports" });
|
||||
for (const [flags, key] of [
|
||||
[["--detail", "summary"], "evidence_output"],
|
||||
|
||||
@@ -137,7 +137,7 @@ cliTest(
|
||||
},
|
||||
}),
|
||||
);
|
||||
const run = cli.sharedCliRun?.bind(cli) ?? cli.run.bind(cli);
|
||||
const run = cli.run.bind(cli);
|
||||
const result = await run({
|
||||
arguments: ["compare-artifacts", requestPath, "--json"],
|
||||
});
|
||||
|
||||
@@ -35,7 +35,7 @@ const checkCliPublication = async (
|
||||
},
|
||||
) => {
|
||||
const { root, parent, selected } = context;
|
||||
const run = cli.sharedCliRun?.bind(cli) ?? cli.run.bind(cli);
|
||||
const run = cli.run.bind(cli);
|
||||
const args = [
|
||||
"inspect-api-contract",
|
||||
"./SDL 雪.graphql",
|
||||
@@ -271,7 +271,7 @@ cliTest(
|
||||
},
|
||||
}),
|
||||
);
|
||||
const run = cli.sharedCliRun?.bind(cli) ?? cli.run.bind(cli);
|
||||
const run = cli.run.bind(cli);
|
||||
const offline = await run({
|
||||
arguments: ["inspect-analysis-view", queryPath, "--json"],
|
||||
});
|
||||
@@ -339,7 +339,7 @@ cliTest(
|
||||
source_coverage: { status: "partial", operations: "unavailable" },
|
||||
},
|
||||
});
|
||||
const run = cli.sharedCliRun?.bind(cli) ?? cli.run.bind(cli);
|
||||
const run = cli.run.bind(cli);
|
||||
const cliResult = await run({
|
||||
arguments: [
|
||||
"inspect-api-contract",
|
||||
@@ -392,7 +392,7 @@ cliTest(
|
||||
items: [{ code: "schema-parse-refused", source: { role: "schema" } }],
|
||||
});
|
||||
await writeFile(path, text);
|
||||
const run = cli.sharedCliRun?.bind(cli) ?? cli.run.bind(cli);
|
||||
const run = cli.run.bind(cli);
|
||||
const result = await run({
|
||||
arguments: [
|
||||
"inspect-api-contract",
|
||||
|
||||
@@ -9,11 +9,7 @@ import { interfaceBuilderAnalysisSchema } from "../../../src/domain/apple/interf
|
||||
import { thinMach } from "../../../src/domain/binaryTarget.fixture.js";
|
||||
import { parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import {
|
||||
cliTest,
|
||||
runFileCli,
|
||||
type TestCli,
|
||||
} from "../../support/cli/cliFixture.js";
|
||||
import { cliTest, type TestCli } from "../../support/cli/cliFixture.js";
|
||||
|
||||
const unknownMixedInteger = {
|
||||
$plist_type: "unknown",
|
||||
@@ -39,7 +35,7 @@ const inspectArchive = (cli: TestCli, app: string, fresh: boolean) => {
|
||||
arguments: ["decode-interface-builder", app, "--json"],
|
||||
environment: { REA_LOG_LEVEL: "silent", REA_ANALYSIS_PROVIDER: "auto" },
|
||||
};
|
||||
return fresh ? cli.run(invocation) : runFileCli(cli, invocation);
|
||||
return cli.run({ ...invocation, freshProcess: fresh });
|
||||
};
|
||||
|
||||
cliTest.for(scenarios)(
|
||||
|
||||
@@ -6,7 +6,7 @@ import { expect } from "vitest";
|
||||
import { analysisViewResultSchema } from "../../../src/domain/analysisView/analysisView.js";
|
||||
import { parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { cliTest, runFileCli } from "../../support/cli/cliFixture.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
cliTest(
|
||||
"pages only module units for a CommonJS file that exports symbols",
|
||||
@@ -23,7 +23,7 @@ cliTest(
|
||||
"exports.beta = function beta() { return 2; };",
|
||||
].join("\n"),
|
||||
);
|
||||
const analyzed = await runFileCli(cli, {
|
||||
const analyzed = await cli.run({
|
||||
arguments: ["analyze-javascript-application", root, "--format", "json"],
|
||||
});
|
||||
expect(analyzed.exitCode, analyzed.stdout + analyzed.stderr).toBe(0);
|
||||
@@ -54,7 +54,7 @@ cliTest(
|
||||
traceInput,
|
||||
JSON.stringify({ application: evidence, seed }),
|
||||
);
|
||||
const traced = await runFileCli(cli, {
|
||||
const traced = await cli.run({
|
||||
arguments: ["trace-application-feature", traceInput, "--json"],
|
||||
});
|
||||
expect(traced.exitCode, traced.stdout + traced.stderr).toBe(0);
|
||||
@@ -86,7 +86,7 @@ cliTest(
|
||||
view,
|
||||
}),
|
||||
);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-analysis-view", queryPath, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stdout + result.stderr).toBe(0);
|
||||
|
||||
@@ -13,7 +13,6 @@ import { findApplicationFeatureSeeds } from "../../../src/domain/javascript/java
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import {
|
||||
cliTest,
|
||||
runFileCli,
|
||||
type SharedMcpCall,
|
||||
type TestCli,
|
||||
} from "../../support/cli/cliFixture.js";
|
||||
@@ -170,7 +169,7 @@ const traceThroughCli = async (
|
||||
const invocation = {
|
||||
arguments: ["trace-application-feature", input, "--json"],
|
||||
};
|
||||
const response = await runFileCli(cli, invocation);
|
||||
const response = await cli.run(invocation);
|
||||
expect(response.exitCode, `${scenario.name} via CLI`).toBe(0);
|
||||
const evidence = toolContract("trace_application_feature").outputSchema.parse(
|
||||
response.json,
|
||||
|
||||
@@ -8,7 +8,7 @@ import { afterAll, expect } from "vitest";
|
||||
import { parse as parseYaml } from "yaml";
|
||||
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { cliTest, runFileCli } from "../../support/cli/cliFixture.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
afterAll(() => disposeMdItInstance());
|
||||
|
||||
@@ -45,9 +45,10 @@ cliTest.for([
|
||||
const invocation = {
|
||||
arguments: ["inspect-keyed-archive", path, ...args],
|
||||
};
|
||||
return label === "high" || label === "pair"
|
||||
? cli.run(invocation)
|
||||
: runFileCli(cli, invocation);
|
||||
return cli.run({
|
||||
...invocation,
|
||||
freshProcess: label === "high" || label === "pair",
|
||||
});
|
||||
};
|
||||
for (const format of ["json", "yaml"] as const) {
|
||||
const output = await run(["--format", format]);
|
||||
@@ -120,9 +121,7 @@ cliTest(
|
||||
const invocation = {
|
||||
arguments: ["inspect-keyed-archive", path, ...args],
|
||||
};
|
||||
return label === "nul"
|
||||
? cli.run(invocation)
|
||||
: runFileCli(cli, invocation);
|
||||
return cli.run({ ...invocation, freshProcess: label === "nul" });
|
||||
};
|
||||
const json = await run(["--json"]);
|
||||
expect(json.exitCode, json.stderr).toBe(0);
|
||||
|
||||
@@ -8,7 +8,7 @@ import { expect } from "vitest";
|
||||
import { keyedArchiveResultSchema } from "../../../src/domain/apple/keyedArchive.js";
|
||||
import { parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { cliTest, runFileCli } from "../../support/cli/cliFixture.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
cliTest.for([
|
||||
{ name: "plain real", element: "<real>1.25</real>", expected: 1.25 },
|
||||
@@ -74,7 +74,7 @@ cliTest.for([
|
||||
await writeFile(path, bytes);
|
||||
const call = await cli.sharedMcpCall?.();
|
||||
if (call === undefined) throw new Error("Missing file-scoped MCP session");
|
||||
const output = await runFileCli(cli, {
|
||||
const output = await cli.run({
|
||||
arguments: [
|
||||
"inspect-keyed-archive",
|
||||
path,
|
||||
@@ -155,7 +155,7 @@ cliTest(
|
||||
}).replace("<string>__VALUE__</string>", element),
|
||||
);
|
||||
await writeFile(path, bytes);
|
||||
const output = await runFileCli(cli, {
|
||||
const output = await cli.run({
|
||||
arguments: ["inspect-keyed-archive", path, "--json"],
|
||||
environment: { REA_LOG_LEVEL: "silent" },
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ import { parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { harmonyApplicationProjectionResultSchema } from "../../../src/domain/harmony/harmonyApplication.js";
|
||||
import { connectLocalToolsMcp } from "../../fixtures/localToolsMcp.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { cliTest, runFileCli } from "../../support/cli/cliFixture.js";
|
||||
import { cliTest, type CliInvocation } from "../../support/cli/cliFixture.js";
|
||||
|
||||
const harmony = {
|
||||
family: "HarmonyOS",
|
||||
@@ -146,10 +146,8 @@ cliTest.for(fixtures)(
|
||||
const { call } = await connectLocalToolsMcp(environment);
|
||||
for (const mode of ["directories", "files", "nested"] as const) {
|
||||
// Keep one fresh dispatcher check per family; reuse later commands.
|
||||
const run =
|
||||
mode === "directories"
|
||||
? cli.run.bind(cli)
|
||||
: runFileCli.bind(undefined, cli);
|
||||
const run = (invocation: CliInvocation) =>
|
||||
cli.run({ ...invocation, freshProcess: mode === "directories" });
|
||||
const writer = new ZipWriter(new Uint8ArrayWriter());
|
||||
for (const path of fixture.paths) {
|
||||
if (mode !== "files")
|
||||
@@ -324,9 +322,8 @@ cliTest.for([
|
||||
const environment = { HOME: root, USERPROFILE: root };
|
||||
const { call } = await connectLocalToolsMcp(environment);
|
||||
for (const encrypted of [false, true]) {
|
||||
const run = encrypted
|
||||
? runFileCli.bind(undefined, cli)
|
||||
: cli.run.bind(cli);
|
||||
const run = (invocation: CliInvocation) =>
|
||||
cli.run({ ...invocation, freshProcess: !encrypted });
|
||||
const writer = new ZipWriter(new Uint8ArrayWriter());
|
||||
for (const member of fixture.paths)
|
||||
await writer.add(
|
||||
|
||||
@@ -40,7 +40,7 @@ cliTest.for(["pe", "elf"] as const)(
|
||||
versionDefinitions: ["fixture", "V1"],
|
||||
});
|
||||
await writeFile(path, bytes);
|
||||
const run = cli.sharedCliRun?.bind(cli) ?? cli.run.bind(cli);
|
||||
const run = cli.run.bind(cli);
|
||||
const complete = await run({
|
||||
arguments: ["inspect-native-exports", path, "--json"],
|
||||
});
|
||||
|
||||
@@ -9,11 +9,7 @@ import { peResourcesSchema } from "../../../src/domain/native/peResources.js";
|
||||
import { peResourceFixture } from "../../../src/native/pe/PeResources.fixture.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
|
||||
import {
|
||||
cliTest,
|
||||
runFileCli,
|
||||
type TestCli,
|
||||
} from "../../support/cli/cliFixture.js";
|
||||
import { cliTest, type TestCli } from "../../support/cli/cliFixture.js";
|
||||
|
||||
type PeResourceCall = (
|
||||
arguments_: Record<string, unknown>,
|
||||
@@ -26,7 +22,7 @@ const expectPeInputFailureAcrossAdapters = async (input: {
|
||||
readonly reason: "invalid_format" | "invalid_value";
|
||||
}) => {
|
||||
const { cli, call, path, reason } = input;
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-pe-resources", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
@@ -117,7 +113,7 @@ const expectPeWordAlignedRecordsAcrossAdapters = async (input: {
|
||||
);
|
||||
const path = join(root, `word-aligned-${plus}-${index}.exe`);
|
||||
await writeFile(path, fixture.bytes);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-pe-resources", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
@@ -208,7 +204,7 @@ const expectPeMappingOwnershipAcrossAdapters = async (
|
||||
});
|
||||
continue;
|
||||
}
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-pe-resources", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, name).toBe(0);
|
||||
@@ -266,9 +262,7 @@ const expectPeInventoriesAcrossAdapters = async (
|
||||
};
|
||||
// Keep an independent public-wrapper process for both PE32 and PE32+.
|
||||
// The empty-payload variants reuse only compiled-command startup.
|
||||
const result = await (empty
|
||||
? runFileCli(cli, invocation)
|
||||
: cli.run(invocation));
|
||||
const result = await (empty ? cli.run(invocation) : cli.run(invocation));
|
||||
expect(result.exitCode).toBe(0);
|
||||
const evidence = contract.outputSchema.parse(result.json);
|
||||
const report = peResourcesSchema.parse(evidence.normalized_result);
|
||||
@@ -348,7 +342,7 @@ const expectPeEmptyBranchesAcrossAdapters = async (
|
||||
shared ? "shared-empty64.exe" : "empty-branches32.exe",
|
||||
);
|
||||
await writeFile(path, fixture.bytes);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-pe-resources", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
|
||||
@@ -73,7 +73,7 @@ cliTest(
|
||||
(await call("inspect_playwright_trace", { path: fixture.path, view }))
|
||||
.structuredContent,
|
||||
).toEqual(selected);
|
||||
const run = cli.sharedCliRun?.bind(cli) ?? cli.run.bind(cli);
|
||||
const run = cli.run.bind(cli);
|
||||
const result = await run({
|
||||
arguments: ["inspect-playwright-trace", fixture.path, "--json"],
|
||||
});
|
||||
@@ -102,7 +102,7 @@ const checkCliPublication = async ({
|
||||
call: McpCall;
|
||||
}) => {
|
||||
const source = { kind: "retained-evidence", evidence_id: parent.evidence_id };
|
||||
const run = cli.sharedCliRun?.bind(cli) ?? cli.run.bind(cli);
|
||||
const run = cli.run.bind(cli);
|
||||
const fullCli = await run({
|
||||
arguments: ["inspect-playwright-trace", fixture.path, "--json"],
|
||||
});
|
||||
@@ -420,7 +420,7 @@ cliTest(
|
||||
expect(
|
||||
parseEvidence(full.structuredContent).normalized_result,
|
||||
).toMatchObject({ items: [{ text: message }] });
|
||||
const run = cli.sharedCliRun?.bind(cli) ?? cli.run.bind(cli);
|
||||
const run = cli.run.bind(cli);
|
||||
const result = await run({
|
||||
arguments: ["inspect-playwright-trace", fixture.path, "--json"],
|
||||
});
|
||||
|
||||
@@ -74,7 +74,7 @@ cliTest(
|
||||
const root = await createTestTempDirectory("rea-pyc-views-");
|
||||
const path = join(root, "module.pyc");
|
||||
await writeSameNamePyc(path);
|
||||
const run = cli.sharedCliRun?.bind(cli) ?? cli.run.bind(cli);
|
||||
const run = cli.run.bind(cli);
|
||||
const complete = await run({
|
||||
arguments: ["inspect-python-pyc", path, "--json"],
|
||||
});
|
||||
@@ -222,7 +222,7 @@ cliTest(
|
||||
async ({ cli }) => {
|
||||
const root = await createTestTempDirectory("rea-pyc-invalid-delivery-");
|
||||
const output = join(root, "unused.json");
|
||||
const run = cli.sharedCliRun?.bind(cli) ?? cli.run.bind(cli);
|
||||
const run = cli.run.bind(cli);
|
||||
const view = JSON.stringify({
|
||||
kind: "python-pyc",
|
||||
collection: "code_objects",
|
||||
|
||||
@@ -47,7 +47,7 @@ const prepareInputs = async (cli: TestCli) => {
|
||||
"f0e7d2c869c811d4272a7b2916c6531c8c6cc7938336a35575e2a6b7b6346714",
|
||||
);
|
||||
await writeFile(harPath, harBytes);
|
||||
const run = cli.sharedCliRun?.bind(cli) ?? cli.run.bind(cli);
|
||||
const run = cli.run.bind(cli);
|
||||
const analyzed = await run({
|
||||
arguments: [
|
||||
"analyze-javascript-application",
|
||||
|
||||
@@ -2,7 +2,6 @@ import { estimateTokenCount } from "tokenx";
|
||||
import { expect } from "vitest";
|
||||
|
||||
import { workspaceCliTest } from "../support/cli/workspaceCliFixture.js";
|
||||
import { runFileCli } from "../support/cli/cliFixture.js";
|
||||
|
||||
workspaceCliTest(
|
||||
"applies token controls to SDK manifest and schema documents",
|
||||
@@ -12,12 +11,15 @@ workspaceCliTest(
|
||||
const freshTokenCount = arguments_.join(" ") === "--llms --token-count";
|
||||
const freshTokenLimit =
|
||||
arguments_.join(" ") === "--llms-full --token-limit 1";
|
||||
const result = await (!freshTokenCount &&
|
||||
!freshTokenLimit &&
|
||||
(arguments_.includes("--token-count") ||
|
||||
arguments_.includes("--token-limit"))
|
||||
? runFileCli(cli, invocation)
|
||||
: cli.run(invocation));
|
||||
const result = await cli.run({
|
||||
...invocation,
|
||||
freshProcess: !(
|
||||
!freshTokenCount &&
|
||||
!freshTokenLimit &&
|
||||
(arguments_.includes("--token-count") ||
|
||||
arguments_.includes("--token-limit"))
|
||||
),
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
expect(result.stderr).toBe("");
|
||||
return result.stdout;
|
||||
|
||||
@@ -36,7 +36,7 @@ cliTest.for([
|
||||
bytes[8] = version;
|
||||
await writeFile(path, bytes);
|
||||
const hash = createHash("sha256").update(bytes).digest("hex");
|
||||
const output = await cli.sharedCliRun!({
|
||||
const output = await cli.run({
|
||||
arguments: ["inspect-elf-notes", path, "--json"],
|
||||
});
|
||||
expect(output.exitCode, output.stderr).toBe(0);
|
||||
|
||||
@@ -51,7 +51,7 @@ for (const { bits, ownership, machine } of [
|
||||
});
|
||||
await writeFile(path, bytes);
|
||||
|
||||
const output = await cli.sharedCliRun!({
|
||||
const output = await cli.run({
|
||||
arguments: ["inspect-elf-notes", path, "--json"],
|
||||
});
|
||||
expect(output.exitCode, output.stderr).toBe(0);
|
||||
|
||||
@@ -65,7 +65,7 @@ for (const target of [
|
||||
}),
|
||||
);
|
||||
|
||||
const cliOut = await cli.sharedCliRun!({
|
||||
const cliOut = await cli.run({
|
||||
arguments: ["inspect-elf-notes", path, "--json"],
|
||||
});
|
||||
expect(cliOut.exitCode, cliOut.stderr).toBe(0);
|
||||
|
||||
@@ -152,7 +152,7 @@ cliTest(
|
||||
});
|
||||
await writeFile(path, bytes);
|
||||
|
||||
const output = await cli.sharedCliRun!({
|
||||
const output = await cli.run({
|
||||
arguments: ["inspect-elf-notes", path, "--json"],
|
||||
});
|
||||
expect(output.exitCode, output.stderr).toBe(0);
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
} from "../fixtures/go/image.js";
|
||||
import { createGoRodataStringFixture } from "../fixtures/go/rodataStringImage.js";
|
||||
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
||||
import { cliTest, runFileCli } from "../support/cli/cliFixture.js";
|
||||
import { cliTest } from "../support/cli/cliFixture.js";
|
||||
import { parseEvidence } from "../../src/domain/evidence.js";
|
||||
import { connectLocalToolsMcp } from "../fixtures/localToolsMcp.js";
|
||||
import { expectCliMcpEvidenceParity } from "../fixtures/cliMcpEvidenceParity.js";
|
||||
@@ -47,7 +47,7 @@ cliTest(
|
||||
] as const) {
|
||||
const path = join(root, `${item.name}.macho`);
|
||||
await writeFile(path, item.bytes);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-go-binary", path, "--json"],
|
||||
});
|
||||
const response = await call("inspect_go_binary", { path });
|
||||
@@ -130,7 +130,7 @@ cliTest(
|
||||
);
|
||||
const path = join(root, `${item.name}.elf`);
|
||||
await writeFile(path, item.fixture.bytes);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-go-binary", path, "--json"],
|
||||
});
|
||||
const response = await call("inspect_go_binary", { path });
|
||||
@@ -199,12 +199,12 @@ cliTest(
|
||||
const path = join(root, "application.elf");
|
||||
const output = join(root, "source.json");
|
||||
await writeFile(path, createGoBinaryFixture().bytes);
|
||||
const complete = await runFileCli(cli, {
|
||||
const complete = await cli.run({
|
||||
arguments: ["inspect-go-binary", path, "--json"],
|
||||
});
|
||||
expect(complete.exitCode, complete.stderr).toBe(0);
|
||||
const full = evidenceResultOf(goBinarySchema).parse(complete.json);
|
||||
const summary = await runFileCli(cli, {
|
||||
const summary = await cli.run({
|
||||
arguments: [
|
||||
"inspect-go-binary",
|
||||
path,
|
||||
@@ -233,7 +233,7 @@ cliTest(
|
||||
.map((record) => parseEvidence(record))
|
||||
.find((record) => record.evidence_id === full.evidence_id),
|
||||
).toEqual(full);
|
||||
const refused = await runFileCli(cli, {
|
||||
const refused = await cli.run({
|
||||
arguments: [
|
||||
"inspect-go-binary",
|
||||
join(root, "missing"),
|
||||
@@ -256,7 +256,7 @@ cliTest(
|
||||
const root = await createTestTempDirectory("rea-go-overlap-");
|
||||
const path = join(root, "overlapping.elf");
|
||||
await writeFile(path, createGoOverlappingElfFixture());
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-go-binary", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
@@ -289,7 +289,7 @@ cliTest(
|
||||
const fixture = createGoSplitElfFixture({ sectionless });
|
||||
const path = join(root, sectionless ? "sectionless.elf" : "original.elf");
|
||||
await writeFile(path, fixture.bytes);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-go-binary", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
@@ -324,7 +324,7 @@ for (const bits of [32, 64] as const) {
|
||||
] as const) {
|
||||
const path = join(root, `${label}.exe`);
|
||||
await writeFile(path, bytes);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-go-binary", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
@@ -379,7 +379,7 @@ cliTest(
|
||||
path,
|
||||
machoImageWithUnknownCommand(fixture.wide, fixture.size),
|
||||
);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-go-binary", path, "--json"],
|
||||
});
|
||||
const response = await call("inspect_go_binary", { path });
|
||||
@@ -418,7 +418,7 @@ for (const expectedErrno of ["ELOOP", "ENAMETOOLONG"] as const) {
|
||||
root,
|
||||
expectedErrno,
|
||||
);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-go-binary", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
@@ -449,7 +449,7 @@ cliTest(
|
||||
const path = join(root, "byte-strings.elf");
|
||||
const fixture = createGoBinaryByteStringFixture();
|
||||
await writeFile(path, fixture.bytes);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-go-binary", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
@@ -467,7 +467,7 @@ for (const fixture of createGoBinaryDiagnosticFixtures()) {
|
||||
const root = await createTestTempDirectory("rea-go-cli-mz-");
|
||||
const path = join(root, `${fixture.name}.exe`);
|
||||
await writeFile(path, fixture.bytes);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-go-binary", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
@@ -488,7 +488,7 @@ cliTest(
|
||||
const root = await createTestTempDirectory("rea-go-cli-");
|
||||
const path = join(root, "text.bin");
|
||||
await writeFile(path, "This is not an executable container.");
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-go-binary", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
@@ -508,7 +508,7 @@ for (const format of ["elf", "pe", "macho"] as const) {
|
||||
const fixture = createGoBinaryFixture({ format, encoding });
|
||||
const path = join(root, "application.bin");
|
||||
await writeFile(path, fixture.bytes);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-go-binary", "application.bin", "--json"],
|
||||
cwd: root,
|
||||
});
|
||||
@@ -582,7 +582,7 @@ cliTest("preserves MIPS identity in Go CLI Evidence", async ({ cli }) => {
|
||||
fixture.bytes.writeUInt16LE(8, 18); // ELF EM_MIPS.
|
||||
const path = join(root, "application.elf");
|
||||
await writeFile(path, fixture.bytes);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-go-binary", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
@@ -601,7 +601,7 @@ cliTest(
|
||||
const root = await createTestTempDirectory("rea-go-cli-");
|
||||
const path = join(root, "metadata-free.elf");
|
||||
await writeFile(path, createGoBinaryFixture({ omitBuildInfo: true }).bytes);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-go-binary", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode).toBe(0);
|
||||
@@ -625,7 +625,7 @@ cliTest(
|
||||
const root = await createTestTempDirectory("rea-go-gopath-cli-");
|
||||
const path = join(root, "application.elf");
|
||||
await writeFile(path, createGoBinaryFixture({ moduleText }).bytes);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-go-binary", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
@@ -662,7 +662,7 @@ cliTest(
|
||||
});
|
||||
await writeFile(path, fixture.bytes);
|
||||
|
||||
const standaloneCap = await runFileCli(cli, {
|
||||
const standaloneCap = await cli.run({
|
||||
arguments: [
|
||||
"inspect-go-binary",
|
||||
path,
|
||||
@@ -680,7 +680,7 @@ cliTest(
|
||||
},
|
||||
});
|
||||
|
||||
const cliResult = await runFileCli(cli, {
|
||||
const cliResult = await cli.run({
|
||||
arguments: [
|
||||
"inspect-go-binary",
|
||||
path,
|
||||
|
||||
@@ -5,7 +5,7 @@ import { evidenceResultOf } from "../../src/contracts/toolOutputSchemaPrimitives
|
||||
import { goBinarySchema } from "../../src/domain/go/goBinary.js";
|
||||
import { createGoBinaryFixture, GO_MODULE_TEXT } from "../fixtures/go/image.js";
|
||||
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
||||
import { cliTest, runFileCli } from "../support/cli/cliFixture.js";
|
||||
import { cliTest } from "../support/cli/cliFixture.js";
|
||||
|
||||
cliTest(
|
||||
"inspects ELF64 Go metadata with four-byte amd64p32 pointers through CLI",
|
||||
@@ -20,7 +20,7 @@ cliTest(
|
||||
});
|
||||
const path = join(root, "nacl-amd64p32.nexe");
|
||||
await writeFile(path, fixture.bytes);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-go-binary", path, "--json"],
|
||||
});
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { connectLocalToolsMcp } from "../fixtures/localToolsMcp.js";
|
||||
import { expectCliMcpEvidenceParity } from "../fixtures/cliMcpEvidenceParity.js";
|
||||
import { parseMcpToolError } from "../fixtures/mcpToolError.js";
|
||||
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
||||
import { cliTest, runFileCli } from "../support/cli/cliFixture.js";
|
||||
import { cliTest } from "../support/cli/cliFixture.js";
|
||||
|
||||
cliTest(
|
||||
"retains header-backed PE and cross-load ELF pointer metadata through CLI and MCP",
|
||||
@@ -34,7 +34,7 @@ cliTest(
|
||||
for (const { name, fixture } of cases) {
|
||||
const path = join(root, name);
|
||||
await writeFile(path, fixture.bytes);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-go-binary", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
@@ -64,7 +64,7 @@ cliTest(
|
||||
damaged.bytes.writeBigUInt64LE(0x11001n, 200); // A one-byte virtual gap.
|
||||
const path = join(root, "elf-cross-load-gap");
|
||||
await writeFile(path, damaged.bytes);
|
||||
const rejected = await runFileCli(cli, {
|
||||
const rejected = await cli.run({
|
||||
arguments: ["inspect-go-binary", path, "--json"],
|
||||
});
|
||||
expect(rejected.exitCode).toBe(1);
|
||||
|
||||
@@ -13,7 +13,7 @@ import { parseEvidence, createEvidence } from "../../src/domain/evidence.js";
|
||||
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
||||
import { connectLocalToolsMcp } from "../fixtures/localToolsMcp.js";
|
||||
import { parseMcpToolError } from "../fixtures/mcpToolError.js";
|
||||
import { cliTest, runFileCli } from "../support/cli/cliFixture.js";
|
||||
import { cliTest } from "../support/cli/cliFixture.js";
|
||||
import {
|
||||
cudaFixture,
|
||||
peGpuFixture,
|
||||
@@ -38,7 +38,7 @@ cliTest(
|
||||
] as const) {
|
||||
const path = join(root, name);
|
||||
await writeFile(path, bytes);
|
||||
const response = await runFileCli(cli, {
|
||||
const response = await cli.run({
|
||||
arguments: [
|
||||
"inspect-gpu-code-objects",
|
||||
path,
|
||||
@@ -76,7 +76,7 @@ cliTest(
|
||||
const bytes = cubinFixture({ name: "k".repeat(65_536) });
|
||||
const path = join(root, "long-symbol.cubin");
|
||||
await writeFile(path, bytes);
|
||||
const response = await runFileCli(cli, {
|
||||
const response = await cli.run({
|
||||
arguments: [
|
||||
"inspect-gpu-code-objects",
|
||||
path,
|
||||
@@ -124,7 +124,7 @@ cliTest(
|
||||
const bytes = Buffer.concat([header, compressed]),
|
||||
path = join(root, `v${version}`);
|
||||
await writeFile(path, bytes);
|
||||
const response = await runFileCli(cli, {
|
||||
const response = await cli.run({
|
||||
arguments: [
|
||||
"inspect-gpu-code-objects",
|
||||
path,
|
||||
@@ -183,7 +183,7 @@ cliTest(
|
||||
const bytes = hipFixture(payload, bundleId),
|
||||
path = join(root, name);
|
||||
await writeFile(path, bytes);
|
||||
const response = await runFileCli(cli, {
|
||||
const response = await cli.run({
|
||||
arguments: [
|
||||
"inspect-gpu-code-objects",
|
||||
path,
|
||||
@@ -233,7 +233,7 @@ cliTest(
|
||||
}
|
||||
const path = join(root, "raw-bitcode");
|
||||
await writeFile(path, bitcode);
|
||||
const raw = await runFileCli(cli, {
|
||||
const raw = await cli.run({
|
||||
arguments: [
|
||||
"inspect-gpu-code-objects",
|
||||
path,
|
||||
@@ -271,7 +271,7 @@ cliTest(
|
||||
]),
|
||||
path = join(root, name);
|
||||
await writeFile(path, bytes);
|
||||
const response = await runFileCli(cli, {
|
||||
const response = await cli.run({
|
||||
arguments: [
|
||||
"inspect-gpu-code-objects",
|
||||
path,
|
||||
@@ -355,7 +355,7 @@ cliTest(
|
||||
]),
|
||||
path = join(root, name);
|
||||
await writeFile(path, bytes);
|
||||
const response = await runFileCli(cli, {
|
||||
const response = await cli.run({
|
||||
arguments: [
|
||||
"inspect-gpu-code-objects",
|
||||
path,
|
||||
@@ -396,7 +396,7 @@ cliTest(
|
||||
]),
|
||||
path = join(root, "large-parameter.cubin");
|
||||
await writeFile(path, bytes);
|
||||
const response = await runFileCli(cli, {
|
||||
const response = await cli.run({
|
||||
arguments: [
|
||||
"inspect-gpu-code-objects",
|
||||
path,
|
||||
@@ -433,7 +433,7 @@ cliTest(
|
||||
const bytes = hipFixture(payload, "hip-amdgcn-amd-amdhsa--gfx906+xnack"),
|
||||
path = join(root, "feature.bundle");
|
||||
await writeFile(path, bytes);
|
||||
const response = await runFileCli(cli, {
|
||||
const response = await cli.run({
|
||||
arguments: [
|
||||
"inspect-gpu-code-objects",
|
||||
path,
|
||||
@@ -478,7 +478,7 @@ cliTest(
|
||||
root = await createTestTempDirectory("rea-ptx-callprototype-"),
|
||||
path = join(root, "prototype.ptx");
|
||||
await writeFile(path, source);
|
||||
const response = await runFileCli(cli, {
|
||||
const response = await cli.run({
|
||||
arguments: [
|
||||
"inspect-gpu-code-objects",
|
||||
path,
|
||||
@@ -528,7 +528,7 @@ cliTest(
|
||||
const bytes = cubinFixture({ extendedSectionIndex: true });
|
||||
const path = join(root, "extended.cubin");
|
||||
await writeFile(path, bytes);
|
||||
const response = await runFileCli(cli, {
|
||||
const response = await cli.run({
|
||||
arguments: [
|
||||
"inspect-gpu-code-objects",
|
||||
path,
|
||||
@@ -576,7 +576,7 @@ cliTest(
|
||||
malformedBytes.writeBigUInt64LE(4n, sectionTable + indexTable! * 64 + 32);
|
||||
const malformedPath = join(root, "short-shndx.cubin");
|
||||
await writeFile(malformedPath, malformedBytes);
|
||||
const malformed = await runFileCli(cli, {
|
||||
const malformed = await cli.run({
|
||||
arguments: [
|
||||
"inspect-gpu-code-objects",
|
||||
malformedPath,
|
||||
@@ -607,7 +607,7 @@ cliTest(
|
||||
const text = `.version 7.0\n.target sm_80\n.address_size 64\n.visible .entry kernel() {\n.reg .b32 ${register};\nmov.u32 ${register}, 0;\nret;\n}\n`;
|
||||
const path = join(root, `${register.slice(1)}.ptx`);
|
||||
await writeFile(path, text);
|
||||
const response = await runFileCli(cli, {
|
||||
const response = await cli.run({
|
||||
arguments: [
|
||||
"inspect-gpu-code-objects",
|
||||
path,
|
||||
@@ -644,7 +644,7 @@ cliTest(
|
||||
const text = `.version 8.8\n.target sm_80\n.address_size 64\n.entry %kernel(.param .u32 input) {\nret;\n}\n`;
|
||||
const path = join(root, "identifiers.ptx");
|
||||
await writeFile(path, text);
|
||||
const response = await runFileCli(cli, {
|
||||
const response = await cli.run({
|
||||
arguments: [
|
||||
"inspect-gpu-code-objects",
|
||||
path,
|
||||
@@ -707,7 +707,7 @@ cliTest(
|
||||
const text = `${preamble}${declaration}\n`,
|
||||
path = join(root, `${name}.ptx`);
|
||||
await writeFile(path, text);
|
||||
const response = await runFileCli(cli, {
|
||||
const response = await cli.run({
|
||||
arguments: [
|
||||
"inspect-gpu-code-objects",
|
||||
path,
|
||||
@@ -757,7 +757,7 @@ cliTest(
|
||||
const text = `.version 8.8\n.target compute_80\n.address_size 64\n.entry kernel() {\nret;\n}\n`;
|
||||
const path = join(root, "target.ptx");
|
||||
await writeFile(path, text);
|
||||
const response = await runFileCli(cli, {
|
||||
const response = await cli.run({
|
||||
arguments: [
|
||||
"inspect-gpu-code-objects",
|
||||
path,
|
||||
@@ -787,7 +787,7 @@ cliTest(
|
||||
const text = `.version 1.3\n.target sm_80\n.address_size 64\n.entry kernel {\n.param .u32 input;\nret;\n}\n`;
|
||||
const path = join(root, "legacy.ptx");
|
||||
await writeFile(path, text);
|
||||
const response = await runFileCli(cli, {
|
||||
const response = await cli.run({
|
||||
arguments: [
|
||||
"inspect-gpu-code-objects",
|
||||
path,
|
||||
@@ -826,7 +826,7 @@ cliTest(
|
||||
malformedPath,
|
||||
`.version 1.3\n.target sm_80\n.entry kernel {\n.param .u32 input\n`,
|
||||
);
|
||||
const malformed = await runFileCli(cli, {
|
||||
const malformed = await cli.run({
|
||||
arguments: [
|
||||
"inspect-gpu-code-objects",
|
||||
malformedPath,
|
||||
@@ -853,7 +853,7 @@ cliTest(
|
||||
"attributes malformed selector JSON to its actual CLI option",
|
||||
async ({ cli }) => {
|
||||
for (const name of ["object_ids", "targets", "view"]) {
|
||||
const response = await runFileCli(cli, {
|
||||
const response = await cli.run({
|
||||
arguments: [
|
||||
"inspect-gpu-code-objects",
|
||||
"/not-opened-for-invalid-selectors",
|
||||
@@ -893,7 +893,7 @@ cliTest(
|
||||
},
|
||||
});
|
||||
// PowerShell's Get-FileHash prints the digest in upper case.
|
||||
const response = await runFileCli(cli, {
|
||||
const response = await cli.run({
|
||||
arguments: [
|
||||
"inspect-gpu-code-objects",
|
||||
"/not-opened-for-invalid-selectors",
|
||||
@@ -1339,7 +1339,7 @@ cliTest(
|
||||
] as const) {
|
||||
const path = join(root, name);
|
||||
await writeFile(path, bytes);
|
||||
const response = await runFileCli(cli, {
|
||||
const response = await cli.run({
|
||||
arguments: [
|
||||
"inspect-gpu-code-objects",
|
||||
path,
|
||||
|
||||
@@ -44,7 +44,7 @@ import {
|
||||
import { connectLocalToolsMcp } from "../fixtures/localToolsMcp.js";
|
||||
import { parseMcpToolError } from "../fixtures/mcpToolError.js";
|
||||
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
||||
import { cliTest, runFileCli } from "../support/cli/cliFixture.js";
|
||||
import { cliTest } from "../support/cli/cliFixture.js";
|
||||
|
||||
const parseCliMetadata = (value: unknown) =>
|
||||
il2cppMetadataSchema.parse(
|
||||
@@ -75,7 +75,7 @@ cliTest(
|
||||
}
|
||||
strings.copy(bytes, 500);
|
||||
await writeFile(path, bytes);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-il2cpp-metadata", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
@@ -122,7 +122,7 @@ cliTest(
|
||||
createLinkedIl2cppMetadataFixture({ version: 39 }).bytes,
|
||||
);
|
||||
await writeFile(binaryPath, createIl2cppBinaryFixture());
|
||||
const produced = await runFileCli(cli, {
|
||||
const produced = await cli.run({
|
||||
arguments: [
|
||||
"inspect-il2cpp-metadata",
|
||||
path,
|
||||
@@ -161,7 +161,7 @@ cliTest(
|
||||
};
|
||||
const query = join(root, `image-${image}.json`);
|
||||
await writeFile(query, JSON.stringify(request));
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-analysis-view", query, "--json"],
|
||||
});
|
||||
const mcp = await call("inspect_analysis_view", request);
|
||||
@@ -222,7 +222,7 @@ cliTest(
|
||||
};
|
||||
const query = join(root, `${scenario}.json`);
|
||||
await writeFile(query, JSON.stringify(request));
|
||||
const cliResult = await runFileCli(cli, {
|
||||
const cliResult = await cli.run({
|
||||
arguments: ["inspect-analysis-view", query, "--json"],
|
||||
});
|
||||
const mcp = await call("inspect_analysis_view", request);
|
||||
@@ -249,7 +249,7 @@ cliTest(
|
||||
});
|
||||
const path = join(root, `valid-${count}.dat`);
|
||||
await writeFile(path, bytes);
|
||||
const output = await runFileCli(cli, {
|
||||
const output = await cli.run({
|
||||
arguments: ["inspect-il2cpp-metadata", path, "--json"],
|
||||
});
|
||||
expect(output.exitCode, output.stderr).toBe(0);
|
||||
@@ -282,7 +282,7 @@ cliTest(
|
||||
);
|
||||
const path = join(root, `invalid-${declaredCount}-${declaredBytes}.dat`);
|
||||
await writeFile(path, fixture.bytes);
|
||||
const output = await runFileCli(cli, {
|
||||
const output = await cli.run({
|
||||
arguments: ["inspect-il2cpp-metadata", path, "--json"],
|
||||
});
|
||||
expect(output.exitCode).toBe(1);
|
||||
@@ -321,7 +321,7 @@ cliTest(
|
||||
propertiesAndEvents: true,
|
||||
});
|
||||
await writeFile(path, bytes);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-il2cpp-metadata", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
@@ -472,7 +472,7 @@ cliTest(
|
||||
] as const) {
|
||||
const path = join(root, name);
|
||||
await writeFile(path, bytes);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-il2cpp-metadata", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
@@ -521,7 +521,7 @@ cliTest(
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-il2cpp-metadata", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
@@ -828,7 +828,7 @@ cliTest(
|
||||
const root = await createTestTempDirectory("rea-il2cpp-cli-export-");
|
||||
const path = join(root, "global-metadata.dat");
|
||||
const bundle = join(root, "evidence.json");
|
||||
const refused = await runFileCli(cli, {
|
||||
const refused = await cli.run({
|
||||
arguments: [
|
||||
"inspect-il2cpp-metadata",
|
||||
join(root, "must-not-be-read"),
|
||||
@@ -852,14 +852,14 @@ cliTest(
|
||||
bundle,
|
||||
"--json",
|
||||
];
|
||||
const result = await runFileCli(cli, { arguments: args });
|
||||
const result = await cli.run({ arguments: args });
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
expect(result.json).toMatchObject({
|
||||
evidence_bundle: { path: bundle, bytes: expect.any(Number) },
|
||||
summary: { normalized_result: { kind: "summary" } },
|
||||
});
|
||||
const before = await readFile(bundle);
|
||||
const overwrite = await runFileCli(cli, { arguments: args });
|
||||
const overwrite = await cli.run({ arguments: args });
|
||||
expect(overwrite.exitCode).toBe(1);
|
||||
expect(Buffer.compare(await readFile(bundle), before)).toBe(0);
|
||||
const saved = await readEvidenceBundle(bundle);
|
||||
@@ -887,7 +887,7 @@ cliTest(
|
||||
},
|
||||
}),
|
||||
);
|
||||
const selected = await runFileCli(cli, {
|
||||
const selected = await cli.run({
|
||||
arguments: ["inspect-analysis-view", query, "--json"],
|
||||
});
|
||||
expect(selected.exitCode, selected.stderr).toBe(0);
|
||||
@@ -913,7 +913,7 @@ cliTest.skipIf(process.platform === "win32")(
|
||||
const decoy = join(root, "evidence.json");
|
||||
await writeFile(decoy, "unrelated caller data");
|
||||
const selected = `${root}/link/../evidence.json`;
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-il2cpp-metadata",
|
||||
path,
|
||||
@@ -932,7 +932,7 @@ cliTest.skipIf(process.platform === "win32")(
|
||||
expect(
|
||||
JSON.parse(await readFile(join(actual, "evidence.json"), "utf8")),
|
||||
).toEqual(saved.value);
|
||||
const read = await runFileCli(cli, {
|
||||
const read = await cli.run({
|
||||
arguments: [
|
||||
"inspect-evidence-bundle",
|
||||
JSON.stringify({ path: selected, detail: "summary" }),
|
||||
@@ -966,7 +966,7 @@ cliTest(
|
||||
rva: `0x${(address - FIXTURE_BINARY_BASE).toString(16)}`,
|
||||
},
|
||||
);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-il2cpp-metadata",
|
||||
path,
|
||||
@@ -1044,7 +1044,7 @@ cliTest(
|
||||
await writeFile(path, createLinkedIl2cppMetadataFixture().bytes);
|
||||
await writeFile(binaryPath, createIl2cppDyldBindFixture(0n));
|
||||
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-il2cpp-metadata",
|
||||
path,
|
||||
@@ -1089,7 +1089,7 @@ cliTest(
|
||||
await writeFile(path, createLinkedIl2cppMetadataFixture().bytes);
|
||||
const inspect = async (bytes: Buffer) => {
|
||||
await writeFile(binaryPath, bytes);
|
||||
const output = await runFileCli(cli, {
|
||||
const output = await cli.run({
|
||||
arguments: [
|
||||
"inspect-il2cpp-metadata",
|
||||
path,
|
||||
@@ -1162,7 +1162,7 @@ cliTest(
|
||||
bytes.fill(0xff, 0x604, 0x608);
|
||||
const binaryPath = join(root, `registration-${tailBytes}.dylib`);
|
||||
await writeFile(binaryPath, bytes);
|
||||
const output = await runFileCli(cli, {
|
||||
const output = await cli.run({
|
||||
arguments: [
|
||||
"inspect-il2cpp-metadata",
|
||||
path,
|
||||
@@ -1252,7 +1252,7 @@ cliTest(
|
||||
const binaryPath = join(root, `${entry.name}.dylib`);
|
||||
await writeFile(path, entry.metadata.bytes);
|
||||
await writeFile(binaryPath, entry.binary);
|
||||
const cliResult = await runFileCli(cli, {
|
||||
const cliResult = await cli.run({
|
||||
arguments: [
|
||||
"inspect-il2cpp-metadata",
|
||||
path,
|
||||
@@ -1307,7 +1307,7 @@ cliTest(
|
||||
{ machine: 62 },
|
||||
),
|
||||
);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-il2cpp-metadata",
|
||||
path,
|
||||
@@ -1338,7 +1338,7 @@ cliTest(
|
||||
await writeFile(path, createLinkedIl2cppMetadataFixture().bytes);
|
||||
await writeFile(binaryPath, createElfFixture(createIl2cppBinaryFixture()));
|
||||
const run = (output: string, extra: readonly string[] = []) =>
|
||||
runFileCli(cli, {
|
||||
cli.run({
|
||||
arguments: [
|
||||
"inspect-il2cpp-metadata",
|
||||
path,
|
||||
@@ -1393,7 +1393,7 @@ cliTest(
|
||||
});
|
||||
expect(await readFile(seeds, "utf8")).toBe("kept\n");
|
||||
|
||||
const unlinked = await runFileCli(cli, {
|
||||
const unlinked = await cli.run({
|
||||
arguments: [
|
||||
"inspect-il2cpp-metadata",
|
||||
path,
|
||||
@@ -1432,7 +1432,7 @@ cliTest(
|
||||
binaryPath,
|
||||
createElfFixture(createIl2cppBinaryFixture({ genericMethods: {} })),
|
||||
);
|
||||
const linked = await runFileCli(cli, {
|
||||
const linked = await cli.run({
|
||||
arguments: [
|
||||
"inspect-il2cpp-metadata",
|
||||
path,
|
||||
@@ -1458,7 +1458,7 @@ cliTest(
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
const unlinked = await runFileCli(cli, {
|
||||
const unlinked = await cli.run({
|
||||
arguments: [
|
||||
"inspect-il2cpp-metadata",
|
||||
path,
|
||||
@@ -1508,7 +1508,7 @@ cliTest(
|
||||
|
||||
const inspect = async (binary: Buffer) => {
|
||||
await writeFile(binaryPath, binary);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-il2cpp-metadata",
|
||||
path,
|
||||
@@ -1551,7 +1551,7 @@ cliTest.skipIf(process.platform === "win32")(
|
||||
await writeFile(path, createLinkedIl2cppMetadataFixture().bytes);
|
||||
await writeFile(binaryPath, createElfFixture(createIl2cppBinaryFixture()));
|
||||
await writeFile(parent, "unchanged");
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-il2cpp-metadata",
|
||||
path,
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createIl2cppMetadataFixture } from "../fixtures/il2cpp/metadata.js";
|
||||
import { connectLocalToolsMcp } from "../fixtures/localToolsMcp.js";
|
||||
import { parseMcpToolError } from "../fixtures/mcpToolError.js";
|
||||
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
||||
import { cliTest, runFileCli } from "../support/cli/cliFixture.js";
|
||||
import { cliTest } from "../support/cli/cliFixture.js";
|
||||
|
||||
cliTest(
|
||||
"rejects partial IL2CPP table extents through CLI and MCP without a binary",
|
||||
@@ -24,7 +24,7 @@ cliTest(
|
||||
});
|
||||
const path = join(root, `${version}-${name}.dat`);
|
||||
await writeFile(path, fixture.bytes);
|
||||
const intact = await runFileCli(cli, {
|
||||
const intact = await cli.run({
|
||||
arguments: ["inspect-il2cpp-metadata", path, "--json"],
|
||||
});
|
||||
expect(intact.exitCode, intact.stderr).toBe(0);
|
||||
@@ -35,7 +35,7 @@ cliTest(
|
||||
fixture.header(name, "size"),
|
||||
);
|
||||
await writeFile(path, damaged);
|
||||
const invalid = await runFileCli(cli, {
|
||||
const invalid = await cli.run({
|
||||
arguments: ["inspect-il2cpp-metadata", path, "--json"],
|
||||
});
|
||||
const expected = {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { expect } from "vitest";
|
||||
import { parseEvidence } from "../../src/domain/evidence.js";
|
||||
import { pythonPycFixture } from "../../src/python/PythonPyc.fixture.js";
|
||||
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
||||
import { cliTest, runFileCli } from "../support/cli/cliFixture.js";
|
||||
import { cliTest } from "../support/cli/cliFixture.js";
|
||||
|
||||
const fileCommands = [
|
||||
"inspect-sqlite-database",
|
||||
@@ -51,7 +51,7 @@ cliTest(
|
||||
},
|
||||
];
|
||||
for (const selection of selections) {
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [...selection.arguments, "--json"],
|
||||
cwd,
|
||||
});
|
||||
@@ -161,7 +161,7 @@ cliTest(
|
||||
},
|
||||
];
|
||||
for (const selection of selections) {
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [...selection.arguments, "--json"],
|
||||
cwd,
|
||||
});
|
||||
@@ -183,7 +183,7 @@ cliTest(
|
||||
const bytes = pythonPycFixture();
|
||||
const path = join(cwd, "selected.pyc");
|
||||
await writeFile(path, bytes);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-python-pyc", "./selected.pyc", "--json"],
|
||||
cwd,
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import { expect } from "vitest";
|
||||
import { elf, thinMach } from "../../src/domain/binaryTarget.fixture.js";
|
||||
import { buildManagedPeFixture } from "../../src/dotnet/ManagedPe.fixture.js";
|
||||
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
||||
import { cliTest, runFileCli } from "../support/cli/cliFixture.js";
|
||||
import { cliTest } from "../support/cli/cliFixture.js";
|
||||
|
||||
cliTest(
|
||||
"recovers the same managed artifact through decimal CLI budgets and retains caller choices",
|
||||
@@ -157,7 +157,7 @@ cliTest(
|
||||
ilBody: Buffer.concat([Buffer.from([(il.length << 2) | 2]), il]),
|
||||
}),
|
||||
);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-managed-members", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
@@ -546,7 +546,7 @@ cliTest(
|
||||
});
|
||||
const path = join(root, `virtual-${virtualSectionSize}.dll`);
|
||||
await writeFile(path, bytes);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-managed-members", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode).toBe(0);
|
||||
@@ -647,7 +647,7 @@ cliTest(
|
||||
"inspect-managed-members",
|
||||
"inspect-managed-native-boundaries",
|
||||
]) {
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [command, path, "--json"],
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
@@ -682,7 +682,7 @@ cliTest(
|
||||
"inspect-managed-members",
|
||||
"inspect-managed-native-boundaries",
|
||||
]) {
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [command, path, "--json"],
|
||||
});
|
||||
expect(result.exitCode).not.toBe(0);
|
||||
|
||||
@@ -37,7 +37,7 @@ cliTest(
|
||||
],
|
||||
] as const) {
|
||||
const selected = operation === "inspect_managed_members";
|
||||
const output = await cli.sharedCliRun!({
|
||||
const output = await cli.run({
|
||||
arguments: [
|
||||
command,
|
||||
path,
|
||||
|
||||
@@ -27,7 +27,7 @@ cliTest(
|
||||
["Inner", "Fixture.Inner"],
|
||||
["Fixture.Inner"],
|
||||
]) {
|
||||
const output = await cli.sharedCliRun!({
|
||||
const output = await cli.run({
|
||||
arguments: [
|
||||
"inspect-managed-members",
|
||||
path,
|
||||
@@ -99,7 +99,7 @@ cliTest(
|
||||
const sha256 = createHash("sha256").update(content).digest("hex");
|
||||
await writeFile(path, content);
|
||||
for (const isSelected of [true, false]) {
|
||||
const output = await cli.sharedCliRun!({
|
||||
const output = await cli.run({
|
||||
arguments: [
|
||||
"inspect-managed-members",
|
||||
path,
|
||||
|
||||
@@ -22,11 +22,7 @@ import {
|
||||
safetensorsFixture,
|
||||
} from "../fixtures/model/model.js";
|
||||
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
||||
import {
|
||||
cliTest,
|
||||
createSharedMcpSession,
|
||||
runFileCli,
|
||||
} from "../support/cli/cliFixture.js";
|
||||
import { cliTest, createSharedMcpSession } from "../support/cli/cliFixture.js";
|
||||
|
||||
// Model queries are target-free; share immutable registration and schema setup.
|
||||
// Portable export keeps its separate post-CLI connection below.
|
||||
@@ -113,7 +109,7 @@ cliTest(
|
||||
});
|
||||
const bytes = Buffer.concat([fixture.bytes, Buffer.alloc(16)]);
|
||||
await writeFile(path, bytes);
|
||||
const cliResult = await runFileCli(cli, {
|
||||
const cliResult = await cli.run({
|
||||
arguments: [
|
||||
"inspect-model-artifact",
|
||||
path,
|
||||
@@ -365,7 +361,7 @@ cliTest(
|
||||
tensors,
|
||||
});
|
||||
await writeFile(path, fixture.bytes);
|
||||
const response = await runFileCli(cli, {
|
||||
const response = await cli.run({
|
||||
arguments: [
|
||||
"inspect-model-artifact",
|
||||
path,
|
||||
@@ -999,7 +995,7 @@ cliTest(
|
||||
] as const) {
|
||||
const path = join(root, name);
|
||||
await writeFile(path, bytes);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-model-artifact",
|
||||
path,
|
||||
@@ -1047,7 +1043,7 @@ cliTest(
|
||||
Buffer.alloc(0),
|
||||
).bytes,
|
||||
);
|
||||
const mapCli = await runFileCli(cli, {
|
||||
const mapCli = await cli.run({
|
||||
arguments: [
|
||||
"inspect-model-artifact",
|
||||
mapPath,
|
||||
@@ -1084,7 +1080,7 @@ cliTest(
|
||||
safetensorsFixture(`{"__metadata__":${numeric}}`, Buffer.alloc(0))
|
||||
.bytes,
|
||||
);
|
||||
const malformedCli = await runFileCli(cli, {
|
||||
const malformedCli = await cli.run({
|
||||
arguments: ["inspect-model-artifact", malformedPath, "--json"],
|
||||
});
|
||||
expect(malformedCli.exitCode).toBe(1);
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import { repeatedCertificateSignedData } from "../fixtures/peCertificateAllowance.js";
|
||||
import { parseMcpToolError } from "../fixtures/mcpToolError.js";
|
||||
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
||||
import { cliTest, runFileCli } from "../support/cli/cliFixture.js";
|
||||
import { cliTest } from "../support/cli/cliFixture.js";
|
||||
|
||||
cliTest.for([0x00, 0x01, 0x7f, 0xfe, 0xff, null])(
|
||||
"requires canonical extension critical encoding %s through CLI and MCP",
|
||||
@@ -41,7 +41,7 @@ cliTest.for([0x00, 0x01, 0x7f, 0xfe, 0xff, null])(
|
||||
}),
|
||||
);
|
||||
await writeFile(path, bytes);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-pe-certificates", path, "--json"],
|
||||
});
|
||||
const call = await cli.sharedMcpCall!();
|
||||
@@ -104,7 +104,7 @@ cliTest.for(["extra-field", "empty-rdn"] as const)(
|
||||
);
|
||||
const bytes = peCertificateFixture(false, payload);
|
||||
await writeFile(path, bytes);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-pe-certificates", path, "--json"],
|
||||
});
|
||||
expect.soft(result.exitCode, result.stdout).toBe(1);
|
||||
@@ -138,7 +138,7 @@ cliTest.for(["pubkey", "signature"])(
|
||||
expect(at).toBeGreaterThanOrEqual(0);
|
||||
payload[at + 2] = 1;
|
||||
await writeFile(path, peCertificateFixture(false, payload));
|
||||
const invalid = await runFileCli(cli, {
|
||||
const invalid = await cli.run({
|
||||
arguments: ["inspect-pe-certificates", path, "--json"],
|
||||
});
|
||||
expect(invalid.exitCode).toBe(1);
|
||||
@@ -159,7 +159,7 @@ cliTest.for(["pubkey", "signature"])(
|
||||
const last = at + marker.length - 1;
|
||||
payload[last] = payload[last]! & 0xfe;
|
||||
await writeFile(path, peCertificateFixture(false, payload));
|
||||
const valid = await runFileCli(cli, {
|
||||
const valid = await cli.run({
|
||||
arguments: ["inspect-pe-certificates", path, "--json"],
|
||||
});
|
||||
expect(valid.exitCode, valid.stderr).toBe(0);
|
||||
@@ -210,7 +210,7 @@ cliTest("reports unsigned attributes through CLI and MCP", async ({ cli }) => {
|
||||
[1, 3, 6, 1, 4, 1, 311, 3, 3, 1],
|
||||
]);
|
||||
await writeFile(path, peCertificateFixture(true, payload));
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-pe-certificates", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
@@ -239,7 +239,7 @@ cliTest(
|
||||
const payload = pkcs7SignedDataIndefiniteEnvelopeFixture();
|
||||
await writeFile(path, peCertificateFixture(false, payload));
|
||||
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-pe-certificates", path, "--json"],
|
||||
});
|
||||
|
||||
@@ -297,7 +297,7 @@ cliTest("reports a truncated PE as invalid input", async ({ cli }) => {
|
||||
const root = await createTestTempDirectory("rea-pe-cert-cli-bad-");
|
||||
const path = join(root, "broken.exe");
|
||||
await writeFile(path, Buffer.from("MZ"));
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-pe-certificates", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
@@ -318,7 +318,7 @@ cliTest(
|
||||
// Replace an equal-length UTF8String with T.61's acute accent plus 'e'.
|
||||
Buffer.from([0x14, 0x02, 0xc2, 0x65]).copy(payload, nameAt);
|
||||
await writeFile(path, peCertificateFixture(false, payload));
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-pe-certificates", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
@@ -348,7 +348,7 @@ cliTest(
|
||||
payload.subarray(4),
|
||||
]);
|
||||
await writeFile(path, peCertificateFixture(false, malformed));
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-pe-certificates", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
@@ -372,7 +372,7 @@ cliTest(
|
||||
// Equal-size replacement keeps the DER lengths and all unrelated fields intact.
|
||||
Buffer.from([0x1e, 0x04, 0xd8, 0, 0xdc, 0]).copy(payload, nameAt);
|
||||
await writeFile(path, peCertificateFixture(false, payload));
|
||||
const invalid = await runFileCli(cli, {
|
||||
const invalid = await cli.run({
|
||||
arguments: ["inspect-pe-certificates", path, "--json"],
|
||||
});
|
||||
expect(invalid.exitCode).toBe(1);
|
||||
@@ -382,7 +382,7 @@ cliTest(
|
||||
});
|
||||
Buffer.from([0x1e, 0x04, 0xd7, 0xff, 0xe0, 0]).copy(payload, nameAt);
|
||||
await writeFile(path, peCertificateFixture(false, payload));
|
||||
const valid = await runFileCli(cli, {
|
||||
const valid = await cli.run({
|
||||
arguments: ["inspect-pe-certificates", path, "--json"],
|
||||
});
|
||||
expect(valid.exitCode, valid.stderr).toBe(0);
|
||||
@@ -407,7 +407,7 @@ cliTest(
|
||||
// 2025 has no 29 February; the equal-length edit keeps every DER length.
|
||||
payload.write("250229000000Z", timeAt, "ascii");
|
||||
await writeFile(path, peCertificateFixture(false, payload));
|
||||
const invalid = await runFileCli(cli, {
|
||||
const invalid = await cli.run({
|
||||
arguments: ["inspect-pe-certificates", path, "--json"],
|
||||
});
|
||||
expect(invalid.exitCode).toBe(1);
|
||||
@@ -417,7 +417,7 @@ cliTest(
|
||||
});
|
||||
payload.write("240229000000Z", timeAt, "ascii");
|
||||
await writeFile(path, peCertificateFixture(false, payload));
|
||||
const valid = await runFileCli(cli, {
|
||||
const valid = await cli.run({
|
||||
arguments: ["inspect-pe-certificates", path, "--json"],
|
||||
});
|
||||
expect(valid.exitCode, valid.stderr).toBe(0);
|
||||
@@ -521,7 +521,7 @@ cliTest.for([
|
||||
expect(at).toBeGreaterThanOrEqual(0);
|
||||
payload[at] = tag;
|
||||
await writeFile(path, peCertificateFixture(false, payload));
|
||||
const invalid = await runFileCli(cli, {
|
||||
const invalid = await cli.run({
|
||||
arguments: ["inspect-pe-certificates", path, "--json"],
|
||||
});
|
||||
expect(invalid.exitCode).toBe(1);
|
||||
@@ -532,7 +532,7 @@ cliTest.for([
|
||||
// Only restore the string tag: the same bytes are valid UTF8String content.
|
||||
payload[at] = 0x0c;
|
||||
await writeFile(path, peCertificateFixture(false, payload));
|
||||
const valid = await runFileCli(cli, {
|
||||
const valid = await cli.run({
|
||||
arguments: ["inspect-pe-certificates", path, "--json"],
|
||||
});
|
||||
expect(valid.exitCode, valid.stderr).toBe(0);
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from "../fixtures/binaryDiagnostics/peImports.js";
|
||||
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
||||
import { parseMcpToolError } from "../fixtures/mcpToolError.js";
|
||||
import { cliTest, runFileCli } from "../support/cli/cliFixture.js";
|
||||
import { cliTest } from "../support/cli/cliFixture.js";
|
||||
|
||||
for (const bits of [32, 64] as const) {
|
||||
cliTest(
|
||||
@@ -263,9 +263,10 @@ cliTest(
|
||||
const invocation = {
|
||||
arguments: ["inspect-pe-imports", path, "--json"],
|
||||
};
|
||||
const result = await (rva === 0xffffffec
|
||||
? cli.run(invocation)
|
||||
: runFileCli(cli, invocation));
|
||||
const result = await cli.run({
|
||||
...invocation,
|
||||
freshProcess: rva === 0xffffffec,
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
const report = toolContract("inspect_pe_imports").outputSchema.parse(
|
||||
result.json,
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
pythonPycHeader,
|
||||
} from "../../src/python/PythonPyc.fixture.js";
|
||||
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
||||
import { cliTest, runFileCli } from "../support/cli/cliFixture.js";
|
||||
import { cliTest } from "../support/cli/cliFixture.js";
|
||||
|
||||
const completePycEvidenceSchema = toolContract(
|
||||
"inspect_python_pyc",
|
||||
@@ -57,7 +57,7 @@ cliTest(
|
||||
}),
|
||||
]),
|
||||
);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-python-pyc", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
@@ -77,7 +77,7 @@ cliTest(
|
||||
}),
|
||||
]),
|
||||
);
|
||||
const ordinaryInt = await runFileCli(cli, {
|
||||
const ordinaryInt = await cli.run({
|
||||
arguments: ["inspect-python-pyc", ordinaryIntPath, "--json"],
|
||||
});
|
||||
expect(ordinaryInt.exitCode, ordinaryInt.stderr).toBe(0);
|
||||
@@ -95,7 +95,7 @@ cliTest(
|
||||
Buffer.alloc(7),
|
||||
]),
|
||||
);
|
||||
const truncated = await runFileCli(cli, {
|
||||
const truncated = await cli.run({
|
||||
arguments: ["inspect-python-pyc", truncatedPath, "--json"],
|
||||
});
|
||||
expect(truncated.exitCode).toBe(1);
|
||||
@@ -183,7 +183,7 @@ cliTest(
|
||||
const path = join(root, `inline-caches-${magic}.pyc`);
|
||||
await writeFile(path, pyc);
|
||||
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-python-pyc", path, "--json"],
|
||||
});
|
||||
expect(
|
||||
@@ -278,7 +278,7 @@ cliTest(
|
||||
header[2] = 0x0d;
|
||||
header[3] = 0x0a;
|
||||
await writeFile(path, Buffer.concat([header, marshal]));
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-python-pyc", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
@@ -295,7 +295,7 @@ cliTest(
|
||||
for (const magic of [3392, 3394]) {
|
||||
const path = join(root, `${magic}.pyc`);
|
||||
await writeFile(path, Buffer.concat([pythonPycHeader(magic), marshal]));
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-python-pyc", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
@@ -354,7 +354,7 @@ cliTest(
|
||||
path,
|
||||
Buffer.concat([pythonPycHeader(magic), marshalCode(layout, fields)]),
|
||||
);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-python-pyc", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
@@ -413,7 +413,7 @@ cliTest(
|
||||
path,
|
||||
Buffer.concat([pythonPycHeader(magic), marshalCode(layout, fields)]),
|
||||
);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-python-pyc", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
@@ -503,7 +503,7 @@ cliTest(
|
||||
}),
|
||||
]),
|
||||
);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-python-pyc", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
@@ -553,7 +553,7 @@ cliTest(
|
||||
marshalCode(testCase.layout, { consts: [frozenSet, dict] }),
|
||||
]),
|
||||
);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-python-pyc", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
@@ -631,7 +631,7 @@ cliTest(
|
||||
marshalCode(testCase.layout, { consts: [frozenSet, dict] }),
|
||||
]),
|
||||
);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-python-pyc", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
@@ -662,7 +662,7 @@ cliTest(
|
||||
}),
|
||||
]),
|
||||
);
|
||||
const rawResult = await runFileCli(cli, {
|
||||
const rawResult = await cli.run({
|
||||
arguments: ["inspect-python-pyc", rawPath, "--json"],
|
||||
});
|
||||
expect(rawResult.exitCode, rawResult.stderr).toBe(0);
|
||||
|
||||
@@ -13,16 +13,12 @@ const outcomes = [
|
||||
command: "axffj",
|
||||
stdout: "",
|
||||
stderr: "ERROR: Command 'axffj' does not exist.\n",
|
||||
// The refusal line on stderr is classified; process facts stay as observed.
|
||||
completion: "rejected",
|
||||
},
|
||||
{ command: "q", stdout: "", stderr: "", completion: "complete" },
|
||||
{ command: "q", stdout: "", stderr: "" },
|
||||
{
|
||||
command: "echo diagnostic",
|
||||
stdout: "ERROR: Command 'axffj' does not exist.\n",
|
||||
stderr: "WARNING: fixture warning\n",
|
||||
// Refusal text on stdout is command output, not a Rizin diagnostic.
|
||||
completion: "complete",
|
||||
},
|
||||
] as const;
|
||||
|
||||
@@ -75,7 +71,7 @@ cliTest.skipIf(process.platform === "win32").for(outcomes)(
|
||||
stdout: outcome.stdout,
|
||||
stderr: outcome.stderr,
|
||||
exit_code: 0,
|
||||
completion_status: outcome.completion,
|
||||
completion_status: "complete",
|
||||
output_truncated: false,
|
||||
artifact_changed: false,
|
||||
});
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { chmod, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { expect } from "vitest";
|
||||
import { REVERSE_ENGINEERING_TOOL_CONTRACTS } from "../../src/contracts/reverseEngineeringToolContracts.js";
|
||||
import { connectLocalToolsMcp } from "../fixtures/localToolsMcp.js";
|
||||
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
||||
import { cliTest } from "../support/cli/cliFixture.js";
|
||||
|
||||
const contract = REVERSE_ENGINEERING_TOOL_CONTRACTS.find(
|
||||
({ name }) => name === "execute_rizin_command",
|
||||
);
|
||||
if (contract === undefined)
|
||||
throw new Error("execute_rizin_command contract is missing");
|
||||
|
||||
interface Outcome {
|
||||
readonly command: string;
|
||||
readonly stdout: string;
|
||||
readonly stderr: string;
|
||||
/** Expected completion and the refusal the limitation must name. */
|
||||
readonly refused?: RegExp;
|
||||
}
|
||||
|
||||
// Captured from the official Rizin 0.9.1 static Linux build on /usr/bin/true.
|
||||
const outcomes: readonly Outcome[] = [
|
||||
{
|
||||
command: "axffj",
|
||||
stdout: "",
|
||||
stderr:
|
||||
"ERROR: Command 'axffj' does not exist.\nERROR: Displaying the help of command 'axf'.\n\nERROR: Usage: axf[jq] # List xrefs from current seek\n",
|
||||
refused: /unknown command "axffj"/u,
|
||||
},
|
||||
{
|
||||
// Rizin runs the statements before a refusal and skips the rest.
|
||||
command: "%v 1; zz; %v 2",
|
||||
stdout: "0x1\n",
|
||||
stderr: "ERROR: Command 'zz' does not exist.\n",
|
||||
refused: /unknown command "zz"/u,
|
||||
},
|
||||
{
|
||||
command: "%v 1; afl 1 2; %v 2",
|
||||
stdout: "0x1\n",
|
||||
stderr:
|
||||
"ERROR: Wrong number of arguments passed to `afl`, see its help with `afl?`\n\nERROR: Usage: afl[?] # List functions\n",
|
||||
refused: /wrong argument count for "afl"/u,
|
||||
},
|
||||
{
|
||||
command: "pd 1 @ nosuch",
|
||||
stdout: "",
|
||||
stderr:
|
||||
"ERROR: core: Invalid address (nosuch)\nERROR: core: Error while executing command: pd 1 @ nosuch\n",
|
||||
refused: /an invalid statement/u,
|
||||
},
|
||||
{
|
||||
command: "?e",
|
||||
stdout: "",
|
||||
stderr: "ERROR: core: Error while parsing command: `?e`\n",
|
||||
refused: /input that failed to parse/u,
|
||||
},
|
||||
// A command that ran and reported its own failure is not a shell refusal.
|
||||
{
|
||||
command: "afxj",
|
||||
stdout: "",
|
||||
stderr: "ERROR: No function found in 0x000023d0.\n",
|
||||
},
|
||||
// Refusal text on stdout is command output, not a Rizin diagnostic.
|
||||
{
|
||||
command: "echo refusal",
|
||||
stdout: "ERROR: Command 'axffj' does not exist.\n",
|
||||
stderr: "",
|
||||
},
|
||||
];
|
||||
|
||||
const expectClassified = (raw: unknown, outcome: Outcome): void => {
|
||||
const evidence = contract.outputSchema.parse(raw);
|
||||
expect(evidence.normalized_result).toMatchObject({
|
||||
stdout: outcome.stdout,
|
||||
stderr: outcome.stderr,
|
||||
exit_code: 0,
|
||||
completion_status: outcome.refused === undefined ? "complete" : "rejected",
|
||||
});
|
||||
expect(evidence.raw_result).toEqual(evidence.normalized_result);
|
||||
const refusals = evidence.limitations.filter((limitation) =>
|
||||
limitation.startsWith("Rizin refused "),
|
||||
);
|
||||
if (outcome.refused === undefined) expect(refusals).toEqual([]);
|
||||
else {
|
||||
expect(refusals).toHaveLength(1);
|
||||
expect(refusals[0]).toMatch(outcome.refused);
|
||||
expect(refusals[0]).toMatch(/empty stdout is not an empty result/u);
|
||||
}
|
||||
};
|
||||
|
||||
cliTest.skipIf(process.platform === "win32")(
|
||||
"reports Rizin shell refusals as rejected through CLI and MCP (simulated Rizin)",
|
||||
async ({ cli }) => {
|
||||
const root = await createTestTempDirectory("rea-rizin-rejection-");
|
||||
const provider = join(root, "rizin-fixture");
|
||||
const artifact = join(root, "artifact.bin");
|
||||
await writeFile(artifact, "caller-selected inert artifact");
|
||||
// A Node script replays recorded Rizin output; it is not Rizin.
|
||||
await writeFile(
|
||||
provider,
|
||||
`#!${process.execPath}\n` +
|
||||
`const cases=${JSON.stringify(outcomes)};\n` +
|
||||
"const selected=cases.find(item=>item.command===process.argv[process.argv.indexOf('-c')+1]);\n" +
|
||||
"if(!selected)process.exit(99);\n" +
|
||||
"process.stdout.write(selected.stdout);process.stderr.write(selected.stderr);\n",
|
||||
);
|
||||
await chmod(provider, 0o755);
|
||||
const environment = { REA_RIZIN_COMMAND: provider };
|
||||
const { call } = await connectLocalToolsMcp(environment);
|
||||
// Both adapters share the service's classification; the CLI checks one
|
||||
// refusal and one command diagnostic.
|
||||
for (const outcome of [outcomes[0]!, outcomes[5]!]) {
|
||||
const response = await cli.sharedCliRun!({
|
||||
arguments: [
|
||||
"execute-rizin-command",
|
||||
artifact,
|
||||
outcome.command,
|
||||
"--json",
|
||||
],
|
||||
environment,
|
||||
});
|
||||
expect(response.exitCode, `${response.stdout}\n${response.stderr}`).toBe(
|
||||
0,
|
||||
);
|
||||
expectClassified(response.json, outcome);
|
||||
}
|
||||
for (const outcome of outcomes) {
|
||||
const mcp = await call("execute_rizin_command", {
|
||||
path: artifact,
|
||||
command: outcome.command,
|
||||
});
|
||||
expect(mcp.isError, JSON.stringify(mcp.content)).not.toBe(true);
|
||||
expectClassified(mcp.structuredContent, outcome);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Real-provider gate: runs the Rizin found on PATH and skips without one.
|
||||
const rizin = (() => {
|
||||
try {
|
||||
const path = execFileSync("which", ["rizin"], { encoding: "utf8" }).trim();
|
||||
return path === "" ? undefined : path;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
})();
|
||||
|
||||
cliTest.skipIf(rizin === undefined)(
|
||||
"classifies real Rizin refusals and command diagnostics",
|
||||
async () => {
|
||||
const root = await createTestTempDirectory("rea-real-rizin-rejection-");
|
||||
const artifact = join(root, "artifact.bin");
|
||||
await writeFile(artifact, Buffer.from([0x90, 0xc3]));
|
||||
const { call } = await connectLocalToolsMcp({ REA_RIZIN_COMMAND: rizin! });
|
||||
const expected = [
|
||||
["axffj", "rejected"],
|
||||
["%v 1; zz; %v 2", "rejected"],
|
||||
["%v 1; afl 1 2; %v 2", "rejected"],
|
||||
["pd 1 @ nosuch", "rejected"],
|
||||
["s nosuch", "complete"],
|
||||
["%v 1; %v 2", "complete"],
|
||||
] as const;
|
||||
for (const [command, status] of expected) {
|
||||
const response = await call("execute_rizin_command", {
|
||||
path: artifact,
|
||||
command,
|
||||
});
|
||||
expect(response.isError, JSON.stringify(response.content)).not.toBe(true);
|
||||
const evidence = contract.outputSchema.parse(response.structuredContent);
|
||||
expect(evidence.normalized_result, command).toMatchObject({
|
||||
exit_code: 0,
|
||||
completion_status: status,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
rustDependencyPayload,
|
||||
} from "../fixtures/rust/image.js";
|
||||
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
||||
import { cliTest, runFileCli } from "../support/cli/cliFixture.js";
|
||||
import { cliTest } from "../support/cli/cliFixture.js";
|
||||
import { connectLocalToolsMcp } from "../fixtures/localToolsMcp.js";
|
||||
import { expectCliMcpEvidenceParity } from "../fixtures/cliMcpEvidenceParity.js";
|
||||
import { parseMcpToolError } from "../fixtures/mcpToolError.js";
|
||||
@@ -187,7 +187,7 @@ cliTest(
|
||||
}),
|
||||
);
|
||||
if (surface === "cli") {
|
||||
const response = await runFileCli(cli, {
|
||||
const response = await cli.run({
|
||||
arguments: ["inspect-rust-binary", path, "--json"],
|
||||
});
|
||||
expect(response.exitCode, response.stderr).toBe(0);
|
||||
@@ -212,7 +212,7 @@ cliTest(
|
||||
}
|
||||
|
||||
if (surface === "cli") {
|
||||
const response = await runFileCli(cli, {
|
||||
const response = await cli.run({
|
||||
arguments: ["inspect-rust-binary", malformedPath, "--json"],
|
||||
});
|
||||
expect(response.exitCode).toBe(1);
|
||||
@@ -245,7 +245,7 @@ cliTest("returns null metadata when .dep-v0 is absent", async ({ cli }) => {
|
||||
const root = await createTestTempDirectory("rea-rust-cli-missing-");
|
||||
const path = join(root, "plain");
|
||||
await writeFile(path, createRustImage({ format: "elf", omitSection: true }));
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-rust-binary", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
@@ -278,7 +278,7 @@ cliTest(
|
||||
fixture.type,
|
||||
),
|
||||
);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-rust-binary", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
@@ -324,7 +324,7 @@ cliTest(
|
||||
bytes.writeUInt32LE(0x0200000c, 4);
|
||||
await writeFile(path, bytes);
|
||||
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-rust-binary", path, "--json"],
|
||||
});
|
||||
|
||||
@@ -356,7 +356,7 @@ cliTest(
|
||||
],
|
||||
});
|
||||
await writeFile(path, createRustImage({ format: "elf", payload }));
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-rust-binary", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
@@ -385,7 +385,7 @@ cliTest("rejects malformed dependency JSON", async ({ cli }) => {
|
||||
includeRustc: false,
|
||||
}),
|
||||
);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-rust-binary", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
@@ -410,7 +410,7 @@ cliTest(
|
||||
}),
|
||||
);
|
||||
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-rust-binary", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
@@ -442,7 +442,7 @@ cliTest(
|
||||
bytes.writeUInt32LE(alignedSize, 264 + 16); // .dep-v0 SizeOfRawData
|
||||
await writeFile(path, bytes);
|
||||
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-rust-binary", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
@@ -519,7 +519,7 @@ cliTest(
|
||||
reported_limits: { maximum_bytes: compressed.length * 64 },
|
||||
},
|
||||
};
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-rust-binary", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
@@ -625,7 +625,7 @@ cliTest(
|
||||
}
|
||||
const path = join(root, `mixed.${format}`);
|
||||
await writeFile(path, bytes);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-rust-binary", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
@@ -656,7 +656,7 @@ cliTest(
|
||||
const response = await call("inspect_rust_binary", { path });
|
||||
expect(response.isError, JSON.stringify(response)).not.toBe(true);
|
||||
expectCliMcpEvidenceParity(evidence, response.structuredContent, path);
|
||||
const go = await runFileCli(cli, {
|
||||
const go = await cli.run({
|
||||
arguments: ["inspect-go-binary", path, "--json"],
|
||||
});
|
||||
expect(go.exitCode).toBe(1);
|
||||
|
||||
@@ -26,7 +26,7 @@ cliTest(
|
||||
if (name === "writable") bytes.writeBigUInt64LE(3n, 384 + 8);
|
||||
const path = join(root, `${name}.elf`);
|
||||
await writeFile(path, bytes);
|
||||
const output = await cli.sharedCliRun!({
|
||||
const output = await cli.run({
|
||||
arguments: ["inspect-rust-binary", path, "--json"],
|
||||
});
|
||||
expect(output.exitCode, output.stderr).toBe(0);
|
||||
|
||||
@@ -33,7 +33,7 @@ import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
||||
import {
|
||||
cliTest,
|
||||
createSharedMcpSession,
|
||||
runFileCli,
|
||||
type TestCli,
|
||||
} from "../support/cli/cliFixture.js";
|
||||
|
||||
const digest = (bytes: Buffer): string =>
|
||||
@@ -49,11 +49,8 @@ const parityMcp = createSharedMcpSession(async (registerCleanup, isClosed) => {
|
||||
afterAll(() => parityMcp.close());
|
||||
|
||||
/** Run the offset page through the CLI so the option name is covered as well as the MCP input. */
|
||||
const pagedCliRows = async (
|
||||
cli: Parameters<typeof runFileCli>[0],
|
||||
path: string,
|
||||
): Promise<unknown> => {
|
||||
const result = await runFileCli(cli, {
|
||||
const pagedCliRows = async (cli: TestCli, path: string): Promise<unknown> => {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-sqlite-database",
|
||||
path,
|
||||
@@ -168,7 +165,7 @@ cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
|
||||
`${base}[0,1].value,${base}.1.value`,
|
||||
`${base}.1.value,${base}[0,1].value`,
|
||||
]) {
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-sqlite-database",
|
||||
path,
|
||||
@@ -187,7 +184,7 @@ cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
|
||||
},
|
||||
});
|
||||
}
|
||||
const refused = await runFileCli(cli, {
|
||||
const refused = await cli.run({
|
||||
arguments: [
|
||||
"inspect-sqlite-database",
|
||||
path,
|
||||
@@ -220,7 +217,7 @@ cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
|
||||
} finally {
|
||||
database.close();
|
||||
}
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-sqlite-database",
|
||||
path,
|
||||
@@ -506,7 +503,7 @@ cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
|
||||
},
|
||||
},
|
||||
});
|
||||
const cliResult = await runFileCli(cli, {
|
||||
const cliResult = await cli.run({
|
||||
arguments: [
|
||||
"inspect-sqlite-database",
|
||||
path,
|
||||
@@ -672,7 +669,7 @@ cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
|
||||
}
|
||||
const { call } = await connectLocalToolsMcp();
|
||||
for (const limit of [1001, Number.MAX_SAFE_INTEGER]) {
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-sqlite-database",
|
||||
path,
|
||||
@@ -896,7 +893,7 @@ cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
|
||||
const wal = before[1];
|
||||
expect(db).toBeDefined();
|
||||
expect(wal).toBeDefined();
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-sqlite-database",
|
||||
path,
|
||||
@@ -962,7 +959,7 @@ cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
|
||||
],
|
||||
},
|
||||
};
|
||||
const corrupt = await runFileCli(cli, {
|
||||
const corrupt = await cli.run({
|
||||
arguments: ["inspect-sqlite-database", selected, "--json"],
|
||||
environment: { REA_LOG_LEVEL: "silent" },
|
||||
});
|
||||
@@ -998,7 +995,7 @@ cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
|
||||
},
|
||||
},
|
||||
});
|
||||
const ambiguous = await runFileCli(cli, {
|
||||
const ambiguous = await cli.run({
|
||||
arguments: [
|
||||
"inspect-sqlite-database",
|
||||
valid,
|
||||
@@ -1018,7 +1015,7 @@ cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
|
||||
})
|
||||
).isError,
|
||||
).toBe(true);
|
||||
const missing = await runFileCli(cli, {
|
||||
const missing = await cli.run({
|
||||
arguments: [
|
||||
"inspect-sqlite-database",
|
||||
valid,
|
||||
@@ -1068,7 +1065,7 @@ cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
|
||||
producer.close();
|
||||
}
|
||||
const before = await sourceState(path);
|
||||
const control = await runFileCli(cli, {
|
||||
const control = await cli.run({
|
||||
arguments: [
|
||||
"inspect-sqlite-database",
|
||||
path,
|
||||
@@ -1098,7 +1095,7 @@ cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
|
||||
const columns = Array.from({ length: selectedColumns }, () => "value");
|
||||
// The framed fixture executes the compiled public CLI without the host's
|
||||
// process-argument ceiling rejecting this many repeated flags first.
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-sqlite-database",
|
||||
path,
|
||||
@@ -1155,7 +1152,7 @@ cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
|
||||
const path = createSqliteApplicationCollationFixture(root);
|
||||
const before = await sourceState(path);
|
||||
const { call } = await connectLocalToolsMcp();
|
||||
const schema = await runFileCli(cli, {
|
||||
const schema = await cli.run({
|
||||
arguments: [
|
||||
"inspect-sqlite-database",
|
||||
path,
|
||||
@@ -1194,7 +1191,7 @@ cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
|
||||
expect(schemaResponse.isError).not.toBe(true);
|
||||
expect(schemaResponse.structuredContent).toMatchObject(expectedSchema);
|
||||
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-sqlite-database",
|
||||
path,
|
||||
@@ -1269,7 +1266,7 @@ cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
|
||||
},
|
||||
};
|
||||
for (const columns of [["b", "a"], undefined]) {
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-sqlite-database",
|
||||
path,
|
||||
@@ -1290,7 +1287,7 @@ cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
|
||||
});
|
||||
expect(parseMcpToolError(response).error).toMatchObject(expectedError);
|
||||
}
|
||||
const base = await runFileCli(cli, {
|
||||
const base = await cli.run({
|
||||
arguments: [
|
||||
"inspect-sqlite-database",
|
||||
path,
|
||||
@@ -1347,7 +1344,7 @@ cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
|
||||
"selected_table",
|
||||
"database",
|
||||
] as const) {
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-sqlite-database",
|
||||
path,
|
||||
@@ -1400,7 +1397,7 @@ cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
|
||||
["UTF-16be", "database"],
|
||||
] as const) {
|
||||
const { path, names } = createSqliteLongNameFixture(root, encoding);
|
||||
const response = await runFileCli(cli, {
|
||||
const response = await cli.run({
|
||||
arguments: [
|
||||
"inspect-sqlite-database",
|
||||
path,
|
||||
@@ -1492,7 +1489,7 @@ cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
|
||||
native.close();
|
||||
}
|
||||
|
||||
const cliResult = await runFileCli(cli, {
|
||||
const cliResult = await cli.run({
|
||||
arguments: [
|
||||
"inspect-sqlite-database",
|
||||
path,
|
||||
@@ -1571,7 +1568,7 @@ cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
|
||||
producer.close();
|
||||
}
|
||||
const original = await readFile(path);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-sqlite-database", path, "--json"],
|
||||
environment: { REA_LOG_LEVEL: "silent" },
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ import { historicalHar } from "../../fixtures/historicalHar.js";
|
||||
import { connectLocalToolsMcp } from "../../fixtures/localToolsMcp.js";
|
||||
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { cliTest, runFileCli } from "../../support/cli/cliFixture.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
cliTest(
|
||||
"omits HAR extension payloads from capture previews while retaining complete records",
|
||||
@@ -166,10 +166,10 @@ cliTest(
|
||||
"--json",
|
||||
],
|
||||
};
|
||||
const output =
|
||||
label === "null-timings"
|
||||
? await cli.run(invocation)
|
||||
: await runFileCli(cli, invocation);
|
||||
const output = await cli.run({
|
||||
...invocation,
|
||||
freshProcess: label === "null-timings",
|
||||
});
|
||||
expect(output.exitCode).toBe(1);
|
||||
const expected = {
|
||||
code: "invalid_request",
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { artifactInventoryResultSchema } from "../../../src/domain/artifactGraph.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { cliTest, runFileCli } from "../../support/cli/cliFixture.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
const graphExamples = [
|
||||
{
|
||||
@@ -112,10 +112,10 @@ cliTest.for(graphExamples)(
|
||||
const invocation = {
|
||||
arguments: [command, JSON.stringify(input), "--json"],
|
||||
};
|
||||
const result =
|
||||
code === "evidence_integrity_mismatch"
|
||||
? await cli.run(invocation)
|
||||
: await runFileCli(cli, invocation);
|
||||
const result = await cli.run({
|
||||
...invocation,
|
||||
freshProcess: code === "evidence_integrity_mismatch",
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.json).toMatchObject({
|
||||
code,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { join } from "node:path";
|
||||
import { expect } from "vitest";
|
||||
import {
|
||||
cliTest,
|
||||
runFileCli,
|
||||
type CliInvocation,
|
||||
type TestCli,
|
||||
} from "../../support/cli/cliFixture.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
@@ -173,7 +173,7 @@ const verifyPortableCli = async (
|
||||
);
|
||||
const original = await readFile(bundlePath);
|
||||
await writeFile(inputPath, JSON.stringify({ ...input, detail: "complete" }));
|
||||
const cliFull = await runFileCli(cli, {
|
||||
const cliFull = await cli.run({
|
||||
arguments: [
|
||||
"match-native-functions",
|
||||
inputPath,
|
||||
@@ -187,7 +187,7 @@ const verifyPortableCli = async (
|
||||
expect(cliFull.exitCode, cliFull.stderr).toBe(0);
|
||||
expect(parseEvidence(cliFull.json).evidence_id).toBe(evidenceId);
|
||||
await writeFile(inputPath, JSON.stringify(input));
|
||||
const cliSummary = await runFileCli(cli, {
|
||||
const cliSummary = await cli.run({
|
||||
arguments: [
|
||||
"match-native-functions",
|
||||
inputPath,
|
||||
@@ -222,7 +222,7 @@ const verifyPortableCli = async (
|
||||
expect(ids.has(unrelated.evidence_id)).toBe(false);
|
||||
expect(ids.has(evidenceId)).toBe(true);
|
||||
expect(Buffer.compare(await readFile(bundlePath), original)).toBe(0);
|
||||
const incomplete = await runFileCli(cli, {
|
||||
const incomplete = await cli.run({
|
||||
arguments: [
|
||||
"match-native-functions",
|
||||
inputPath,
|
||||
@@ -508,8 +508,8 @@ cliTest(
|
||||
"right_observations",
|
||||
] as const) {
|
||||
// Keep fresh-process coverage for both source-admission routes on left.
|
||||
const run =
|
||||
field === "left" ? cli.run.bind(cli) : runFileCli.bind(undefined, cli);
|
||||
const run = (invocation: CliInvocation) =>
|
||||
cli.run({ ...invocation, freshProcess: field === "left" });
|
||||
const observation = field.endsWith("observations");
|
||||
const request = {
|
||||
left,
|
||||
@@ -581,7 +581,7 @@ cliTest(
|
||||
detail: "complete",
|
||||
};
|
||||
await writeFile(requestPath, JSON.stringify(inlineRequest));
|
||||
const inline = await runFileCli(cli, {
|
||||
const inline = await cli.run({
|
||||
arguments: ["match-native-functions", requestPath, "--json"],
|
||||
});
|
||||
await writeFile(
|
||||
@@ -589,7 +589,7 @@ cliTest(
|
||||
JSON.stringify(createEvidenceBundle([wrongRole, right])),
|
||||
);
|
||||
await writeFile(requestPath, JSON.stringify(portableRequest));
|
||||
const portable = await runFileCli(cli, {
|
||||
const portable = await cli.run({
|
||||
arguments: [
|
||||
"match-native-functions",
|
||||
requestPath,
|
||||
@@ -611,7 +611,7 @@ cliTest(
|
||||
|
||||
const corruptRequest = { left: badCoordinate, right, detail: "complete" };
|
||||
await writeFile(requestPath, JSON.stringify(corruptRequest));
|
||||
const corruptInline = await runFileCli(cli, {
|
||||
const corruptInline = await cli.run({
|
||||
arguments: ["match-native-functions", requestPath, "--json"],
|
||||
});
|
||||
const corruptPortableRequest = {
|
||||
@@ -623,7 +623,7 @@ cliTest(
|
||||
detail: "complete",
|
||||
};
|
||||
await writeFile(requestPath, JSON.stringify(corruptPortableRequest));
|
||||
const corruptPortable = await runFileCli(cli, {
|
||||
const corruptPortable = await cli.run({
|
||||
arguments: [
|
||||
"match-native-functions",
|
||||
requestPath,
|
||||
@@ -641,7 +641,7 @@ cliTest(
|
||||
|
||||
const malformedBundlePath = join(root, "malformed-outer-frame.json");
|
||||
await writeFile(malformedBundlePath, JSON.stringify({ records: [left] }));
|
||||
const malformedBundle = await runFileCli(cli, {
|
||||
const malformedBundle = await cli.run({
|
||||
arguments: [
|
||||
"match-native-functions",
|
||||
requestPath,
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
withPassiveDroppedEvents,
|
||||
} from "../../fixtures/passiveNetworkCapture.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { cliTest, runFileCli } from "../../support/cli/cliFixture.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
type Capture = {
|
||||
inspection: CompareWebCapturesInput["before"]["inspection"];
|
||||
@@ -516,9 +516,10 @@ cliTest.for(cases.filter(({ name }) => cliCaseNames.has(name)))(
|
||||
arguments: ["compare-web-captures", ...paths, "--json"],
|
||||
environment: { REA_LOG_LEVEL: "silent" },
|
||||
};
|
||||
const response = await (scenario.name === "heterogeneous HTTP omission 0"
|
||||
? cli.run(invocation)
|
||||
: runFileCli(cli, invocation));
|
||||
const response = await cli.run({
|
||||
...invocation,
|
||||
freshProcess: scenario.name === "heterogeneous HTTP omission 0",
|
||||
});
|
||||
expect(response.exitCode, response.stderr).toBe(0);
|
||||
expectComparison(response.json, scenario);
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
withPassiveDroppedEvents,
|
||||
} from "../../fixtures/passiveNetworkCapture.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { cliTest, runFileCli } from "../../support/cli/cliFixture.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
const comparisonCases = () => {
|
||||
const before = passiveNetworkCapture();
|
||||
@@ -191,9 +191,10 @@ cliTest.for(cases)(
|
||||
arguments: ["compare-web-captures", ...paths, "--json"],
|
||||
environment: { REA_LOG_LEVEL: "silent" },
|
||||
};
|
||||
const response = await (name === "added" || name === "removed"
|
||||
? cli.run(options)
|
||||
: runFileCli(cli, options));
|
||||
const response = await cli.run({
|
||||
...options,
|
||||
freshProcess: name === "added" || name === "removed",
|
||||
});
|
||||
expect(response.exitCode, response.stderr).toBe(0);
|
||||
expectComparison(response.json, name, change);
|
||||
expect(
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { expect } from "vitest";
|
||||
import { cliTest, runFileCli } from "../../support/cli/cliFixture.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import {
|
||||
protobufCaptureDescriptor,
|
||||
@@ -649,9 +649,10 @@ captureTest.for(
|
||||
"--json",
|
||||
],
|
||||
};
|
||||
const response = await (name === "valid"
|
||||
? cli.run(options)
|
||||
: runFileCli(cli, options));
|
||||
const response = await cli.run({
|
||||
...options,
|
||||
freshProcess: name === "valid",
|
||||
});
|
||||
expect(response.exitCode, response.stdout + response.stderr).toBe(0);
|
||||
const evidence = parseEvidence(response.json);
|
||||
const report = webNetworkCaptureSchema.parse(evidence.normalized_result);
|
||||
|
||||
@@ -147,7 +147,7 @@ cliTest(
|
||||
},
|
||||
};
|
||||
if (change === "declarations") {
|
||||
const output = await cli.sharedCliRun!({
|
||||
const output = await cli.run({
|
||||
arguments: [
|
||||
"inspect-web-network-capture",
|
||||
capture,
|
||||
@@ -254,7 +254,7 @@ cliTest(
|
||||
const bytes = writer.finish();
|
||||
const descriptor = join(root, `${mode}.pb`);
|
||||
await writeFile(descriptor, bytes);
|
||||
const output = await cli.sharedCliRun!({
|
||||
const output = await cli.run({
|
||||
arguments: [
|
||||
"inspect-web-network-capture",
|
||||
capture,
|
||||
|
||||
@@ -132,7 +132,7 @@ cliTest.for([
|
||||
const descriptor = join(root, `${variant}.pb`);
|
||||
const bytes = toBinary(FileDescriptorSetSchema, definition);
|
||||
await writeFile(descriptor, bytes);
|
||||
const output = await cli.sharedCliRun!({
|
||||
const output = await cli.run({
|
||||
arguments: [
|
||||
"inspect-web-network-capture",
|
||||
capture,
|
||||
@@ -222,7 +222,7 @@ cliTest(
|
||||
const definition = toBinary(FileDescriptorSetSchema, valid);
|
||||
await writeFile(capture, protobufHar(body, "application/x-protobuf"));
|
||||
await writeFile(descriptor, definition);
|
||||
const output = await cli.sharedCliRun!({
|
||||
const output = await cli.run({
|
||||
arguments: [
|
||||
"inspect-web-network-capture",
|
||||
capture,
|
||||
|
||||
@@ -30,7 +30,7 @@ cliTest(
|
||||
const path = join(root, `${index}.har`);
|
||||
const original = protobufHar(body, mime);
|
||||
await writeFile(path, original);
|
||||
const output = await cli.sharedCliRun!({
|
||||
const output = await cli.run({
|
||||
arguments: [
|
||||
"inspect-web-network-capture",
|
||||
path,
|
||||
@@ -109,7 +109,7 @@ cliTest(
|
||||
await writeFile(path, original);
|
||||
const call = await cli.sharedMcpCall!();
|
||||
for (const framing of ["auto", "raw"] as const) {
|
||||
const output = await cli.sharedCliRun!({
|
||||
const output = await cli.run({
|
||||
arguments: [
|
||||
"inspect-web-network-capture",
|
||||
path,
|
||||
@@ -233,7 +233,7 @@ cliTest(
|
||||
const mime = 'application/protobuf;encoding="binary';
|
||||
const original = protobufHar(body, mime);
|
||||
await writeFile(path, original);
|
||||
const output = await cli.sharedCliRun!({
|
||||
const output = await cli.run({
|
||||
arguments: [
|
||||
"inspect-web-network-capture",
|
||||
path,
|
||||
|
||||
@@ -117,7 +117,7 @@ cliTest(
|
||||
// Both orders cross the actual CLI adapter; other geometries reuse
|
||||
// the production MCP session and the same application workflow.
|
||||
if (scope === "same-file") {
|
||||
const output = await cli.sharedCliRun!({
|
||||
const output = await cli.run({
|
||||
arguments: [
|
||||
"inspect-web-network-capture",
|
||||
capture,
|
||||
|
||||
@@ -124,7 +124,7 @@ cliTest(
|
||||
},
|
||||
];
|
||||
const call = await cli.sharedMcpCall!();
|
||||
const run = cli.sharedCliRun!.bind(cli);
|
||||
const run = cli.run.bind(cli);
|
||||
for (const test of cases) {
|
||||
const request = {
|
||||
source: { kind: "inline", evidence: test.evidence },
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { describe, expect } from "vitest";
|
||||
|
||||
import { nativeDataTypeInputSchema } from "../../../src/domain/native/nativeDataType.js";
|
||||
import { cliTest, runFileCli } from "../../support/cli/cliFixture.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
const selectors = [
|
||||
["function", "procedure", "analyze_function"],
|
||||
@@ -101,7 +101,7 @@ describe("CLI analysis selector boundaries", () => {
|
||||
["selector", `--${option}=selector`],
|
||||
]) {
|
||||
const label = `${command} ${selection.join(" ")}`;
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
command,
|
||||
"does-not-exist",
|
||||
@@ -174,7 +174,7 @@ cliTest(
|
||||
[["--address", "0x1000"], { code: "target_unavailable" }],
|
||||
] as const) {
|
||||
const label = `inspect-native-data-type ${selection.join(" ")}`;
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-native-data-type",
|
||||
"does-not-exist",
|
||||
|
||||
@@ -16,7 +16,7 @@ import { expect } from "vitest";
|
||||
import { TextReader, Uint8ArrayWriter, ZipWriter } from "@zip.js/zip.js";
|
||||
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { cliTest, runFileCli } from "../../support/cli/cliFixture.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
import {
|
||||
analysisViewLayoutEvidence,
|
||||
@@ -119,7 +119,7 @@ cliTest(
|
||||
);
|
||||
await writeFile(path, JSON.stringify(createEvidenceBundle([parent])));
|
||||
const inspect = async (view: JsonValue) => {
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-analysis-view",
|
||||
JSON.stringify({
|
||||
@@ -177,7 +177,7 @@ cliTest(
|
||||
]);
|
||||
const original = JSON.stringify(bundle);
|
||||
await writeFile(path, original);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-analysis-view",
|
||||
JSON.stringify(request),
|
||||
@@ -216,10 +216,10 @@ cliTest(
|
||||
"--json",
|
||||
],
|
||||
};
|
||||
const rejected =
|
||||
source.kind === "inline"
|
||||
? await cli.run(invocation)
|
||||
: await runFileCli(cli, invocation);
|
||||
const rejected = await cli.run({
|
||||
...invocation,
|
||||
freshProcess: source.kind === "inline",
|
||||
});
|
||||
expect(rejected.exitCode).toBe(1);
|
||||
expect(rejected.json).toMatchObject({ code: expectedCode });
|
||||
expect(rejected.json).not.toHaveProperty("evidence_id");
|
||||
@@ -251,9 +251,10 @@ cliTest(
|
||||
};
|
||||
// Keep a fresh-process integrity check for the altered bundle while the
|
||||
// worker must reread the same path for the other invalid inputs.
|
||||
const rejected = freshProcess
|
||||
? await cli.run(invocation)
|
||||
: await runFileCli(cli, invocation);
|
||||
const rejected = await cli.run({
|
||||
...invocation,
|
||||
freshProcess: freshProcess,
|
||||
});
|
||||
expect(rejected.exitCode).toBe(1);
|
||||
expect(rejected.json).toMatchObject({ code: expectedCode });
|
||||
expect(rejected.json).not.toHaveProperty("evidence_id");
|
||||
@@ -280,7 +281,7 @@ cliTest(
|
||||
view: { kind: "native", facet: "callees", offset: 0, limit: 1 },
|
||||
}),
|
||||
);
|
||||
const response = await runFileCli(cli, {
|
||||
const response = await cli.run({
|
||||
arguments: ["inspect-analysis-view", request, "--json"],
|
||||
});
|
||||
expect(response.exitCode).toBe(0);
|
||||
@@ -319,7 +320,7 @@ cliTest(
|
||||
const expected = await inspectAnalysisView(input);
|
||||
if (!expected.ok) throw expected.error;
|
||||
await writeFile(request, JSON.stringify(input));
|
||||
const response = await runFileCli(cli, {
|
||||
const response = await cli.run({
|
||||
arguments: ["inspect-analysis-view", request, "--json"],
|
||||
});
|
||||
expect(response.exitCode).toBe(0);
|
||||
@@ -360,7 +361,7 @@ cliTest(
|
||||
view: { kind: "api-surface", include_query_parameter_names: true },
|
||||
}),
|
||||
);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-analysis-view", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode).toBe(0);
|
||||
@@ -390,7 +391,7 @@ cliTest(
|
||||
await writer.add("main.js", new TextReader("console.log('portable');\n"));
|
||||
await writeFile(archive, await writer.close());
|
||||
const bundlePath = join(root, "inspection-bundle.json");
|
||||
const inspected = await runFileCli(cli, {
|
||||
const inspected = await cli.run({
|
||||
arguments: [
|
||||
"inspect-artifact",
|
||||
archive,
|
||||
@@ -440,7 +441,7 @@ cliTest(
|
||||
view: { kind: "page", collection: "sections", offset: 0, limit: 20 },
|
||||
};
|
||||
await writeFile(unsupportedRequest, JSON.stringify(unsupportedInput));
|
||||
const unsupported = await runFileCli(cli, {
|
||||
const unsupported = await cli.run({
|
||||
arguments: ["inspect-analysis-view", unsupportedRequest, "--json"],
|
||||
});
|
||||
expect(unsupported.exitCode).toBe(1);
|
||||
@@ -468,7 +469,7 @@ cliTest(
|
||||
},
|
||||
}),
|
||||
);
|
||||
const projected = await runFileCli(cli, {
|
||||
const projected = await cli.run({
|
||||
arguments: ["inspect-analysis-view", request, "--json"],
|
||||
});
|
||||
expect(projected.exitCode).toBe(0);
|
||||
@@ -497,7 +498,7 @@ cliTest(
|
||||
const writer = new ZipWriter(new Uint8ArrayWriter());
|
||||
await writer.add("main.js", new TextReader("console.log(1);\n"));
|
||||
await writeFile(archive, await writer.close());
|
||||
const inspected = await runFileCli(cli, {
|
||||
const inspected = await cli.run({
|
||||
arguments: ["inspect-artifact", archive, "--json"],
|
||||
});
|
||||
expect(inspected.exitCode).toBe(0);
|
||||
@@ -541,10 +542,10 @@ cliTest(
|
||||
};
|
||||
// Exercise a valid and a malformed parent in fresh processes as well
|
||||
// as the shared worker's repeated reads of the rewritten input path.
|
||||
const reply =
|
||||
group === "manifest" && variant !== "missing"
|
||||
? await cli.run(invocation)
|
||||
: await runFileCli(cli, invocation);
|
||||
const reply = await cli.run({
|
||||
...invocation,
|
||||
freshProcess: group === "manifest" && variant !== "missing",
|
||||
});
|
||||
expect(reply.stderr).toBe("");
|
||||
if (variant === "valid") {
|
||||
expect(application.ok).toBe(true);
|
||||
@@ -599,7 +600,7 @@ cliTest(
|
||||
],
|
||||
});
|
||||
await writeFile(path, JSON.stringify(request));
|
||||
const refused = await runFileCli(cli, {
|
||||
const refused = await cli.run({
|
||||
arguments: ["inspect-analysis-view", path, "--json"],
|
||||
});
|
||||
expect(refused.exitCode).toBe(1);
|
||||
@@ -648,7 +649,7 @@ cliTest(
|
||||
view: { kind: "api-surface" },
|
||||
}),
|
||||
);
|
||||
const refused = await runFileCli(cli, {
|
||||
const refused = await cli.run({
|
||||
arguments: ["inspect-analysis-view", path, "--json"],
|
||||
environment,
|
||||
});
|
||||
@@ -679,7 +680,7 @@ cliTest(
|
||||
view: { kind: "api-surface" },
|
||||
}),
|
||||
);
|
||||
const response = await runFileCli(cli, {
|
||||
const response = await cli.run({
|
||||
arguments: ["inspect-analysis-view", path, "--json"],
|
||||
environment,
|
||||
});
|
||||
@@ -716,7 +717,7 @@ cliTest(
|
||||
detail: "summary" as const,
|
||||
filters: { evidence_id: parent.evidence_id },
|
||||
};
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-evidence-bundle",
|
||||
JSON.stringify({ path, ...options }),
|
||||
@@ -729,7 +730,7 @@ cliTest(
|
||||
if (!expected.ok) throw expected.error;
|
||||
expect(result.json).toEqual(expected.value);
|
||||
// The same shared owner selects the complete record from the portable file.
|
||||
const records = await runFileCli(cli, {
|
||||
const records = await cli.run({
|
||||
arguments: [
|
||||
"inspect-evidence-bundle",
|
||||
JSON.stringify({ path, ...options, detail: "records", limit: 1 }),
|
||||
@@ -746,7 +747,7 @@ cliTest(
|
||||
records: [parent],
|
||||
missing_evidence_links: [],
|
||||
});
|
||||
const complete = await runFileCli(cli, {
|
||||
const complete = await cli.run({
|
||||
arguments: [
|
||||
"inspect-evidence-bundle",
|
||||
JSON.stringify({ path }),
|
||||
@@ -781,7 +782,7 @@ cliTest(
|
||||
detail: "links",
|
||||
traversal: "retained-closure",
|
||||
} as const;
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-evidence-bundle",
|
||||
JSON.stringify({ path, ...options }),
|
||||
@@ -850,7 +851,7 @@ cliTest(
|
||||
};
|
||||
|
||||
{
|
||||
const json = await runFileCli(cli, {
|
||||
const json = await cli.run({
|
||||
arguments: [
|
||||
"inspect-analysis-view",
|
||||
input,
|
||||
@@ -869,7 +870,7 @@ cliTest(
|
||||
).toHaveLength(rowCount);
|
||||
}
|
||||
|
||||
const markdown = await runFileCli(cli, {
|
||||
const markdown = await cli.run({
|
||||
arguments: [
|
||||
"inspect-analysis-view",
|
||||
input,
|
||||
@@ -908,7 +909,7 @@ cliTest(
|
||||
const input = join(root, "view.json");
|
||||
await mkdir(root, { recursive: true });
|
||||
await writeFile(input, JSON.stringify(request));
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-analysis-view", input, "--json"],
|
||||
environment: {
|
||||
HOME: root,
|
||||
@@ -934,7 +935,7 @@ cliTest(
|
||||
const root = await createTestTempDirectory("rea-empty-layout-view-cli-");
|
||||
const empty = analysisViewLayoutEvidence(binaryLayoutFixture());
|
||||
for (const collection of ["sections", "symbols"] as const) {
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-analysis-view",
|
||||
JSON.stringify({
|
||||
@@ -965,7 +966,7 @@ cliTest(
|
||||
expect(result.json).not.toHaveProperty("details.issues.0.minimum");
|
||||
expect(result.json).not.toHaveProperty("details.issues.0.maximum");
|
||||
}
|
||||
const populated = await runFileCli(cli, {
|
||||
const populated = await cli.run({
|
||||
arguments: [
|
||||
"inspect-analysis-view",
|
||||
JSON.stringify({
|
||||
@@ -1022,7 +1023,7 @@ cliTest(
|
||||
view: { kind: "native", facet: "pseudocode", offset: 0, limit: 12 },
|
||||
}),
|
||||
);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-analysis-view", input, "--json"],
|
||||
environment: { HOME: root, XDG_CONFIG_HOME: root, XDG_CACHE_HOME: root },
|
||||
});
|
||||
@@ -1065,7 +1066,7 @@ cliTest(
|
||||
{ kind: "summary" },
|
||||
{ kind: "evm", facet: "bytecode_hex", offset: 0, limit: 4 },
|
||||
]) {
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-analysis-view",
|
||||
JSON.stringify({
|
||||
@@ -1087,7 +1088,7 @@ cliTest(
|
||||
|
||||
cliTest("rejects malformed JSON before projection", async ({ cli }) => {
|
||||
const root = await createTestTempDirectory("rea-analysis-view-cli-bad-");
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-analysis-view", "{", "--json"],
|
||||
environment: {
|
||||
HOME: root,
|
||||
@@ -1107,7 +1108,7 @@ cliTest("rejects a request that omits source", async ({ cli }) => {
|
||||
const root = await createTestTempDirectory("rea-analysis-view-cli-missing-");
|
||||
const input = join(root, "missing-source.json");
|
||||
await writeFile(input, JSON.stringify({ view: { kind: "summary" } }));
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-analysis-view", input, "--json"],
|
||||
environment: {
|
||||
HOME: root,
|
||||
@@ -1142,7 +1143,7 @@ for (const [view, reason] of [
|
||||
view,
|
||||
}),
|
||||
);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-analysis-view", input, "--json"],
|
||||
environment: {
|
||||
HOME: root,
|
||||
@@ -1195,7 +1196,7 @@ cliTest(
|
||||
XDG_CACHE_HOME: root,
|
||||
};
|
||||
await writeFile(capturePath, bytes);
|
||||
const capture = await runFileCli(cli, {
|
||||
const capture = await cli.run({
|
||||
arguments: ["inspect-web-network-capture", capturePath, "har", "--json"],
|
||||
environment,
|
||||
});
|
||||
@@ -1212,7 +1213,7 @@ cliTest(
|
||||
view: { kind: "api-surface" },
|
||||
}),
|
||||
);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-analysis-view", input, "--json"],
|
||||
environment,
|
||||
});
|
||||
@@ -1266,7 +1267,7 @@ cliTest(
|
||||
view,
|
||||
}),
|
||||
);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-analysis-view", requestPath, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stdout).toBe(0);
|
||||
@@ -1312,7 +1313,7 @@ cliTest(
|
||||
},
|
||||
}),
|
||||
);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-analysis-view", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stdout).toBe(0);
|
||||
@@ -1348,7 +1349,7 @@ cliTest(
|
||||
view: { kind: "environment-manifest" },
|
||||
}),
|
||||
);
|
||||
const refused = await runFileCli(cli, {
|
||||
const refused = await cli.run({
|
||||
arguments: ["inspect-analysis-view", path, "--json"],
|
||||
});
|
||||
expect(refused.exitCode).toBe(1);
|
||||
@@ -1383,7 +1384,7 @@ cliTest(
|
||||
},
|
||||
}),
|
||||
);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-analysis-view", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stdout).toBe(0);
|
||||
@@ -1457,7 +1458,7 @@ cliTest.for([
|
||||
view: { kind: "environment-state" },
|
||||
});
|
||||
await writeFile(path, request);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-analysis-view", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stdout).toBe(0);
|
||||
@@ -1485,7 +1486,7 @@ cliTest(
|
||||
const root = await createTestTempDirectory("rea-manifest-chain-cli-");
|
||||
const capturePath = join(root, "capture.har");
|
||||
await writeFile(capturePath, JSON.stringify(environmentManifestHar()));
|
||||
const capture = await runFileCli(cli, {
|
||||
const capture = await cli.run({
|
||||
arguments: ["inspect-web-network-capture", capturePath, "har", "--json"],
|
||||
});
|
||||
expect(capture.exitCode, capture.stdout).toBe(0);
|
||||
@@ -1498,7 +1499,7 @@ cliTest(
|
||||
view: { kind: "environment-manifest" },
|
||||
}),
|
||||
);
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-analysis-view", input, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stdout).toBe(0);
|
||||
|
||||
@@ -68,16 +68,8 @@ const REQUIRED_NUMERIC_FIELDS = [
|
||||
"inspect-web-page options.observationMs",
|
||||
"observe-web-execution options.observationMs",
|
||||
"extract-firmware options.maxDepth",
|
||||
"trace-web-source-location args.line",
|
||||
"cutter-command args.expectedGeneration",
|
||||
"observe-native-ui options.maxNodes",
|
||||
"inspect-web-network-capture options.record",
|
||||
"environment-serve options.port",
|
||||
] as const;
|
||||
|
||||
/** Decimal argv tokens the parser admits as numbers, for fields advertised as strings. */
|
||||
const PROBE_TOKENS = ["0", "1"] as const;
|
||||
|
||||
describe("decimal integer CLI admission", () => {
|
||||
it("rejects 0x10 and 1e3 for every numeric input and keeps an in-range decimal", () => {
|
||||
const cli = createCli({});
|
||||
@@ -91,6 +83,7 @@ describe("decimal integer CLI admission", () => {
|
||||
const advertised = Schema.toJsonSchema(parser);
|
||||
if (!isRecord(advertised) || !isRecord(advertised.properties)) continue;
|
||||
for (const [name, property] of Object.entries(advertised.properties)) {
|
||||
if (!numericBranches(property)) continue;
|
||||
const field = parser.shape[name];
|
||||
if (field === undefined) {
|
||||
failures.push(
|
||||
@@ -99,31 +92,11 @@ describe("decimal integer CLI admission", () => {
|
||||
continue;
|
||||
}
|
||||
const label = `${path} ${group}.${name}`;
|
||||
// A repeated option parses each argv token as one array item.
|
||||
const repeated = isRecord(property) && property.type === "array";
|
||||
const item = repeated ? property.items : property;
|
||||
const isolated = z.object({ [name]: field });
|
||||
const read = (token: string): unknown => {
|
||||
const parsed =
|
||||
group === "options"
|
||||
? Parser.parse([flagName(name), token], { options: isolated })
|
||||
.options[name]
|
||||
: Parser.parse([token], { args: isolated }).args[name];
|
||||
return repeated && Array.isArray(parsed) ? parsed[0] : parsed;
|
||||
};
|
||||
const readsNumber = (token: string): boolean => {
|
||||
try {
|
||||
return typeof read(token) === "number";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
// String-advertised token grammars (for example --port) still count.
|
||||
const probed = PROBE_TOKENS.find(readsNumber);
|
||||
if (!numericBranches(item) && probed === undefined) continue;
|
||||
let validate: ((value: unknown) => boolean) | undefined;
|
||||
try {
|
||||
validate = ajv.compile(item as Parameters<Ajv2020["compile"]>[0]);
|
||||
validate = ajv.compile(
|
||||
property as Parameters<Ajv2020["compile"]>[0],
|
||||
);
|
||||
} catch (error) {
|
||||
failures.push(
|
||||
`${label}: Ajv could not compile the advertised schema (${error instanceof Error ? error.message : String(error)})`,
|
||||
@@ -134,6 +107,15 @@ describe("decimal integer CLI admission", () => {
|
||||
if (validate(token))
|
||||
failures.push(`${label}: advertised schema accepts ${token}`);
|
||||
}
|
||||
const isolated = z.object({ [name]: field });
|
||||
const read = (token: string): unknown => {
|
||||
if (group === "options") {
|
||||
return Parser.parse([flagName(name), token], {
|
||||
options: isolated,
|
||||
}).options[name];
|
||||
}
|
||||
return Parser.parse([token], { args: isolated }).args[name];
|
||||
};
|
||||
for (const token of NON_DECIMAL) {
|
||||
try {
|
||||
read(token);
|
||||
@@ -142,9 +124,7 @@ describe("decimal integer CLI admission", () => {
|
||||
// Decimal tokens are the only numeric spelling.
|
||||
}
|
||||
}
|
||||
const candidate =
|
||||
bounds(item)[0]?.min ??
|
||||
(probed === undefined ? undefined : Number(probed));
|
||||
const candidate = bounds(property)[0]?.min;
|
||||
if (candidate === undefined) {
|
||||
failures.push(`${label}: no in-range decimal candidate`);
|
||||
continue;
|
||||
@@ -158,7 +138,8 @@ describe("decimal integer CLI admission", () => {
|
||||
);
|
||||
continue;
|
||||
}
|
||||
const admitsLeadingZeros = JSON.stringify(item).includes("^[0-9]+$");
|
||||
const admitsLeadingZeros =
|
||||
JSON.stringify(property).includes("^[0-9]+$");
|
||||
try {
|
||||
const parsed = read(`0${decimal}`);
|
||||
if (parsed !== candidate)
|
||||
@@ -171,10 +152,9 @@ describe("decimal integer CLI admission", () => {
|
||||
`${label}: leading-zero decimal was rejected (${error instanceof Error ? error.message : String(error)})`,
|
||||
);
|
||||
}
|
||||
// Argv supplies text, so the advertisement must admit the token itself.
|
||||
if (!validate(decimal))
|
||||
if (!(validate(decimal) || validate(candidate)))
|
||||
failures.push(
|
||||
`${label}: advertised schema rejects in-range token "${decimal}"`,
|
||||
`${label}: advertised schema rejects in-range ${decimal}`,
|
||||
);
|
||||
checked.push(label);
|
||||
}
|
||||
@@ -188,60 +168,3 @@ describe("decimal integer CLI admission", () => {
|
||||
expect(failures).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("decimal integer CLI advertisement", () => {
|
||||
const schemaOf = async (command: string): Promise<unknown> => {
|
||||
let stdout = "";
|
||||
let exitCode = 0;
|
||||
await createCli({}).serve([command, "--schema", "--json"], {
|
||||
env: {},
|
||||
exit: (code) => {
|
||||
exitCode = code;
|
||||
},
|
||||
stdout: (text) => {
|
||||
stdout += text;
|
||||
},
|
||||
});
|
||||
expect(exitCode, command).toBe(0);
|
||||
return JSON.parse(stdout) as unknown;
|
||||
};
|
||||
|
||||
it("publishes the token grammar in --schema for arguments and options", async () => {
|
||||
const location = await schemaOf("trace-web-source-location");
|
||||
expect(location).toMatchObject({
|
||||
args: {
|
||||
properties: {
|
||||
line: {
|
||||
description: "One-based generated line",
|
||||
anyOf: [
|
||||
{ type: "integer", minimum: 1 },
|
||||
{ type: "string", pattern: "^[0-9]+$" },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(await schemaOf("inspect-web-network-capture")).toMatchObject({
|
||||
options: {
|
||||
properties: {
|
||||
record: {
|
||||
type: "array",
|
||||
items: {
|
||||
anyOf: [
|
||||
{ type: "integer", minimum: 0 },
|
||||
{ type: "string", pattern: "^[0-9]+$" },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(await schemaOf("environment-serve")).toMatchObject({
|
||||
options: {
|
||||
properties: {
|
||||
port: { type: "string", pattern: "^(?:0|[1-9]\\d*)$" },
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -739,12 +739,7 @@ describe("reference-source outline-depth input schema", () => {
|
||||
const cli = createCli({});
|
||||
const advertised = await advertisedSchema(cli, "import-reference-source");
|
||||
const outlineDepth = advertised.options?.properties?.outlineDepth;
|
||||
expect(outlineDepth).toMatchObject({
|
||||
anyOf: [
|
||||
{ type: "integer", minimum: 0 },
|
||||
{ type: "string", pattern: "^[0-9]+$" },
|
||||
],
|
||||
});
|
||||
expect(outlineDepth).toMatchObject({ type: "integer", minimum: 0 });
|
||||
|
||||
const parser = parserSchema(cli, "import-reference-source", "options");
|
||||
expect(parser?.safeParse({}).success).toBe(true);
|
||||
@@ -790,10 +785,7 @@ describe("reference-source outline-depth input schema", () => {
|
||||
if (parsed === undefined || !parsed.success)
|
||||
throw new Error(`Expected outline-depth token ${token} to parse`);
|
||||
expect(validate(parsed.data)).toBe(true);
|
||||
expect(validate({ outlineDepth: token, detail: "summary" })).toBe(true);
|
||||
}
|
||||
for (const token of ["0x2", "2e0", "-1"])
|
||||
expect(validate({ outlineDepth: token, detail: "summary" })).toBe(false);
|
||||
expect(validate({ outlineDepth: -1 })).toBe(false);
|
||||
expect(validate({ outlineDepth: 9007199254740992 })).toBe(false);
|
||||
expect(validate({ outlineDepth: 2 })).toBe(false);
|
||||
|
||||
@@ -4,11 +4,7 @@ import { join } from "node:path";
|
||||
import { describe, expect } from "vitest";
|
||||
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import {
|
||||
cliTest,
|
||||
runFileCli,
|
||||
type CliResult,
|
||||
} from "../../support/cli/cliFixture.js";
|
||||
import { cliTest, type CliResult } from "../../support/cli/cliFixture.js";
|
||||
|
||||
const SESSION_ID = "27d3e3f1-f1e5-49ae-91ec-95af1f343a5a";
|
||||
const NON_INTEGER_TEXT = [
|
||||
@@ -132,7 +128,7 @@ describe("CLI integer arguments", () => {
|
||||
cliTest.for(NON_INTEGER_OPTION_TEXT)(
|
||||
"rejects %j as an Android reference overload index",
|
||||
async (value, { cli }) => {
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"trace-android-references",
|
||||
"/nonexistent/missing.apk",
|
||||
@@ -149,7 +145,7 @@ describe("CLI integer arguments", () => {
|
||||
cliTest.for(NON_INTEGER_TEXT)(
|
||||
"rejects %j as a Cutter expected generation",
|
||||
async (value, { cli }) => {
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["cutter-command", SESSION_ID, value, "pdi", "--json"],
|
||||
});
|
||||
|
||||
@@ -160,7 +156,7 @@ describe("CLI integer arguments", () => {
|
||||
cliTest.for(NON_INTEGER_TEXT)(
|
||||
"rejects %j as a web module script index",
|
||||
async (value, { cli }) => {
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"trace-web-module-imports",
|
||||
"/nonexistent/manifest.json",
|
||||
@@ -176,7 +172,7 @@ describe("CLI integer arguments", () => {
|
||||
cliTest.for(NON_INTEGER_OPTION_TEXT)(
|
||||
"rejects %j in a repeated web capture record selector",
|
||||
async (value, { cli }) => {
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-web-network-capture",
|
||||
"/nonexistent/capture.qlog",
|
||||
@@ -206,7 +202,7 @@ describe("byte-entropy window options", () => {
|
||||
cliTest.for(values)(
|
||||
`rejects %j as a byte-entropy ${flag} value`,
|
||||
async (value, { cli }) => {
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-byte-entropy",
|
||||
await entropySample(),
|
||||
@@ -225,7 +221,7 @@ describe("byte-entropy window options", () => {
|
||||
async ({ cli }) => {
|
||||
const path = await entropySample();
|
||||
const window = async (...flags: string[]): Promise<unknown> => {
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-byte-entropy", path, ...flags, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stdout).toBe(0);
|
||||
@@ -271,7 +267,7 @@ describe("pyc and BEAM limit options", () => {
|
||||
cliTest.for(values)(
|
||||
`rejects %j as ${command} ${flag}`,
|
||||
async (value, { cli }) => {
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
command,
|
||||
"/nonexistent/missing-artifact",
|
||||
@@ -289,7 +285,7 @@ describe("pyc and BEAM limit options", () => {
|
||||
["inspect-python-pyc", "--max-depth=016"],
|
||||
["inspect-erlang-beam", "--max-chunks=016"],
|
||||
] as const)("keeps a decimal %s limit", async ([command, flag], { cli }) => {
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [command, "/nonexistent/missing-artifact", flag, "--json"],
|
||||
});
|
||||
|
||||
@@ -322,7 +318,7 @@ describe("artifact decimal options", () => {
|
||||
cliTest.for(values)(
|
||||
`rejects %j as ${command} ${flag}`,
|
||||
async (value, { cli }) => {
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
command,
|
||||
"/nonexistent/missing-artifact",
|
||||
@@ -337,7 +333,7 @@ describe("artifact decimal options", () => {
|
||||
}
|
||||
|
||||
cliTest("keeps decimal keyed-archive offset and limit", async ({ cli }) => {
|
||||
const result = await runFileCli(cli, {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-keyed-archive",
|
||||
"/nonexistent/missing-archive",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect } from "vitest";
|
||||
|
||||
import { cliTest, runFileCli } from "../../support/cli/cliFixture.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
const invalidLogLevelMessage =
|
||||
'REA configuration is invalid: REA_LOG_LEVEL: Invalid option: expected one of "trace"|"debug"|"info"|"warn"|"error"|"fatal"|"silent".\n';
|
||||
@@ -23,7 +23,7 @@ for (const level of ["loud", "INFO"])
|
||||
};
|
||||
for (const doctor of [
|
||||
await cli.run(doctorInvocation),
|
||||
await runFileCli(cli, doctorInvocation),
|
||||
await cli.run(doctorInvocation),
|
||||
]) {
|
||||
expect(doctor.exitCode, doctor.stderr).toBe(1);
|
||||
expect(doctor.stderr).toBe("");
|
||||
@@ -83,7 +83,7 @@ cliTest(
|
||||
environment: { REA_LOG_LEVEL: "loud" },
|
||||
};
|
||||
const fresh = await cli.run(invocation);
|
||||
const shared = await runFileCli(cli, invocation);
|
||||
const shared = await cli.run(invocation);
|
||||
for (const result of [fresh, shared]) {
|
||||
expect(result.exitCode, JSON.stringify(arguments_)).toBe(1);
|
||||
expect(result.stdout, JSON.stringify(arguments_)).toBe("");
|
||||
@@ -98,7 +98,7 @@ cliTest(
|
||||
environment: { REA_LOG_LEVEL: "loud", COMPLETE: "bash" },
|
||||
};
|
||||
const fresh = await cli.run(completion);
|
||||
const shared = await runFileCli(cli, completion);
|
||||
const shared = await cli.run(completion);
|
||||
for (const result of [fresh, shared]) {
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.stdout).toBe("");
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { expect } from "vitest";
|
||||
|
||||
import {
|
||||
cliProcessEnvironment,
|
||||
cliTest,
|
||||
definedCliEnvironment,
|
||||
} from "../../support/cli/cliFixture.js";
|
||||
|
||||
// The file session and the executable run one shared one-shot owner. Compare
|
||||
// a fresh process with the session itself: `cli.run` may route either way.
|
||||
cliTest.for([
|
||||
{ name: "a formatted version report", arguments: ["--version", "--json"] },
|
||||
{
|
||||
name: "a project-scoped Amp registration refusal",
|
||||
arguments: ["mcp", "add", "--no-global", "--agent", "amp", "--json"],
|
||||
},
|
||||
{
|
||||
name: "a rejected output combination",
|
||||
arguments: ["capabilities", "--json", "--token-limit", "5"],
|
||||
},
|
||||
{
|
||||
// The session's own stdin carries its frames; a command that reads
|
||||
// stdin must see the same empty input a fresh process does.
|
||||
name: "a command reading absent stdin",
|
||||
arguments: ["compare-wasm-interfaces", "-", "--json"],
|
||||
},
|
||||
])(
|
||||
"the file session reports $name exactly as a fresh process does",
|
||||
async ({ arguments: arguments_ }, { cli, cliCommandSession }) => {
|
||||
const environment = { REA_LOG_LEVEL: "silent" };
|
||||
const fresh = await cli.run({
|
||||
arguments: arguments_,
|
||||
environment,
|
||||
freshProcess: true,
|
||||
});
|
||||
const shared = await cliCommandSession.run({
|
||||
arguments: arguments_,
|
||||
cwd: process.cwd(),
|
||||
environment: definedCliEnvironment(cliProcessEnvironment(environment)),
|
||||
});
|
||||
expect(fresh.stdout + fresh.stderr).not.toBe("");
|
||||
expect({
|
||||
exitCode: shared.exitCode,
|
||||
stdout: shared.stdout,
|
||||
stderr: shared.stderr,
|
||||
}).toEqual({
|
||||
exitCode: fresh.exitCode,
|
||||
stdout: fresh.stdout,
|
||||
stderr: fresh.stderr,
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -1,4 +1,4 @@
|
||||
import { access, copyFile, cp } from "node:fs/promises";
|
||||
import { access, copyFile, cp, symlink } from "node:fs/promises";
|
||||
import { join, resolve } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
@@ -8,9 +8,6 @@ import { unsupportedRuntimeMessage } from "../../../scripts/lib/runtime-admissio
|
||||
import { workspaceCliTest } from "../../support/cli/workspaceCliFixture.js";
|
||||
import type { TestWorkspace } from "../../support/workspace/workspaceFixture.js";
|
||||
|
||||
const CLI_OUTPUT_RUNTIME =
|
||||
"export const cliConfigurationError = () => undefined; export const parseCliOutputArguments = () => ({ validation: { ok: true } }); export const renderCliOutputArgumentError = () => ''; export const renderEmptyFilteredCliOutput = () => undefined; export const renderVersionReport = () => undefined;\n";
|
||||
|
||||
const installCliRuntime = async (
|
||||
workspace: TestWorkspace,
|
||||
cliSource: string,
|
||||
@@ -18,10 +15,18 @@ const installCliRuntime = async (
|
||||
): Promise<void> => {
|
||||
await workspace.mkdir("scripts/lib");
|
||||
await workspace.mkdir("dist");
|
||||
// The bundle's entrypoints and shared chunks form one emitted runtime.
|
||||
// The bundle's entrypoints and shared chunks form one emitted runtime. The
|
||||
// one-shot owner and its output admission stay production code; only the
|
||||
// command runtime and result output are replaced below.
|
||||
await cp("dist/cli-runtime", workspace.path("dist/cli-runtime"), {
|
||||
recursive: true,
|
||||
});
|
||||
// Production chunks import their packages from the installation root.
|
||||
await symlink(
|
||||
resolve("node_modules"),
|
||||
workspace.path("node_modules"),
|
||||
"junction",
|
||||
);
|
||||
await Promise.all([
|
||||
copyFile("scripts/rea.mjs", workspace.path("scripts/rea.mjs")),
|
||||
copyFile(
|
||||
@@ -37,7 +42,6 @@ const installCliRuntime = async (
|
||||
JSON.stringify({ type: "module", version: "1.0.0" }),
|
||||
),
|
||||
workspace.write("dist/cli-runtime/cli.js", cliSource),
|
||||
workspace.write("dist/cli-runtime/cliOutput.js", CLI_OUTPUT_RUNTIME),
|
||||
workspace.write("dist/cli-runtime/cliResultOutput.js", resultOutput),
|
||||
]);
|
||||
};
|
||||
@@ -57,11 +61,6 @@ describe("version report dispatch", () => {
|
||||
version: "1.0.0",
|
||||
}),
|
||||
);
|
||||
// Use the built production preflight, while making full CLI imports fail.
|
||||
await workspace.write(
|
||||
"dist/cli-runtime/cliOutput.js",
|
||||
`export * from ${JSON.stringify(pathToFileURL(resolve("dist/cli-runtime/cliOutput.js")).href)};\n`,
|
||||
);
|
||||
const invoke = (arguments_: readonly string[], invalidLogLevel = false) =>
|
||||
processes.run(
|
||||
process.execPath,
|
||||
@@ -112,11 +111,6 @@ describe("doctor configuration dispatch", () => {
|
||||
workspace,
|
||||
`process.stderr.write(${JSON.stringify(`${imported}\n`)}); export const closeCliResources = () => {}; export const createCli = () => ({ serve: (args, options) => options.stdout(JSON.stringify({ adapter: "cli", args })) });\n`,
|
||||
);
|
||||
// Both token ownership and output/configuration admission use production code.
|
||||
await workspace.write(
|
||||
"dist/cli-runtime/cliOutput.js",
|
||||
`export * from ${JSON.stringify(pathToFileURL(resolve("dist/cli-runtime/cliOutput.js")).href)};\n`,
|
||||
);
|
||||
const invoke = (args: readonly string[], completion = "") =>
|
||||
processes.run(
|
||||
process.execPath,
|
||||
@@ -595,19 +589,10 @@ describe("project-scoped Amp registration dispatch", () => {
|
||||
},
|
||||
);
|
||||
|
||||
const installProductionOutputRuntime = async (workspace: TestWorkspace) => {
|
||||
await installGuardRuntime(workspace);
|
||||
// The guard's structured refusal renders through production output code.
|
||||
await workspace.write(
|
||||
"dist/cli-runtime/cliOutput.js",
|
||||
`export * from ${JSON.stringify(pathToFileURL(resolve("dist/cli-runtime/cliOutput.js")).href)};\n`,
|
||||
);
|
||||
};
|
||||
|
||||
workspaceCliTest(
|
||||
"renders the Amp scope refusal as a structured document when a structured format is requested",
|
||||
async ({ processes, workspace }) => {
|
||||
await installProductionOutputRuntime(workspace);
|
||||
await installGuardRuntime(workspace);
|
||||
const invoke = (arguments_: readonly string[]) =>
|
||||
processes.run(
|
||||
process.execPath,
|
||||
|
||||
Loaded 100 of 127 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user