2026-10-06 16:26:40 +08:00
|
|
|
import type { ExecutableFormatHint } from "../domain/dosCom.js";
|
2026-10-11 09:06:29 -07:00
|
|
|
import type { BinaryArchitecture } from "../domain/binaryTargetTypes.js";
|
2026-07-12 19:32:13 +08:00
|
|
|
import type { JsonValue } from "../domain/jsonValue.js";
|
2026-10-07 21:56:58 +08:00
|
|
|
import type { DirectAnalysisDependencies } from "./DirectAnalysisDependencies.js";
|
|
|
|
|
import type { BinarySession } from "./binary/BinarySession.js";
|
2026-10-08 01:33:39 +08:00
|
|
|
import type { SessionProviderRoute } from "./binary/SessionProviderRouter.js";
|
|
|
|
|
import type { ResolvedSessionOpen } from "./binary/BinarySessionOpen.js";
|
2026-10-09 22:45:18 +08:00
|
|
|
import { silentLogger } from "../logger.js";
|
|
|
|
|
import type { Logger } from "pino";
|
2026-07-12 19:32:13 +08:00
|
|
|
import { createEvidence } from "../domain/evidence.js";
|
2026-07-14 04:18:38 +08:00
|
|
|
import type { Evidence } from "../domain/evidence.js";
|
2026-10-07 22:40:48 +08:00
|
|
|
import type { NativeToolName } from "../contracts/native/nativeToolContracts.js";
|
2026-10-14 13:58:12 +08:00
|
|
|
import {
|
|
|
|
|
ARTIFACT_ANALYSIS_OPERATIONS,
|
|
|
|
|
type ArtifactAnalysisOperation,
|
|
|
|
|
} from "../contracts/artifactToolContracts.js";
|
2026-10-06 00:50:38 +08:00
|
|
|
import {
|
|
|
|
|
isManagedToolName,
|
|
|
|
|
type ManagedToolName,
|
2026-10-07 19:22:04 +08:00
|
|
|
} from "../contracts/managed/managedToolContracts.js";
|
2026-10-12 06:29:27 +08:00
|
|
|
import {
|
|
|
|
|
EvidenceFileError,
|
|
|
|
|
EvidenceIntegrityError,
|
|
|
|
|
} from "../domain/evidenceErrors.js";
|
2026-10-06 05:57:38 +08:00
|
|
|
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
|
2026-10-10 21:04:05 -07:00
|
|
|
import type { AnalysisError } from "../domain/analysisErrorBase.js";
|
2026-10-21 04:21:16 +08:00
|
|
|
import { access, stat } from "node:fs/promises";
|
|
|
|
|
import { absoluteCallerPath } from "../filesystem/callerPath.js";
|
2026-10-12 06:29:27 +08:00
|
|
|
import { readAnalysisSnapshot } from "./binary/AnalysisSnapshotFiles.js";
|
2026-10-13 21:13:01 +08:00
|
|
|
import { requiresNativeSourceAdmissionBeforeReplay } from "./binary/AnalysisSnapshotCache.js";
|
2026-10-21 04:21:16 +08:00
|
|
|
import {
|
|
|
|
|
classifyBinaryTargetFilesystemFailure,
|
|
|
|
|
parseBinaryTarget,
|
|
|
|
|
} from "./BinaryTargetResolver.js";
|
2026-10-09 22:45:18 +08:00
|
|
|
import type { BinaryTarget } from "../domain/binaryTargetTypes.js";
|
2026-07-14 04:18:38 +08:00
|
|
|
import {
|
|
|
|
|
snapshotEvidenceForQuery,
|
|
|
|
|
snapshotMatchesTarget,
|
|
|
|
|
} from "../domain/analysisSnapshot.js";
|
2026-10-08 23:29:27 +08:00
|
|
|
import type { AnalysisProfileCommitment } from "../domain/analysisProfile.js";
|
2026-10-20 23:31:45 +08:00
|
|
|
import { profilelessNativeDispatchProfile } from "./binary/ProfilelessNativeWorkflowSnapshot.js";
|
2026-07-14 04:18:38 +08:00
|
|
|
import { err, ok, type Result } from "../domain/result.js";
|
|
|
|
|
import type { AnalysisSnapshot } from "../domain/analysisSnapshot.js";
|
2026-10-08 01:33:39 +08:00
|
|
|
import type {
|
|
|
|
|
AnalysisExecution,
|
|
|
|
|
ProviderIdentity,
|
|
|
|
|
} from "./AnalysisProvider.js";
|
2026-10-20 19:04:52 +08:00
|
|
|
import { createAnalysisExecutionEvidence } from "./AnalysisProvider.js";
|
2026-07-15 19:22:30 +08:00
|
|
|
import {
|
|
|
|
|
REA_WORKFLOW_PROVIDER,
|
|
|
|
|
workflowAnalysisProfile,
|
|
|
|
|
} from "./InvestigationProviders.js";
|
2026-10-08 23:29:27 +08:00
|
|
|
import {
|
2026-10-14 08:32:00 +08:00
|
|
|
executeComposedWorkflow,
|
2026-10-08 23:29:27 +08:00
|
|
|
createWorkflowEvidence,
|
|
|
|
|
workflowSnapshotRecord,
|
2026-10-09 00:20:46 +08:00
|
|
|
recordWorkflowUnknowns,
|
2026-10-08 23:29:27 +08:00
|
|
|
} from "./WorkflowEvidence.js";
|
2026-07-15 21:06:06 +08:00
|
|
|
import type { AnalysisProviderSelector } from "../contracts/providerSelection.js";
|
2026-07-24 13:14:24 +00:00
|
|
|
import { artifactInspectionResultSchema } from "../domain/artifactInspection.js";
|
2026-10-09 13:28:48 +08:00
|
|
|
import { resolveXrefsAddress } from "./XrefsAddressResolution.js";
|
|
|
|
|
import { AnalysisInputError } from "../domain/analysisErrorCore.js";
|
2026-10-21 04:21:16 +08:00
|
|
|
import type { BinaryTargetError } from "../domain/configurationErrors.js";
|
2026-10-09 02:49:09 -04:00
|
|
|
import {
|
|
|
|
|
directAnalysisCleanupFailure,
|
|
|
|
|
managedAnalysisCleanupFailure,
|
|
|
|
|
withSessionCleanup,
|
|
|
|
|
} from "./DirectAnalysisCleanup.js";
|
2026-10-21 04:21:16 +08:00
|
|
|
import { BUNDLE_RESOURCE_OPERATIONS } from "./binary/BundleResourceSession.js";
|
2026-07-12 06:34:27 +08:00
|
|
|
|
2026-07-13 03:26:32 +00:00
|
|
|
type DirectAnalysisTool =
|
2026-10-06 17:24:33 +08:00
|
|
|
| "annotate_native_function"
|
2026-10-06 15:48:48 +08:00
|
|
|
| "inspect_native_load_image"
|
|
|
|
|
| "read_bytes"
|
2026-10-13 08:41:45 -07:00
|
|
|
| "search_bytes"
|
2026-10-06 15:48:48 +08:00
|
|
|
| "address_to_file_offset"
|
2026-07-13 03:26:32 +00:00
|
|
|
| "binary_overview"
|
|
|
|
|
| "procedure_pseudo_code"
|
2026-07-23 23:18:17 +08:00
|
|
|
| "read_function_instructions"
|
2026-10-04 16:02:36 +08:00
|
|
|
| "inspect_native_instruction"
|
|
|
|
|
| "inspect_native_data_type"
|
|
|
|
|
| "resolve_native_call_targets"
|
2026-07-13 03:26:32 +00:00
|
|
|
| "analyze_function"
|
2026-07-25 14:29:28 +00:00
|
|
|
| "inspect_native_api"
|
2026-09-30 00:41:05 +08:00
|
|
|
| "inspect_native_dispatch_metadata"
|
2026-07-13 03:26:32 +00:00
|
|
|
| "search_strings"
|
|
|
|
|
| "search_procedures"
|
|
|
|
|
| "xrefs"
|
2026-09-30 00:41:05 +08:00
|
|
|
| "trace_feature"
|
2026-10-04 16:02:36 +08:00
|
|
|
| "trace_native_ui_action"
|
|
|
|
|
| "trace_native_values";
|
2026-07-13 03:26:32 +00:00
|
|
|
|
2026-10-17 02:56:35 +08:00
|
|
|
// These one-shot CLI questions require native utilities, not a deep engine.
|
|
|
|
|
// Runtime observation retains the ordinary session and authority checks.
|
|
|
|
|
const NATIVE_INSPECTION_OPERATIONS: ReadonlySet<string> = new Set([
|
|
|
|
|
"inspect_macho",
|
|
|
|
|
"inspect_signature",
|
|
|
|
|
"list_architectures",
|
|
|
|
|
"inspect_plist",
|
|
|
|
|
"demangle_swift",
|
|
|
|
|
] satisfies readonly NativeToolName[]);
|
|
|
|
|
|
2026-10-14 13:58:12 +08:00
|
|
|
const ARTIFACT_ANALYSIS_OPERATION_SET: ReadonlySet<string> = new Set(
|
|
|
|
|
ARTIFACT_ANALYSIS_OPERATIONS,
|
|
|
|
|
);
|
2026-07-12 08:45:35 +08:00
|
|
|
/**
|
2026-07-15 19:22:30 +08:00
|
|
|
* Open one binary, execute one tool, and always release provider resources.
|
2026-07-12 08:45:35 +08:00
|
|
|
* Unlike MCP mode, every CLI invocation is intentionally isolated and does not
|
2026-07-15 19:22:30 +08:00
|
|
|
* retain a target or provider client for a subsequent command.
|
2026-07-12 08:45:35 +08:00
|
|
|
*/
|
2026-07-12 06:34:27 +08:00
|
|
|
export const runDirectAnalysis = async (
|
2026-10-07 21:56:58 +08:00
|
|
|
dependencies: DirectAnalysisDependencies,
|
2026-07-12 06:34:27 +08:00
|
|
|
path: string,
|
2026-07-13 03:26:32 +00:00
|
|
|
tool: DirectAnalysisTool,
|
2026-07-12 06:34:27 +08:00
|
|
|
arguments_: Readonly<Record<string, JsonValue>>,
|
2026-07-14 04:18:38 +08:00
|
|
|
options: {
|
|
|
|
|
readonly logger?: Logger;
|
|
|
|
|
readonly snapshotPath?: string | undefined;
|
2026-07-14 01:12:27 +00:00
|
|
|
readonly signal?: AbortSignal;
|
2026-07-15 21:06:06 +08:00
|
|
|
readonly providerId?: AnalysisProviderSelector;
|
2026-10-06 16:26:40 +08:00
|
|
|
readonly formatHint?: ExecutableFormatHint;
|
2026-10-11 09:06:29 -07:00
|
|
|
readonly architecture?: BinaryArchitecture;
|
|
|
|
|
readonly entryPoint?: string;
|
2026-07-14 04:18:38 +08:00
|
|
|
} = {},
|
|
|
|
|
): Promise<JsonValue> =>
|
2026-07-14 01:12:27 +00:00
|
|
|
withProcessCancellation(options.signal, (signal) =>
|
2026-10-07 21:56:58 +08:00
|
|
|
runAnalysis(dependencies, path, tool, arguments_, {
|
2026-07-14 01:12:27 +00:00
|
|
|
logger: options.logger ?? silentLogger,
|
|
|
|
|
snapshotPath: options.snapshotPath,
|
|
|
|
|
signal,
|
2026-10-06 16:26:40 +08:00
|
|
|
...(options.formatHint === undefined
|
|
|
|
|
? {}
|
|
|
|
|
: { formatHint: options.formatHint }),
|
2026-10-11 09:06:29 -07:00
|
|
|
...(options.architecture === undefined
|
|
|
|
|
? {}
|
|
|
|
|
: { architecture: options.architecture }),
|
|
|
|
|
...(options.entryPoint === undefined
|
|
|
|
|
? {}
|
|
|
|
|
: { entryPoint: options.entryPoint }),
|
2026-07-15 21:06:06 +08:00
|
|
|
...(options.providerId === undefined
|
|
|
|
|
? {}
|
|
|
|
|
: { providerId: options.providerId }),
|
2026-07-14 01:12:27 +00:00
|
|
|
}),
|
|
|
|
|
);
|
2026-07-12 21:12:18 +00:00
|
|
|
|
|
|
|
|
/** Execute one provider-native semantic operation with atomic provenance. */
|
|
|
|
|
export const runProviderAnalysis = async (
|
2026-10-07 21:56:58 +08:00
|
|
|
dependencies: DirectAnalysisDependencies,
|
2026-07-18 04:11:29 +00:00
|
|
|
...[path, tool, arguments_, logger = silentLogger, signal]: readonly [
|
|
|
|
|
path: string,
|
2026-09-29 09:22:24 +08:00
|
|
|
tool: NativeToolName | ArtifactAnalysisOperation | ManagedToolName,
|
2026-07-18 04:11:29 +00:00
|
|
|
arguments_: Readonly<Record<string, JsonValue>>,
|
|
|
|
|
logger?: Logger,
|
|
|
|
|
signal?: AbortSignal,
|
|
|
|
|
]
|
2026-10-14 13:58:12 +08:00
|
|
|
): Promise<JsonValue> => {
|
|
|
|
|
if (isManagedToolName(tool))
|
|
|
|
|
return runManagedProviderAnalysis(
|
|
|
|
|
dependencies,
|
|
|
|
|
path,
|
|
|
|
|
tool,
|
|
|
|
|
arguments_,
|
|
|
|
|
signal,
|
|
|
|
|
);
|
|
|
|
|
return withProcessCancellation(signal, (operationSignal) =>
|
|
|
|
|
runAnalysis(dependencies, path, tool, arguments_, {
|
|
|
|
|
logger,
|
|
|
|
|
snapshotPath: undefined,
|
|
|
|
|
signal: operationSignal,
|
|
|
|
|
...(ARTIFACT_ANALYSIS_OPERATION_SET.has(tool)
|
|
|
|
|
? { createSession: dependencies.createArtifactBinarySession }
|
2026-10-17 02:56:35 +08:00
|
|
|
: NATIVE_INSPECTION_OPERATIONS.has(tool)
|
|
|
|
|
? { createSession: dependencies.createNativeBinarySession }
|
|
|
|
|
: {}),
|
2026-10-14 13:58:12 +08:00
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
};
|
2026-10-06 00:50:38 +08:00
|
|
|
|
|
|
|
|
/** Execute managed metadata inspection in an isolated managed-only session. */
|
|
|
|
|
export const runManagedProviderExecution = async (
|
2026-10-09 20:02:52 +08:00
|
|
|
dependencies: Pick<DirectAnalysisDependencies, "createManagedBinarySession">,
|
2026-10-06 00:50:38 +08:00
|
|
|
path: string,
|
|
|
|
|
tool: ManagedToolName,
|
|
|
|
|
signal?: AbortSignal,
|
2026-10-11 19:31:37 -05:00
|
|
|
parameters: Readonly<Record<string, JsonValue>> = {},
|
2026-10-06 00:50:38 +08:00
|
|
|
): Promise<Result<AnalysisExecution, AnalysisError>> =>
|
|
|
|
|
withProcessCancellation(signal, async (operationSignal) => {
|
2026-10-11 10:53:40 +08:00
|
|
|
const session = await dependencies.createManagedBinarySession();
|
2026-10-09 02:49:09 -04:00
|
|
|
return withSessionCleanup(
|
|
|
|
|
session,
|
|
|
|
|
async () => {
|
|
|
|
|
const opened = await session.open(path, { signal: operationSignal });
|
|
|
|
|
if (!opened.ok) return opened;
|
2026-10-11 19:31:37 -05:00
|
|
|
return await session.execute(tool, parameters, {
|
|
|
|
|
signal: operationSignal,
|
|
|
|
|
});
|
2026-10-09 02:49:09 -04:00
|
|
|
},
|
|
|
|
|
managedAnalysisCleanupFailure,
|
|
|
|
|
);
|
2026-10-06 00:50:38 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const runManagedProviderAnalysis = async (
|
2026-10-07 21:56:58 +08:00
|
|
|
dependencies: DirectAnalysisDependencies,
|
2026-10-06 00:50:38 +08:00
|
|
|
path: string,
|
|
|
|
|
tool: ManagedToolName,
|
2026-10-11 19:31:37 -05:00
|
|
|
arguments_: Readonly<Record<string, JsonValue>>,
|
2026-10-06 00:50:38 +08:00
|
|
|
signal?: AbortSignal,
|
|
|
|
|
): Promise<JsonValue> => {
|
2026-10-07 21:56:58 +08:00
|
|
|
const execution = await runManagedProviderExecution(
|
|
|
|
|
dependencies,
|
|
|
|
|
path,
|
|
|
|
|
tool,
|
|
|
|
|
signal,
|
2026-10-11 19:31:37 -05:00
|
|
|
arguments_,
|
2026-10-07 21:56:58 +08:00
|
|
|
);
|
2026-10-06 00:50:38 +08:00
|
|
|
if (!execution.ok) return cliError(execution.error);
|
|
|
|
|
const value = execution.value;
|
2026-10-20 19:04:52 +08:00
|
|
|
return createAnalysisExecutionEvidence(tool, arguments_, value, "observed");
|
2026-10-06 00:50:38 +08:00
|
|
|
};
|
2026-07-12 21:12:18 +00:00
|
|
|
|
|
|
|
|
const runAnalysis = async (
|
2026-10-07 21:56:58 +08:00
|
|
|
dependencies: DirectAnalysisDependencies,
|
2026-07-12 21:12:18 +00:00
|
|
|
path: string,
|
2026-07-16 15:56:40 +08:00
|
|
|
tool:
|
|
|
|
|
| NativeToolName
|
2026-09-29 09:22:24 +08:00
|
|
|
| ArtifactAnalysisOperation
|
2026-07-16 15:56:40 +08:00
|
|
|
| ManagedToolName
|
|
|
|
|
| DirectAnalysisTool,
|
2026-07-12 21:12:18 +00:00
|
|
|
arguments_: Readonly<Record<string, JsonValue>>,
|
2026-07-14 04:18:38 +08:00
|
|
|
options: {
|
|
|
|
|
readonly logger: Logger;
|
|
|
|
|
readonly snapshotPath: string | undefined;
|
2026-07-14 01:12:27 +00:00
|
|
|
readonly signal: AbortSignal;
|
2026-10-14 13:58:12 +08:00
|
|
|
readonly createSession?: () => BinarySession | Promise<BinarySession>;
|
2026-07-15 21:06:06 +08:00
|
|
|
readonly providerId?: AnalysisProviderSelector;
|
2026-10-06 16:26:40 +08:00
|
|
|
readonly formatHint?: ExecutableFormatHint;
|
2026-10-11 09:06:29 -07:00
|
|
|
readonly architecture?: BinaryArchitecture;
|
|
|
|
|
readonly entryPoint?: string;
|
2026-07-14 04:18:38 +08:00
|
|
|
},
|
2026-07-12 06:34:27 +08:00
|
|
|
): Promise<JsonValue> => {
|
2026-07-14 01:12:27 +00:00
|
|
|
const { logger, signal, snapshotPath } = options;
|
2026-10-14 13:58:12 +08:00
|
|
|
let session: BinarySession;
|
|
|
|
|
if (options.createSession !== undefined) {
|
|
|
|
|
session = await options.createSession();
|
|
|
|
|
} else {
|
|
|
|
|
const config = dependencies.readConfiguration();
|
|
|
|
|
if (!config.ok) return cliError(config.error);
|
2026-10-17 03:11:32 +08:00
|
|
|
session = await (
|
|
|
|
|
tool === "inspect_native_dispatch_metadata" && snapshotPath === undefined
|
|
|
|
|
? dependencies.createNativeDispatchBinarySession
|
|
|
|
|
: dependencies.createBinarySession
|
|
|
|
|
)(config.value, logger);
|
2026-10-14 13:58:12 +08:00
|
|
|
}
|
2026-10-09 02:49:09 -04:00
|
|
|
return withSessionCleanup(
|
|
|
|
|
session,
|
|
|
|
|
async () => {
|
2026-10-21 04:21:16 +08:00
|
|
|
let isDirectory = false;
|
|
|
|
|
if (BUNDLE_RESOURCE_OPERATIONS.has(tool)) {
|
|
|
|
|
const inspected = await inspectBundleResourceInput(path);
|
|
|
|
|
if (!inspected.ok) return cliError(inspected.error);
|
|
|
|
|
isDirectory = inspected.value;
|
|
|
|
|
}
|
|
|
|
|
if (BUNDLE_RESOURCE_OPERATIONS.has(tool) && isDirectory) {
|
|
|
|
|
const opened = await session.openBundleResources(path, { signal });
|
|
|
|
|
if (!opened.ok) return cliError(opened.error);
|
|
|
|
|
const execution = await session.execute(tool, arguments_, { signal });
|
|
|
|
|
if (!execution.ok) return cliError(execution.error);
|
|
|
|
|
return createAnalysisExecutionEvidence(
|
|
|
|
|
tool,
|
|
|
|
|
arguments_,
|
|
|
|
|
execution.value,
|
|
|
|
|
"observed",
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-10-09 02:49:09 -04:00
|
|
|
const prepared = await prepareSnapshot({
|
|
|
|
|
path,
|
|
|
|
|
snapshotPath,
|
2026-10-10 00:16:04 +08:00
|
|
|
signal,
|
2026-10-09 02:49:09 -04:00
|
|
|
...(options.formatHint === undefined
|
|
|
|
|
? {}
|
|
|
|
|
: { formatHint: options.formatHint }),
|
2026-10-11 09:06:29 -07:00
|
|
|
...(options.architecture === undefined
|
|
|
|
|
? {}
|
|
|
|
|
: { architecture: options.architecture }),
|
|
|
|
|
...(options.entryPoint === undefined
|
|
|
|
|
? {}
|
|
|
|
|
: { entryPoint: options.entryPoint }),
|
2026-10-09 02:49:09 -04:00
|
|
|
});
|
|
|
|
|
if (!prepared.ok) return cliError(prepared.error);
|
|
|
|
|
const { snapshot } = prepared.value;
|
|
|
|
|
let resolvedTarget: ResolvedSessionOpen | undefined;
|
|
|
|
|
if (snapshot !== undefined && prepared.value.target !== undefined) {
|
|
|
|
|
const preview = await session.previewTarget(prepared.value.target, {
|
|
|
|
|
signal,
|
|
|
|
|
snapshot,
|
|
|
|
|
...(options.formatHint === undefined
|
|
|
|
|
? {}
|
|
|
|
|
: { formatHint: options.formatHint }),
|
2026-10-11 09:06:29 -07:00
|
|
|
...(options.architecture === undefined
|
|
|
|
|
? {}
|
|
|
|
|
: { architecture: options.architecture }),
|
|
|
|
|
...(options.entryPoint === undefined
|
|
|
|
|
? {}
|
|
|
|
|
: { entryPoint: options.entryPoint }),
|
2026-10-09 02:49:09 -04:00
|
|
|
...(options.providerId === undefined
|
|
|
|
|
? {}
|
|
|
|
|
: { providerId: options.providerId }),
|
|
|
|
|
});
|
|
|
|
|
if (!preview.ok) return cliError(preview.error);
|
|
|
|
|
resolvedTarget = preview.value;
|
|
|
|
|
const route = preview.value.route;
|
2026-10-20 23:31:45 +08:00
|
|
|
const bindingProfile =
|
|
|
|
|
route.profile ?? nativeDispatchBindingProfile(route, tool);
|
2026-10-09 02:49:09 -04:00
|
|
|
const evidenceProfile = analysisProfileForRoute(route, tool);
|
2026-10-13 21:13:01 +08:00
|
|
|
// A parsed Windows path does not prove Ghidra's native snapshot owner
|
|
|
|
|
// admitted the selected source; CLI replay must wait for that client.
|
2026-10-09 02:49:09 -04:00
|
|
|
if (
|
|
|
|
|
bindingProfile !== undefined &&
|
|
|
|
|
evidenceProfile !== undefined &&
|
2026-10-13 21:13:01 +08:00
|
|
|
!requiresNativeSourceAdmissionBeforeReplay(
|
|
|
|
|
route.binding?.identity.id,
|
|
|
|
|
) &&
|
2026-10-09 02:49:09 -04:00
|
|
|
allowsSnapshotReplay(route, tool)
|
|
|
|
|
) {
|
|
|
|
|
const cached = snapshotEvidenceForQuery(snapshot, {
|
|
|
|
|
target: preview.value.target,
|
|
|
|
|
bindingProfile,
|
|
|
|
|
operation: tool,
|
|
|
|
|
parameters: arguments_,
|
|
|
|
|
provider: isWorkflowEvidenceTool(tool)
|
|
|
|
|
? REA_WORKFLOW_PROVIDER
|
|
|
|
|
: providerIdentityForRoute(route, tool),
|
|
|
|
|
evidenceProfile,
|
|
|
|
|
});
|
|
|
|
|
if (cached !== undefined) return cached;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const openOptions = {
|
2026-10-08 01:33:39 +08:00
|
|
|
signal,
|
|
|
|
|
...(options.formatHint === undefined
|
|
|
|
|
? {}
|
|
|
|
|
: { formatHint: options.formatHint }),
|
2026-10-11 09:06:29 -07:00
|
|
|
...(options.architecture === undefined
|
|
|
|
|
? {}
|
|
|
|
|
: { architecture: options.architecture }),
|
|
|
|
|
...(options.entryPoint === undefined
|
|
|
|
|
? {}
|
|
|
|
|
: { entryPoint: options.entryPoint }),
|
2026-10-09 02:49:09 -04:00
|
|
|
...(snapshot === undefined ? {} : { snapshot }),
|
2026-10-08 01:33:39 +08:00
|
|
|
...(options.providerId === undefined
|
|
|
|
|
? {}
|
|
|
|
|
: { providerId: options.providerId }),
|
2026-10-09 02:49:09 -04:00
|
|
|
};
|
|
|
|
|
const opened =
|
|
|
|
|
resolvedTarget === undefined
|
|
|
|
|
? await session.open(path, openOptions)
|
|
|
|
|
: await session.openResolvedTarget(resolvedTarget, openOptions);
|
|
|
|
|
if (!opened.ok) return cliError(opened.error);
|
|
|
|
|
const evidenceProfile = analysisProfileForEvidence(session, tool);
|
2026-10-20 08:08:08 -04:00
|
|
|
const { output, evidence, cachePolicy } = await executeAnalysisTool({
|
2026-10-09 02:49:09 -04:00
|
|
|
session,
|
|
|
|
|
openedTarget: opened.value,
|
|
|
|
|
tool,
|
|
|
|
|
arguments: arguments_,
|
|
|
|
|
signal,
|
|
|
|
|
evidenceProfile,
|
2026-10-08 01:33:39 +08:00
|
|
|
});
|
2026-10-09 02:49:09 -04:00
|
|
|
if (evidence !== undefined) {
|
|
|
|
|
const recorded = session.recordEvidence(evidence);
|
2026-10-10 00:21:19 +08:00
|
|
|
if (!recorded.ok)
|
|
|
|
|
return cliError(recorded.error.retainPartialObservation(evidence));
|
2026-10-09 02:49:09 -04:00
|
|
|
if (isWorkflowEvidenceTool(tool)) {
|
|
|
|
|
const unknowns = recordWorkflowUnknowns({
|
|
|
|
|
name: tool,
|
|
|
|
|
result: evidence.normalized_result,
|
|
|
|
|
evidenceId: evidence.evidence_id,
|
|
|
|
|
recordUnknown: (unknown) => session.recordUnknown(unknown),
|
|
|
|
|
});
|
2026-10-10 00:21:19 +08:00
|
|
|
if (!unknowns.ok)
|
|
|
|
|
return cliError(unknowns.error.retainPartialObservation(evidence));
|
2026-10-09 02:49:09 -04:00
|
|
|
}
|
|
|
|
|
}
|
2026-10-08 01:33:39 +08:00
|
|
|
if (
|
2026-10-09 02:49:09 -04:00
|
|
|
isWorkflowEvidenceTool(tool) &&
|
|
|
|
|
tool !== "trace_native_ui_action" &&
|
|
|
|
|
snapshotPath !== undefined &&
|
|
|
|
|
evidence !== undefined &&
|
|
|
|
|
session.allowsSnapshotReplay(tool)
|
2026-10-08 01:33:39 +08:00
|
|
|
) {
|
2026-10-20 08:08:08 -04:00
|
|
|
const workflowRecord = workflowSnapshotRecord(
|
|
|
|
|
evidence,
|
|
|
|
|
tool,
|
2026-10-20 23:31:45 +08:00
|
|
|
evidenceProfile,
|
2026-10-20 08:08:08 -04:00
|
|
|
cachePolicy,
|
|
|
|
|
);
|
2026-10-09 02:49:09 -04:00
|
|
|
if (workflowRecord !== undefined) {
|
|
|
|
|
const recorded = session.recordWorkflowSnapshot(workflowRecord);
|
2026-10-10 00:21:19 +08:00
|
|
|
if (!recorded.ok)
|
|
|
|
|
return cliError(recorded.error.retainPartialObservation(evidence));
|
2026-10-09 02:49:09 -04:00
|
|
|
}
|
2026-10-08 01:33:39 +08:00
|
|
|
}
|
2026-10-09 02:49:09 -04:00
|
|
|
if (
|
|
|
|
|
tool !== "trace_native_ui_action" &&
|
|
|
|
|
snapshotPath !== undefined &&
|
|
|
|
|
evidence !== undefined
|
|
|
|
|
) {
|
|
|
|
|
const snapshot = session.exportAnalysisSnapshot();
|
2026-10-10 00:21:19 +08:00
|
|
|
if (!snapshot.ok)
|
|
|
|
|
return cliError(snapshot.error.retainPartialObservation(evidence));
|
2026-10-12 06:29:27 +08:00
|
|
|
const written = await session.writeAnalysisSnapshot(
|
2026-10-09 02:49:09 -04:00
|
|
|
snapshot.value,
|
|
|
|
|
snapshotPath,
|
|
|
|
|
true,
|
|
|
|
|
);
|
2026-10-12 06:29:27 +08:00
|
|
|
if (!written.ok) {
|
|
|
|
|
const recovered =
|
|
|
|
|
written.error instanceof EvidenceFileError &&
|
|
|
|
|
written.error.published !== undefined &&
|
|
|
|
|
(await session.closeWriteResources()).ok;
|
|
|
|
|
if (!recovered)
|
|
|
|
|
return cliError(written.error.retainPartialObservation(evidence));
|
|
|
|
|
}
|
2026-10-08 23:29:27 +08:00
|
|
|
}
|
2026-10-09 02:49:09 -04:00
|
|
|
return output;
|
|
|
|
|
},
|
|
|
|
|
directAnalysisCleanupFailure,
|
|
|
|
|
);
|
2026-07-12 06:34:27 +08:00
|
|
|
};
|
2026-07-13 11:51:37 +00:00
|
|
|
|
2026-07-18 04:11:29 +00:00
|
|
|
const executeAnalysisTool = async (input: {
|
2026-10-07 21:56:58 +08:00
|
|
|
readonly session: BinarySession;
|
2026-07-18 04:11:29 +00:00
|
|
|
readonly openedTarget: BinaryTarget;
|
|
|
|
|
readonly tool:
|
|
|
|
|
| NativeToolName
|
2026-09-29 09:22:24 +08:00
|
|
|
| ArtifactAnalysisOperation
|
2026-07-18 04:11:29 +00:00
|
|
|
| ManagedToolName
|
|
|
|
|
| DirectAnalysisTool;
|
|
|
|
|
readonly arguments: Readonly<Record<string, JsonValue>>;
|
|
|
|
|
readonly signal: AbortSignal;
|
|
|
|
|
readonly evidenceProfile: AnalysisProfileCommitment | undefined;
|
2026-10-20 08:08:08 -04:00
|
|
|
}): Promise<{
|
|
|
|
|
readonly output: JsonValue;
|
|
|
|
|
readonly evidence?: Evidence;
|
|
|
|
|
readonly cachePolicy?: "live";
|
|
|
|
|
}> => {
|
2026-07-18 04:11:29 +00:00
|
|
|
const { session, tool, signal, evidenceProfile } = input;
|
2026-10-07 01:47:05 +08:00
|
|
|
if (tool === "analyze_function") {
|
2026-10-11 13:07:54 +08:00
|
|
|
const { executeFunctionAnalysisEvidence } =
|
|
|
|
|
await import("./FunctionAnalysisEvidence.js");
|
2026-10-07 01:47:05 +08:00
|
|
|
const result = await executeFunctionAnalysisEvidence(
|
|
|
|
|
session,
|
|
|
|
|
input.arguments,
|
|
|
|
|
input.openedTarget,
|
|
|
|
|
{ signal },
|
|
|
|
|
);
|
|
|
|
|
return result.ok
|
|
|
|
|
? { output: result.value, evidence: result.value }
|
|
|
|
|
: { output: cliError(result.error) };
|
|
|
|
|
}
|
2026-07-18 04:11:29 +00:00
|
|
|
if (
|
|
|
|
|
tool === "binary_overview" ||
|
2026-07-25 14:29:28 +00:00
|
|
|
tool === "inspect_native_api" ||
|
2026-09-30 00:41:05 +08:00
|
|
|
tool === "inspect_native_dispatch_metadata" ||
|
|
|
|
|
tool === "trace_feature" ||
|
2026-10-04 16:02:36 +08:00
|
|
|
tool === "trace_native_values" ||
|
2026-09-30 00:41:05 +08:00
|
|
|
tool === "trace_native_ui_action"
|
2026-07-18 04:11:29 +00:00
|
|
|
) {
|
2026-10-11 13:07:54 +08:00
|
|
|
const { EnhancedTools } = await import("./EnhancedTools.js");
|
2026-10-14 08:32:00 +08:00
|
|
|
const result = await executeComposedWorkflow(session, (observed) =>
|
|
|
|
|
new EnhancedTools(observed).execute(tool, input.arguments, signal),
|
2026-07-18 04:11:29 +00:00
|
|
|
);
|
|
|
|
|
if (!result.ok) return { output: cliError(result.error) };
|
2026-10-08 23:29:27 +08:00
|
|
|
const evidence = createWorkflowEvidence({
|
|
|
|
|
target: input.openedTarget,
|
2026-10-07 01:47:05 +08:00
|
|
|
operation: tool,
|
|
|
|
|
parameters: input.arguments,
|
2026-10-14 08:32:00 +08:00
|
|
|
result: result.value.result,
|
2026-10-08 23:29:27 +08:00
|
|
|
upstreamProfile: evidenceProfile,
|
2026-10-14 08:32:00 +08:00
|
|
|
upstreamLimitations: result.value.limitations,
|
2026-10-07 01:47:05 +08:00
|
|
|
});
|
2026-10-20 08:08:08 -04:00
|
|
|
return {
|
|
|
|
|
output: evidence,
|
|
|
|
|
evidence,
|
|
|
|
|
...(result.value.cachePolicy === undefined
|
|
|
|
|
? {}
|
|
|
|
|
: { cachePolicy: result.value.cachePolicy }),
|
|
|
|
|
};
|
2026-07-18 04:11:29 +00:00
|
|
|
}
|
2026-10-09 13:28:48 +08:00
|
|
|
let result = await session.execute(tool, input.arguments, { signal });
|
|
|
|
|
if (
|
|
|
|
|
tool === "xrefs" &&
|
|
|
|
|
typeof input.arguments.address === "string" &&
|
|
|
|
|
!result.ok &&
|
|
|
|
|
result.error instanceof AnalysisInputError &&
|
|
|
|
|
!result.error.cleanupIncomplete &&
|
|
|
|
|
result.error.issues.length > 0 &&
|
|
|
|
|
result.error.issues.every(
|
|
|
|
|
({ path, reason }) =>
|
|
|
|
|
path.length === 1 &&
|
|
|
|
|
path[0] === "address" &&
|
|
|
|
|
reason === "invalid_format",
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
const address = await resolveXrefsAddress(
|
|
|
|
|
session,
|
|
|
|
|
input.arguments.address,
|
|
|
|
|
signal,
|
|
|
|
|
);
|
|
|
|
|
if (!address.ok) return { output: cliError(address.error) };
|
|
|
|
|
if (address.value !== input.arguments.address)
|
|
|
|
|
result = await session.execute(
|
|
|
|
|
tool,
|
|
|
|
|
{ ...input.arguments, address: address.value },
|
|
|
|
|
{ signal },
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-07-18 04:11:29 +00:00
|
|
|
if (!result.ok) return { output: cliError(result.error) };
|
|
|
|
|
const evidence = createEvidence(
|
2026-10-21 04:21:16 +08:00
|
|
|
result.value.subject === undefined
|
|
|
|
|
? input.openedTarget
|
|
|
|
|
: (result.value.subject ?? undefined),
|
2026-07-18 04:11:29 +00:00
|
|
|
result.value.provider,
|
|
|
|
|
{
|
|
|
|
|
operation: tool,
|
|
|
|
|
parameters: input.arguments,
|
|
|
|
|
result: result.value.result,
|
|
|
|
|
...(result.value.analysisProfile === undefined
|
|
|
|
|
? {}
|
|
|
|
|
: { analysisProfile: result.value.analysisProfile }),
|
|
|
|
|
rawResult: result.value.rawResult,
|
|
|
|
|
limitations: result.value.limitations,
|
2026-10-12 05:56:15 +08:00
|
|
|
...(result.value.relationshipCoverage === undefined
|
|
|
|
|
? {}
|
|
|
|
|
: { relationshipCoverage: result.value.relationshipCoverage }),
|
2026-07-18 04:11:29 +00:00
|
|
|
locations: result.value.locations,
|
2026-07-24 13:14:24 +00:00
|
|
|
evidenceLinks:
|
|
|
|
|
tool === "inspect_artifact"
|
|
|
|
|
? artifactInspectionResultSchema.parse(result.value.result)
|
|
|
|
|
.evidence_links
|
|
|
|
|
: [],
|
2026-07-18 04:11:29 +00:00
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
return { output: evidence, evidence };
|
|
|
|
|
};
|
|
|
|
|
|
2026-07-14 01:12:27 +00:00
|
|
|
const withProcessCancellation = async <Value>(
|
|
|
|
|
suppliedSignal: AbortSignal | undefined,
|
|
|
|
|
operation: (signal: AbortSignal) => Promise<Value>,
|
|
|
|
|
): Promise<Value> => {
|
|
|
|
|
if (suppliedSignal !== undefined) return operation(suppliedSignal);
|
|
|
|
|
const controller = new AbortController();
|
|
|
|
|
const cancel = (): void => controller.abort();
|
2026-10-09 10:15:23 +05:30
|
|
|
// Package runners can forward a terminal interrupt after REA already received
|
|
|
|
|
// it directly. Keep both guards installed until provider cleanup has settled.
|
|
|
|
|
process.on("SIGINT", cancel);
|
|
|
|
|
process.on("SIGTERM", cancel);
|
2026-07-14 01:12:27 +00:00
|
|
|
try {
|
|
|
|
|
return await operation(controller.signal);
|
|
|
|
|
} finally {
|
|
|
|
|
process.off("SIGINT", cancel);
|
2026-10-09 10:15:23 +05:30
|
|
|
process.off("SIGTERM", cancel);
|
2026-07-14 01:12:27 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-07-14 04:18:38 +08:00
|
|
|
const prepareSnapshot = async (options: {
|
|
|
|
|
readonly path: string;
|
2026-10-06 16:26:40 +08:00
|
|
|
readonly formatHint?: ExecutableFormatHint;
|
2026-10-11 09:06:29 -07:00
|
|
|
readonly architecture?: BinaryArchitecture;
|
|
|
|
|
readonly entryPoint?: string;
|
2026-07-14 04:18:38 +08:00
|
|
|
readonly snapshotPath: string | undefined;
|
2026-10-10 00:16:04 +08:00
|
|
|
readonly signal: AbortSignal;
|
2026-07-14 04:18:38 +08:00
|
|
|
}): Promise<
|
2026-10-08 01:33:39 +08:00
|
|
|
Result<
|
|
|
|
|
{ readonly snapshot?: AnalysisSnapshot; readonly target?: BinaryTarget },
|
|
|
|
|
AnalysisError
|
|
|
|
|
>
|
2026-07-14 04:18:38 +08:00
|
|
|
> => {
|
2026-09-29 14:53:20 +08:00
|
|
|
const { path, snapshotPath } = options;
|
2026-07-14 04:18:38 +08:00
|
|
|
if (snapshotPath === undefined || !(await fileExists(snapshotPath)))
|
|
|
|
|
return ok({});
|
2026-10-11 13:59:40 +08:00
|
|
|
const loaded = await readAnalysisSnapshot(snapshotPath, options.signal);
|
2026-07-14 04:18:38 +08:00
|
|
|
if (!loaded.ok) return loaded;
|
2026-10-10 00:16:04 +08:00
|
|
|
const target = await parseBinaryTarget(path, {
|
|
|
|
|
signal: options.signal,
|
|
|
|
|
...(options.formatHint === undefined
|
|
|
|
|
? {}
|
|
|
|
|
: { formatHint: options.formatHint }),
|
2026-10-11 09:06:29 -07:00
|
|
|
...(options.architecture === undefined
|
|
|
|
|
? {}
|
|
|
|
|
: { architecture: options.architecture }),
|
|
|
|
|
...(options.entryPoint === undefined
|
|
|
|
|
? {}
|
|
|
|
|
: { entryPoint: options.entryPoint }),
|
2026-10-10 00:16:04 +08:00
|
|
|
});
|
2026-07-14 04:18:38 +08:00
|
|
|
if (!target.ok) return target;
|
|
|
|
|
if (!snapshotMatchesTarget(loaded.value.target, target.value))
|
|
|
|
|
return err(
|
|
|
|
|
new EvidenceIntegrityError(
|
|
|
|
|
"Analysis snapshot target does not match the requested binary",
|
|
|
|
|
),
|
|
|
|
|
);
|
2026-10-08 01:33:39 +08:00
|
|
|
return ok({ snapshot: loaded.value, target: target.value });
|
2026-07-14 04:18:38 +08:00
|
|
|
};
|
|
|
|
|
|
2026-10-21 04:21:16 +08:00
|
|
|
const inspectBundleResourceInput = async (
|
|
|
|
|
path: string,
|
|
|
|
|
): Promise<Result<boolean, BinaryTargetError>> => {
|
|
|
|
|
const callerPath = absoluteCallerPath(path);
|
|
|
|
|
try {
|
|
|
|
|
return ok((await stat(callerPath)).isDirectory());
|
|
|
|
|
} catch (cause: unknown) {
|
|
|
|
|
const failure = classifyBinaryTargetFilesystemFailure(callerPath, cause);
|
|
|
|
|
if (failure !== undefined) return err(failure);
|
|
|
|
|
throw cause;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-10-08 01:33:39 +08:00
|
|
|
const allowsSnapshotReplay = (
|
|
|
|
|
route: SessionProviderRoute,
|
|
|
|
|
tool:
|
|
|
|
|
| NativeToolName
|
|
|
|
|
| ArtifactAnalysisOperation
|
|
|
|
|
| ManagedToolName
|
|
|
|
|
| DirectAnalysisTool,
|
|
|
|
|
): boolean => {
|
|
|
|
|
if (tool === "trace_native_ui_action") return false;
|
|
|
|
|
const descriptor = route.capabilities?.get(tool);
|
|
|
|
|
return descriptor === undefined
|
|
|
|
|
? ![...(route.capabilities?.values() ?? [])].some(
|
|
|
|
|
({ cachePolicy }) => cachePolicy === "live",
|
|
|
|
|
)
|
|
|
|
|
: descriptor.cachePolicy !== "live";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const analysisProfileForRoute = (
|
|
|
|
|
route: SessionProviderRoute,
|
2026-07-16 15:56:40 +08:00
|
|
|
tool:
|
|
|
|
|
| NativeToolName
|
2026-09-29 09:22:24 +08:00
|
|
|
| ArtifactAnalysisOperation
|
2026-07-16 15:56:40 +08:00
|
|
|
| ManagedToolName
|
|
|
|
|
| DirectAnalysisTool,
|
2026-10-08 01:33:39 +08:00
|
|
|
): AnalysisProfileCommitment | undefined => {
|
|
|
|
|
const profile = route.profile;
|
2026-10-20 23:31:45 +08:00
|
|
|
const nativeDispatchProfile =
|
|
|
|
|
profile === null || profile === undefined
|
|
|
|
|
? nativeDispatchBindingProfile(route, tool)
|
|
|
|
|
: undefined;
|
|
|
|
|
if (profile === null || profile === undefined) {
|
|
|
|
|
if (nativeDispatchProfile === undefined) return undefined;
|
|
|
|
|
return isWorkflowEvidenceTool(tool)
|
|
|
|
|
? workflowAnalysisProfile(nativeDispatchProfile, tool)
|
|
|
|
|
: nativeDispatchProfile;
|
|
|
|
|
}
|
2026-10-09 00:57:28 +08:00
|
|
|
if (isWorkflowEvidenceTool(tool))
|
|
|
|
|
return workflowAnalysisProfile(profile, tool);
|
2026-10-08 01:33:39 +08:00
|
|
|
const provider = providerIdentityForRoute(route, tool);
|
|
|
|
|
return provider.id === profile.provider.id ? profile : undefined;
|
|
|
|
|
};
|
|
|
|
|
|
2026-10-20 23:31:45 +08:00
|
|
|
const nativeDispatchBindingProfile = (
|
|
|
|
|
route: SessionProviderRoute,
|
|
|
|
|
operation: string,
|
|
|
|
|
): AnalysisProfileCommitment | undefined => {
|
|
|
|
|
if (operation !== "inspect_native_dispatch_metadata") return undefined;
|
|
|
|
|
return profilelessNativeDispatchProfile(route);
|
|
|
|
|
};
|
|
|
|
|
|
2026-10-08 01:33:39 +08:00
|
|
|
/** Mirror BinarySession.providerIdentity for a route not opened yet. */
|
|
|
|
|
const providerIdentityForRoute = (
|
|
|
|
|
route: SessionProviderRoute,
|
|
|
|
|
operation: string,
|
2026-10-11 19:36:28 -07:00
|
|
|
): ProviderIdentity =>
|
|
|
|
|
route.capabilities?.get(operation)?.provider ?? route.identity;
|
2026-07-14 04:18:38 +08:00
|
|
|
|
2026-07-15 19:22:30 +08:00
|
|
|
const analysisProfileForEvidence = (
|
2026-10-07 21:56:58 +08:00
|
|
|
session: BinarySession,
|
2026-07-16 15:56:40 +08:00
|
|
|
tool:
|
|
|
|
|
| NativeToolName
|
2026-09-29 09:22:24 +08:00
|
|
|
| ArtifactAnalysisOperation
|
2026-07-16 15:56:40 +08:00
|
|
|
| ManagedToolName
|
|
|
|
|
| DirectAnalysisTool,
|
2026-07-15 19:22:30 +08:00
|
|
|
): AnalysisProfileCommitment | undefined => {
|
2026-07-25 14:29:28 +00:00
|
|
|
if (!isWorkflowEvidenceTool(tool)) return session.analysisProfile(tool);
|
2026-10-20 23:31:45 +08:00
|
|
|
return session.analysisProfile() ?? session.analysisProfile(tool);
|
2026-07-15 19:22:30 +08:00
|
|
|
};
|
|
|
|
|
|
2026-07-25 14:29:28 +00:00
|
|
|
const isWorkflowEvidenceTool = (
|
|
|
|
|
tool:
|
|
|
|
|
| NativeToolName
|
2026-09-29 09:22:24 +08:00
|
|
|
| ArtifactAnalysisOperation
|
2026-07-25 14:29:28 +00:00
|
|
|
| ManagedToolName
|
|
|
|
|
| DirectAnalysisTool,
|
|
|
|
|
): boolean =>
|
|
|
|
|
tool === "binary_overview" ||
|
|
|
|
|
tool === "inspect_native_api" ||
|
2026-10-04 16:02:36 +08:00
|
|
|
tool === "inspect_native_dispatch_metadata" ||
|
2026-09-30 00:41:05 +08:00
|
|
|
tool === "trace_feature" ||
|
2026-10-04 16:02:36 +08:00
|
|
|
tool === "trace_native_values" ||
|
2026-09-30 00:41:05 +08:00
|
|
|
tool === "trace_native_ui_action";
|
2026-07-25 14:29:28 +00:00
|
|
|
|
2026-07-14 04:18:38 +08:00
|
|
|
const fileExists = async (path: string): Promise<boolean> => {
|
|
|
|
|
try {
|
|
|
|
|
await access(path);
|
|
|
|
|
return true;
|
|
|
|
|
} catch (cause: unknown) {
|
|
|
|
|
if (
|
|
|
|
|
typeof cause === "object" &&
|
|
|
|
|
cause !== null &&
|
|
|
|
|
"code" in cause &&
|
|
|
|
|
cause.code === "ENOENT"
|
|
|
|
|
)
|
|
|
|
|
return false;
|
|
|
|
|
throw cause;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-07-13 11:51:37 +00:00
|
|
|
const cliError = (error: AnalysisError): JsonValue => ({
|
|
|
|
|
error: "Analysis failed",
|
|
|
|
|
...projectAnalysisError(error),
|
|
|
|
|
});
|