rea-agents 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +95 -27
  2. package/bridge/hopper_bridge.py +172 -14
  3. package/dist/application/AnalysisSnapshotCache.js +131 -0
  4. package/dist/application/AnalysisSnapshotFiles.js +31 -0
  5. package/dist/application/ArtifactGraphConstruction.js +1 -1
  6. package/dist/application/ArtifactInventory.js +100 -24
  7. package/dist/application/AuthorizedArtifactInventory.js +16 -0
  8. package/dist/application/BinarySession.js +149 -36
  9. package/dist/application/BinarySessionPort.js +1 -0
  10. package/dist/application/BoundedJsonFiles.js +109 -0
  11. package/dist/application/CapabilityInventory.js +127 -0
  12. package/dist/application/ClientRegistrationStatus.js +69 -0
  13. package/dist/application/CrossVersionInventory.js +109 -0
  14. package/dist/application/CrossVersionInvestigation.js +358 -0
  15. package/dist/application/DirectAnalysis.js +205 -24
  16. package/dist/application/Doctor.js +104 -3
  17. package/dist/application/EvidenceBundleFiles.js +15 -108
  18. package/dist/application/EvidenceLedger.js +3 -2
  19. package/dist/application/InvestigationProviders.js +48 -0
  20. package/dist/application/InvestigationWorkspaceStore.js +214 -0
  21. package/dist/application/LinuxHopper.js +72 -48
  22. package/dist/application/PermissionAuthority.js +183 -0
  23. package/dist/application/PermissionConfiguration.js +26 -0
  24. package/dist/application/ProcessCaptureAuthority.js +2 -2
  25. package/dist/application/ProcessCaptureError.js +25 -0
  26. package/dist/application/ProcessCaptureLifecycle.js +22 -2
  27. package/dist/application/ProcessCli.js +111 -31
  28. package/dist/application/ProcessHarness.js +6 -3
  29. package/dist/application/ProgressReporter.js +30 -0
  30. package/dist/application/ProjectPermissionStore.js +110 -0
  31. package/dist/application/ReferenceSourceImportEntries.js +18 -3
  32. package/dist/application/ReferenceSourceImportTypes.js +32 -0
  33. package/dist/application/Setup.js +35 -101
  34. package/dist/application/SetupClients.js +19 -0
  35. package/dist/application/SetupInstallFailure.js +53 -0
  36. package/dist/application/SetupPlan.js +31 -0
  37. package/dist/application/SupportedClients.js +41 -0
  38. package/dist/application/Uninstall.js +34 -24
  39. package/dist/application/UnknownEvidence.js +33 -0
  40. package/dist/application/Upgrade.js +3 -1
  41. package/dist/application/runtime.js +2 -2
  42. package/dist/artifacts/ArtifactProvider.js +15 -8
  43. package/dist/catalogIdentity.js +121 -0
  44. package/dist/cli.js +76 -13
  45. package/dist/cliEvidenceCommands.js +52 -12
  46. package/dist/cliInvestigationCommands.js +137 -0
  47. package/dist/cliLogging.js +20 -3
  48. package/dist/cliOutput.js +41 -0
  49. package/dist/cliPolicyCommands.js +115 -0
  50. package/dist/config.js +102 -27
  51. package/dist/contracts/artifactToolContracts.js +14 -1
  52. package/dist/contracts/errorSchemas.js +98 -0
  53. package/dist/contracts/promptContracts.js +256 -0
  54. package/dist/contracts/sessionLifecycleInputs.js +11 -0
  55. package/dist/contracts/toolContractTypes.js +1 -0
  56. package/dist/contracts/toolContracts.js +20 -7
  57. package/dist/contracts/toolOutputSchemas.js +70 -9
  58. package/dist/domain/analysisSnapshot.js +149 -0
  59. package/dist/domain/artifactComparison.js +38 -11
  60. package/dist/domain/artifactGraph.js +25 -1
  61. package/dist/domain/artifactInventoryEvidence.js +18 -0
  62. package/dist/domain/changedBehavior.js +35 -10
  63. package/dist/domain/errors.js +384 -32
  64. package/dist/domain/evidence.js +4 -2
  65. package/dist/domain/evidenceBundle.js +28 -0
  66. package/dist/domain/hopperStartupFailure.js +55 -0
  67. package/dist/domain/investigationWorkspace.js +341 -0
  68. package/dist/domain/jsonValue.js +2 -0
  69. package/dist/domain/nativeInspection.js +3 -2
  70. package/dist/domain/permissionPolicy.js +157 -0
  71. package/dist/domain/processCapture.js +5 -4
  72. package/dist/domain/processComparison.js +6 -4
  73. package/dist/domain/reconstructionVerification.js +1 -1
  74. package/dist/domain/reconstructionVerificationSchemas.js +1 -0
  75. package/dist/domain/staticRuntimeCorrelation.js +5 -1
  76. package/dist/generatedPackageMetadata.js +9 -0
  77. package/dist/hopper/BridgeLauncher.js +51 -20
  78. package/dist/hopper/HopperClient.js +45 -3
  79. package/dist/hopper/HopperProvider.js +1 -0
  80. package/dist/identity.js +10 -3
  81. package/dist/logger.js +2 -2
  82. package/dist/main.js +139 -28
  83. package/dist/server/createServer.js +54 -5
  84. package/dist/server/mcpProgress.js +23 -0
  85. package/dist/server/promptCompletion.js +144 -0
  86. package/dist/server/registerArtifactComparisonTool.js +6 -2
  87. package/dist/server/registerBundleComparisonTool.js +6 -2
  88. package/dist/server/registerEnhancedTools.js +20 -7
  89. package/dist/server/registerEvidenceResources.js +302 -0
  90. package/dist/server/registerEvidenceTools.js +59 -8
  91. package/dist/server/registerFunctionComparisonTool.js +7 -3
  92. package/dist/server/registerInvestigationTools.js +111 -18
  93. package/dist/server/registerOfficialTools.js +25 -14
  94. package/dist/server/registerProcessComparisonTool.js +8 -10
  95. package/dist/server/registerPrompts.js +67 -0
  96. package/dist/server/registerSessionStatusTool.js +52 -0
  97. package/dist/server/registerSessionTools.js +178 -22
  98. package/dist/server/runDerivedOperation.js +35 -0
  99. package/dist/server/sessionEvidence.js +2 -8
  100. package/dist/server/sessionToolPolicies.js +1 -42
  101. package/dist/server/toolResult.js +34 -6
  102. package/dist/serverIdentity.js +70 -0
  103. package/install.sh +11 -11
  104. package/package.json +10 -6
  105. package/scripts/hopper-demo-x11.py +349 -0
  106. package/scripts/rea.mjs +6 -3
  107. package/skills/rea-analysis/SKILL.md +9 -2
@@ -5,7 +5,14 @@ import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
5
5
  import writeFileAtomic from "write-file-atomic";
6
6
  import { z } from "zod";
7
7
  import { PRODUCT_IDENTITY } from "../identity.js";
8
- import { supportedClients } from "./Setup.js";
8
+ import { supportedClients } from "./SupportedClients.js";
9
+ const systemFileSystem = {
10
+ readText: (path) => readFile(path, "utf8"),
11
+ copy: (source, destination) => copyFile(source, destination),
12
+ writeText: (path, contents) => writeFileAtomic(path, contents, { encoding: "utf8" }),
13
+ stat: (path) => lstat(path),
14
+ remove: (path) => rm(path, { recursive: true }),
15
+ };
9
16
  /** Remove only REA-owned registrations and managed files, optionally including local state. */
10
17
  export const runUninstall = async (purgeData, host = systemUninstallHost()) => {
11
18
  const items = [];
@@ -27,26 +34,26 @@ export const runUninstall = async (purgeData, host = systemUninstallHost()) => {
27
34
  };
28
35
  };
29
36
  /** Create uninstall effects contained to detected client configs and REA-owned paths. */
30
- export const systemUninstallHost = (home = homedir()) => ({
37
+ export const systemUninstallHost = (home = homedir(), fileSystem = systemFileSystem) => ({
31
38
  clients: () => Promise.resolve(supportedClients(home)),
32
- removeClient,
33
- removeSkill: () => removeManagedPath(join(home, ".agents/skills", PRODUCT_IDENTITY.skillName), "skill"),
39
+ removeClient: (client) => removeClient(client, fileSystem),
40
+ removeSkill: () => removeManagedPath(join(home, ".agents/skills", PRODUCT_IDENTITY.skillName), "skill", fileSystem),
34
41
  purgeData: async () => [
35
- await removeManagedPath(join(home, ".rea/cache"), "cache"),
36
- await removeManagedPath(join(home, ".rea/state"), "state"),
42
+ await removeManagedPath(join(home, ".rea/cache"), "cache", fileSystem),
43
+ await removeManagedPath(join(home, ".rea/state"), "state", fileSystem),
37
44
  ],
38
45
  });
39
- const removeClient = async (client) => {
46
+ const removeClient = async (client, fileSystem) => {
40
47
  if (client.format === "unsupported")
41
48
  return item(client.name, "skipped", "This client has no documented local MCP configuration boundary.");
42
49
  let original;
43
50
  try {
44
- original = await readFile(client.configPath, "utf8");
51
+ original = await fileSystem.readText(client.configPath);
45
52
  }
46
53
  catch (cause) {
47
54
  return isMissing(cause)
48
55
  ? item(client.name, "skipped", "Configuration does not exist.")
49
- : item(client.name, "failed", "Configuration could not be read.");
56
+ : item(client.name, "failed", "Configuration could not be read. Check file permissions, then rerun uninstall.");
50
57
  }
51
58
  let document;
52
59
  let key;
@@ -57,7 +64,7 @@ const removeClient = async (client) => {
57
64
  servers = optionalObject(document[key]);
58
65
  }
59
66
  catch {
60
- return item(client.name, "failed", "Malformed configuration was preserved without mutation.");
67
+ return item(client.name, "failed", `Configuration is not valid ${client.format === "toml" ? "TOML" : "JSON"} and was not changed. Repair it, then rerun uninstall.`);
61
68
  }
62
69
  const registration = servers[PRODUCT_IDENTITY.mcpServerKey];
63
70
  if (registration === undefined)
@@ -69,28 +76,31 @@ const removeClient = async (client) => {
69
76
  document[key] = remaining;
70
77
  const backupPath = `${client.configPath}.rea.backup`;
71
78
  try {
72
- await copyFile(client.configPath, backupPath);
79
+ await fileSystem.copy(client.configPath, backupPath);
73
80
  }
74
81
  catch {
75
- return item(client.name, "failed", "Configuration backup failed; no mutation was attempted.");
82
+ return item(client.name, "failed", "Configuration could not be backed up, so no change was made. Check file permissions, then rerun uninstall.");
76
83
  }
77
84
  try {
78
85
  const encoded = client.format === "toml"
79
86
  ? stringifyToml(document)
80
87
  : `${JSON.stringify(document, null, 2)}\n`;
81
- await writeFileAtomic(client.configPath, encoded, { encoding: "utf8" });
88
+ await fileSystem.writeText(client.configPath, encoded);
82
89
  const readback = objectSchema.parse(client.format === "toml"
83
- ? parseToml(await readFile(client.configPath, "utf8"))
84
- : JSON.parse(await readFile(client.configPath, "utf8")));
90
+ ? parseToml(await fileSystem.readText(client.configPath))
91
+ : JSON.parse(await fileSystem.readText(client.configPath)));
85
92
  if (PRODUCT_IDENTITY.mcpServerKey in optionalObject(readback[key]))
86
93
  throw new Error("registration readback mismatch");
87
94
  return item(client.name, "removed", `Removed registration from ${client.configPath}.`);
88
95
  }
89
96
  catch {
90
- await writeFileAtomic(client.configPath, original, {
91
- encoding: "utf8",
92
- }).catch(() => undefined);
93
- return item(client.name, "failed", `Configuration update failed; the original was restored and ${backupPath} was retained.`);
97
+ try {
98
+ await fileSystem.writeText(client.configPath, original);
99
+ }
100
+ catch {
101
+ return item(client.name, "failed", "Configuration could not be updated or restored. Restore its `.rea.backup` manually, then rerun uninstall.");
102
+ }
103
+ return item(client.name, "failed", "Configuration could not be updated. The original was restored and its `.rea.backup` was retained. Repair the configuration or restore the backup, then rerun uninstall.");
94
104
  }
95
105
  };
96
106
  /** Ownership rule for persistent MCP registrations created by REA setup. */
@@ -107,18 +117,18 @@ const isOwnedRegistration = (value) => {
107
117
  JSON.stringify(args) ===
108
118
  JSON.stringify(["-y", PRODUCT_IDENTITY.packageName, "mcp"])));
109
119
  };
110
- const removeManagedPath = async (path, name) => {
120
+ const removeManagedPath = async (path, name, fileSystem) => {
111
121
  try {
112
- const stats = await lstat(path);
122
+ const stats = await fileSystem.stat(path);
113
123
  if (stats.isSymbolicLink())
114
- return item(name, "retained", `Refused to follow symlink ${path}.`);
115
- await rm(path, { recursive: true });
124
+ return item(name, "retained", "REA did not remove this item because its managed path is a symbolic link. Verify the link target before removing it manually.");
125
+ await fileSystem.remove(path);
116
126
  return item(name, "removed", `Removed ${path}.`);
117
127
  }
118
128
  catch (cause) {
119
129
  return isMissing(cause)
120
130
  ? item(name, "skipped", `${path} does not exist.`)
121
- : item(name, "failed", `${path} could not be removed.`);
131
+ : item(name, "failed", "This item could not be removed. Check file permissions, then rerun uninstall.");
122
132
  }
123
133
  };
124
134
  const item = (name, status, detail) => ({ name, status, detail });
@@ -0,0 +1,33 @@
1
+ import { createEvidence } from "../domain/evidence.js";
2
+ /** Provider identity for analyst-authored residual-unknown mutations. */
3
+ export const UNKNOWN_REGISTRY_PROVIDER = {
4
+ id: "rea-unknown-registry",
5
+ name: "REA residual unknown registry",
6
+ version: "1",
7
+ };
8
+ /** Create immutable evidence for one approved residual-unknown mutation. */
9
+ export const unknownMutationEvidence = (target, input) => createEvidence(target, UNKNOWN_REGISTRY_PROVIDER, {
10
+ predicateType: "rea.residual-unknown-mutation/v1",
11
+ operation: "record_unknown",
12
+ parameters: { domain: input.domain, severity: input.severity },
13
+ result: {
14
+ action: "record",
15
+ question: input.question,
16
+ required_authority: input.required_authority,
17
+ required_confidence: input.required_confidence,
18
+ },
19
+ confidence: "derived",
20
+ authority: "analyst-inference",
21
+ evidenceLinks: unknownEvidenceLinks(input),
22
+ limitations: [
23
+ "Registry mutation evidence records analyst intent, not proof of the answer.",
24
+ ],
25
+ });
26
+ /** Deduplicate evidence links carried by an unknown mutation. */
27
+ export const unknownEvidenceLinks = (input) => [
28
+ ...input.supporting_evidence_ids,
29
+ ...input.contradicting_evidence_ids,
30
+ ...("resolution" in input && input.resolution !== null
31
+ ? input.resolution.evidence_ids
32
+ : []),
33
+ ].filter((id, index, values) => values.indexOf(id) === index);
@@ -33,7 +33,7 @@ export const runUpgrade = async (currentVersion, host = systemUpgradeHost(), out
33
33
  currentVersion,
34
34
  latestVersion: latestVersion ?? null,
35
35
  reason: "install",
36
- remediation: `${UPGRADE_COMMAND} failed.`,
36
+ remediation: `REA could not update through npm. Check npm registry access and global install permissions, then run: ${UPGRADE_COMMAND}`,
37
37
  };
38
38
  return {
39
39
  status: "upgraded",
@@ -42,6 +42,8 @@ export const runUpgrade = async (currentVersion, host = systemUpgradeHost(), out
42
42
  versionCheck: latestVersion === undefined ? "unavailable" : "available",
43
43
  installMethod: "npm",
44
44
  command: UPGRADE_COMMAND,
45
+ clientRestartRequired: true,
46
+ remediation: "Rerun rea setup to refresh registrations and the skill, then restart clients that may retain an older MCP server.",
45
47
  };
46
48
  };
47
49
  /** Create the npm registry and subprocess effects for a production upgrade. */
@@ -11,8 +11,8 @@ import { silentLogger } from "../logger.js";
11
11
  */
12
12
  export const createBinarySession = (config, logger = silentLogger) => {
13
13
  return new BinarySession(new CompositeProvider([
14
- new ArtifactProvider(config.artifactNativeMountEnabled),
14
+ new ArtifactProvider(config.artifactNativeMountEnabled, config.artifactIntegrityContinueEnabled),
15
15
  new NativeMacOSProvider(),
16
16
  new HopperProvider(config, logger),
17
- ]));
17
+ ]), { snapshotsEnabled: config.hopperLoaderArgs.length === 0 });
18
18
  };
@@ -5,16 +5,15 @@ import { ARTIFACT_TOOL_CONTRACTS, artifactInventoryInputSchema, artifactExtracti
5
5
  import { AnalysisCapabilityUnavailableError, ArtifactOperationError, } from "../domain/errors.js";
6
6
  import { err, ok } from "../domain/result.js";
7
7
  import { ArtifactReaderFailure, } from "./ArtifactReader.js";
8
- const IDENTITY = Object.freeze({
9
- id: "rea-artifact-graph",
10
- name: "REA safe artifact graph provider",
11
- version: "1",
12
- });
8
+ import { ARTIFACT_GRAPH_PROVIDER } from "../application/InvestigationProviders.js";
9
+ const IDENTITY = Object.freeze(ARTIFACT_GRAPH_PROVIDER);
13
10
  /** Read-only inventory and exclusively owned extraction provider. */
14
11
  export class ArtifactProvider {
15
12
  nativeMountEnabled;
16
- constructor(nativeMountEnabled = false) {
13
+ integrityContinueEnabled;
14
+ constructor(nativeMountEnabled = false, integrityContinueEnabled = false) {
17
15
  this.nativeMountEnabled = nativeMountEnabled;
16
+ this.integrityContinueEnabled = integrityContinueEnabled;
18
17
  }
19
18
  #capabilities = Object.freeze(ARTIFACT_TOOL_CONTRACTS.map((contract) => Object.freeze({
20
19
  provider: IDENTITY,
@@ -51,15 +50,17 @@ export class ArtifactProvider {
51
50
  return this.#capabilities;
52
51
  }
53
52
  createClient(target) {
54
- return new ArtifactClient(target, this.nativeMountEnabled);
53
+ return new ArtifactClient(target, this.nativeMountEnabled, this.integrityContinueEnabled);
55
54
  }
56
55
  }
57
56
  class ArtifactClient {
58
57
  target;
59
58
  nativeMountEnabled;
60
- constructor(target, nativeMountEnabled) {
59
+ integrityContinueEnabled;
60
+ constructor(target, nativeMountEnabled, integrityContinueEnabled) {
61
61
  this.target = target;
62
62
  this.nativeMountEnabled = nativeMountEnabled;
63
+ this.integrityContinueEnabled = integrityContinueEnabled;
63
64
  }
64
65
  async execute(operation, parameters, options) {
65
66
  if (operation === "health")
@@ -102,6 +103,12 @@ class ArtifactClient {
102
103
  nativeMountApproved: parsed.native_mount_approved === true,
103
104
  nativeMountEnabled: this.nativeMountEnabled,
104
105
  },
106
+ integrity: {
107
+ mode: parsed.integrity_policy,
108
+ approved: parsed.integrity_continue_approved === true,
109
+ enabled: this.integrityContinueEnabled,
110
+ maxMismatches: parsed.max_integrity_mismatches,
111
+ },
105
112
  });
106
113
  return ok(createAnalysisExecution(result, IDENTITY, {
107
114
  rawResult: null,
@@ -0,0 +1,121 @@
1
+ import { createHash } from "node:crypto";
2
+ import canonicalize from "canonicalize";
3
+ import { z } from "zod";
4
+ import { PROMPT_CONTRACTS } from "./contracts/promptContracts.js";
5
+ import { TOOL_CONTRACTS } from "./contracts/toolContracts.js";
6
+ /** Explicit CLI command inventory, kept separate from MCP tool counts. */
7
+ export const CLI_COMMAND_NAMES = [
8
+ "analyze",
9
+ "inspect",
10
+ "decompile",
11
+ "setup",
12
+ "doctor",
13
+ "uninstall",
14
+ "upgrade",
15
+ "xrefs",
16
+ "trace",
17
+ "capabilities",
18
+ "providers",
19
+ "function",
20
+ "search",
21
+ "import-reference-source",
22
+ "inventory-artifact",
23
+ "extract-artifact",
24
+ "inspect-macho",
25
+ "inspect-signature",
26
+ "list-architectures",
27
+ "inspect-plist",
28
+ "demangle-swift",
29
+ "evidence-import",
30
+ "evidence-export",
31
+ "compare",
32
+ "investigate-versions",
33
+ "capture-process",
34
+ "compare-process-captures",
35
+ "policy",
36
+ ];
37
+ const toolCatalog = TOOL_CONTRACTS.map((contract) => ({
38
+ name: contract.name,
39
+ surface: contract.kind,
40
+ description: contract.description,
41
+ annotations: contract.annotations,
42
+ input_schema: z.toJSONSchema(contract.inputSchema, {
43
+ unrepresentable: "any",
44
+ }),
45
+ output_schema: z.toJSONSchema(contract.outputSchema, {
46
+ unrepresentable: "any",
47
+ }),
48
+ })).sort((left, right) => left.name.localeCompare(right.name));
49
+ const promptCatalog = PROMPT_CONTRACTS.map((contract) => ({
50
+ name: contract.name,
51
+ title: contract.title,
52
+ description: contract.description,
53
+ arguments: contract.arguments,
54
+ steps: contract.steps,
55
+ })).sort((left, right) => left.name.localeCompare(right.name));
56
+ const resourceCatalog = [
57
+ { name: "server-identity", uri: "rea://server/identity" },
58
+ { name: "active-residual-unknowns", uri: "rea://unknowns/active" },
59
+ ];
60
+ const resourceTemplateCatalog = [
61
+ { name: "session-evidence", uri_template: "rea://evidence/{evidenceId}" },
62
+ {
63
+ name: "evidence-section",
64
+ uri_template: "rea://evidence/{evidenceId}/section/{section}",
65
+ },
66
+ { name: "residual-unknown", uri_template: "rea://unknown/{unknownId}" },
67
+ {
68
+ name: "analysis-snapshot",
69
+ uri_template: "rea://snapshot/{snapshotDigest}",
70
+ },
71
+ {
72
+ name: "artifact-page",
73
+ uri_template: "rea://artifact/{manifestId}/{collection}",
74
+ },
75
+ {
76
+ name: "function-dossier",
77
+ uri_template: "rea://function/{targetSha256}/{address}",
78
+ },
79
+ {
80
+ name: "investigation-workspace-revision",
81
+ uri_template: "rea://workspace/{workspaceId}/revision/{revision}",
82
+ },
83
+ ];
84
+ const digest = (value) => {
85
+ const encoded = canonicalize(value);
86
+ if (encoded === undefined)
87
+ throw new TypeError("Catalog is not canonical JSON");
88
+ return createHash("sha256").update(encoded).digest("hex");
89
+ };
90
+ /** Stable, schema-sensitive identity for every public REA surface. */
91
+ export const CATALOG_IDENTITY = {
92
+ counts: {
93
+ cli_commands: CLI_COMMAND_NAMES.length,
94
+ mcp_tools: toolCatalog.length,
95
+ mcp_prompts: promptCatalog.length,
96
+ mcp_resources: resourceCatalog.length,
97
+ mcp_resource_templates: resourceTemplateCatalog.length,
98
+ },
99
+ digests: {
100
+ tools_sha256: digest(toolCatalog),
101
+ prompts_sha256: digest(promptCatalog),
102
+ resources_sha256: digest({ resourceCatalog, resourceTemplateCatalog }),
103
+ combined_sha256: digest({
104
+ cli: CLI_COMMAND_NAMES,
105
+ tools: toolCatalog,
106
+ prompts: promptCatalog,
107
+ resources: resourceCatalog,
108
+ resource_templates: resourceTemplateCatalog,
109
+ }),
110
+ },
111
+ tools: toolCatalog.map(({ name, surface, annotations }) => ({
112
+ name,
113
+ surface,
114
+ annotations: {
115
+ read_only: annotations.readOnlyHint ?? false,
116
+ destructive: annotations.destructiveHint ?? false,
117
+ idempotent: annotations.idempotentHint ?? false,
118
+ open_world: annotations.openWorldHint ?? true,
119
+ },
120
+ })),
121
+ };
package/dist/cli.js CHANGED
@@ -13,6 +13,11 @@ import { parseConfig } from "./config.js";
13
13
  import { importReferenceSource } from "./application/ReferenceSourceImport.js";
14
14
  import { registerEvidenceCommands } from "./cliEvidenceCommands.js";
15
15
  import { registerProcessCommands } from "./cliProcessCommands.js";
16
+ import { registerInvestigationCommands } from "./cliInvestigationCommands.js";
17
+ import { AnalysisProtocolError, PermissionRequiredError, projectAnalysisError, } from "./domain/errors.js";
18
+ import { projectReferenceSourceImportError } from "./application/ReferenceSourceImportTypes.js";
19
+ import { loadConfiguredPermissionAuthority } from "./application/PermissionConfiguration.js";
20
+ import { registerPolicyCommands } from "./cliPolicyCommands.js";
16
21
  /**
17
22
  * Build the one-shot Incur CLI without starting Hopper at import time.
18
23
  * Analysis commands acquire and close their own sessions; bare `mcp` and
@@ -23,8 +28,8 @@ export const createCli = () => {
23
28
  ? "silent"
24
29
  : parseLogLevel(process.env.REA_LOG_LEVEL));
25
30
  const cli = Cli.create(PRODUCT_IDENTITY.cliBinary, {
26
- version: process.env.REA_PACKAGE_VERSION ?? "0.0.0-development",
27
- description: "Reverse engineer anything from your terminal or coding agent.",
31
+ version: PRODUCT_IDENTITY.packageVersion,
32
+ description: "Reverse engineer anything from your terminal or agent.",
28
33
  mcp: {
29
34
  command: PRODUCT_IDENTITY.mcpCommand,
30
35
  instructions: "Ask what software, artifact, protocol, or behavior the user wants to understand, then choose the available investigation capabilities that can produce evidence.",
@@ -48,15 +53,22 @@ export const createCli = () => {
48
53
  registerCapabilityCommands(cli, logger);
49
54
  registerNativeCommands(cli, logger);
50
55
  registerArtifactCommands(cli, logger);
56
+ registerInvestigationCommands(cli, logger);
51
57
  registerEvidenceCommands(cli, logger);
52
58
  registerReferenceSourceCommand(cli, logger);
53
59
  registerProcessCommands(cli, logger);
60
+ registerPolicyCommands(cli, logger);
54
61
  return cli;
55
62
  };
56
63
  const registerCoreCommands = (cli, logger) => {
57
64
  const overviewOptions = z.object({
58
65
  detail: z.enum(["concise", "detailed"]).default("concise"),
59
66
  limit: z.number().int().min(1).max(50).default(10),
67
+ snapshot: z
68
+ .string()
69
+ .min(1)
70
+ .optional()
71
+ .describe("Load and update a local analysis snapshot"),
60
72
  });
61
73
  cli.command("analyze", {
62
74
  description: "Get an overview of an app",
@@ -64,7 +76,7 @@ const registerCoreCommands = (cli, logger) => {
64
76
  path: z.string().describe("App, program, or Hopper database path"),
65
77
  }),
66
78
  options: overviewOptions,
67
- run: ({ args, options }) => logCliCommand(logger, "analyze", () => runDirectAnalysis(args.path, "binary_overview", { detail: options.detail, limit: options.limit }, logger)),
79
+ run: ({ args, options }) => logCliCommand(logger, "analyze", () => runDirectAnalysis(args.path, "binary_overview", { detail: options.detail, limit: options.limit }, { logger, snapshotPath: options.snapshot })),
68
80
  });
69
81
  cli.command("inspect", {
70
82
  description: "Inspect an app overview with evidence",
@@ -72,7 +84,7 @@ const registerCoreCommands = (cli, logger) => {
72
84
  path: z.string().describe("App, program, or Hopper database path"),
73
85
  }),
74
86
  options: overviewOptions,
75
- run: ({ args, options }) => logCliCommand(logger, "inspect", () => runDirectAnalysis(args.path, "binary_overview", { detail: options.detail, limit: options.limit }, logger)),
87
+ run: ({ args, options }) => logCliCommand(logger, "inspect", () => runDirectAnalysis(args.path, "binary_overview", { detail: options.detail, limit: options.limit }, { logger, snapshotPath: options.snapshot })),
76
88
  });
77
89
  cli.command("decompile", {
78
90
  description: "Read one part of an app as code",
@@ -80,12 +92,13 @@ const registerCoreCommands = (cli, logger) => {
80
92
  path: z.string().describe("App or program path"),
81
93
  address: z.string().describe("Procedure address"),
82
94
  }),
83
- run: ({ args }) => logCliCommand(logger, "decompile", () => runDirectAnalysis(args.path, "procedure_pseudo_code", { procedure: args.address }, logger)),
95
+ options: z.object({ snapshot: z.string().min(1).optional() }),
96
+ run: ({ args, options }) => logCliCommand(logger, "decompile", () => runDirectAnalysis(args.path, "procedure_pseudo_code", { procedure: args.address }, { logger, snapshotPath: options.snapshot })),
84
97
  });
85
98
  };
86
99
  const registerSetupCommands = (cli, logger) => {
87
100
  cli.command("setup", {
88
- description: "Install requirements and configure coding agents",
101
+ description: "Install requirements and configure agents",
89
102
  options: z.object({
90
103
  yes: z
91
104
  .boolean()
@@ -125,7 +138,7 @@ const registerSetupCommands = (cli, logger) => {
125
138
  });
126
139
  cli.command("upgrade", {
127
140
  description: "Upgrade a global npm installation to the latest REA release",
128
- run: ({ formatExplicit }) => logCliCommand(logger, "upgrade", () => runUpgrade(process.env.REA_PACKAGE_VERSION ?? "0.0.0-development", systemUpgradeHost(), formatExplicit ? "structured" : "human")),
141
+ run: ({ formatExplicit }) => logCliCommand(logger, "upgrade", () => runUpgrade(PRODUCT_IDENTITY.packageVersion, systemUpgradeHost(), formatExplicit ? "structured" : "human")),
129
142
  });
130
143
  };
131
144
  const confirmSetup = async (actions) => {
@@ -153,7 +166,8 @@ const registerXrefsCommand = (cli, logger) => {
153
166
  path: z.string().describe("App or program path"),
154
167
  address: z.string().describe("Hexadecimal address"),
155
168
  }),
156
- run: ({ args }) => logCliCommand(logger, "xrefs", () => runDirectAnalysis(args.path, "xrefs", { address: args.address }, logger)),
169
+ options: z.object({ snapshot: z.string().min(1).optional() }),
170
+ run: ({ args, options }) => logCliCommand(logger, "xrefs", () => runDirectAnalysis(args.path, "xrefs", { address: args.address }, { logger, snapshotPath: options.snapshot })),
157
171
  });
158
172
  };
159
173
  const registerTraceCommand = (cli, logger) => {
@@ -167,6 +181,7 @@ const registerTraceCommand = (cli, logger) => {
167
181
  caseSensitive: z.boolean().default(false),
168
182
  limit: z.number().int().min(1).max(100).default(20),
169
183
  maxOperations: z.number().int().min(1).max(100).default(20),
184
+ snapshot: z.string().min(1).optional(),
170
185
  }),
171
186
  alias: {
172
187
  caseSensitive: "case-sensitive",
@@ -177,7 +192,7 @@ const registerTraceCommand = (cli, logger) => {
177
192
  case_sensitive: options.caseSensitive,
178
193
  limit: options.limit,
179
194
  max_operations: options.maxOperations,
180
- }, logger)),
195
+ }, { logger, snapshotPath: options.snapshot })),
181
196
  });
182
197
  };
183
198
  const registerCapabilityCommands = (cli, logger) => {
@@ -202,6 +217,7 @@ const registerFunctionCommand = (cli, logger) => {
202
217
  limit: z.number().int().min(1).max(500).default(100),
203
218
  maxPseudocodeChars: z.number().int().min(1).max(100_000).default(20_000),
204
219
  maxInstructions: z.number().int().min(1).max(5_000).default(500),
220
+ snapshot: z.string().min(1).optional(),
205
221
  }),
206
222
  alias: {
207
223
  includeAssembly: "include-assembly",
@@ -214,7 +230,7 @@ const registerFunctionCommand = (cli, logger) => {
214
230
  limit: options.limit,
215
231
  max_pseudocode_chars: options.maxPseudocodeChars,
216
232
  max_instructions: options.maxInstructions,
217
- }, logger)),
233
+ }, { logger, snapshotPath: options.snapshot })),
218
234
  });
219
235
  };
220
236
  const registerSearchCommand = (cli, logger) => {
@@ -230,6 +246,7 @@ const registerSearchCommand = (cli, logger) => {
230
246
  caseSensitive: z.boolean().default(false),
231
247
  offset: z.number().int().min(0).default(0),
232
248
  limit: z.number().int().min(1).max(100).default(100),
249
+ snapshot: z.string().min(1).optional(),
233
250
  }),
234
251
  alias: { caseSensitive: "case-sensitive" },
235
252
  run: ({ args, options }) => logCliCommand(logger, "search", () => runDirectAnalysis(args.path, options.kind === "strings" ? "search_strings" : "search_procedures", {
@@ -238,7 +255,7 @@ const registerSearchCommand = (cli, logger) => {
238
255
  case_sensitive: options.caseSensitive,
239
256
  offset: options.offset,
240
257
  limit: options.limit,
241
- }, logger)),
258
+ }, { logger, snapshotPath: options.snapshot })),
242
259
  });
243
260
  };
244
261
  const registerReferenceSourceCommand = (cli, logger) => {
@@ -252,7 +269,36 @@ const registerReferenceSourceCommand = (cli, logger) => {
252
269
  run: ({ args }) => logCliCommand(logger, "import-reference-source", async () => {
253
270
  const config = parseConfig(process.env);
254
271
  if (!config.ok)
255
- return { error: config.error._tag, message: config.error.message };
272
+ return {
273
+ error: "Import failed",
274
+ ...projectAnalysisError(config.error),
275
+ };
276
+ const authority = await loadConfiguredPermissionAuthority(config.value);
277
+ if (!authority.ok)
278
+ return {
279
+ error: "Import failed",
280
+ ...projectAnalysisError(authority.error),
281
+ };
282
+ const authorized = await authority.value.authorize({
283
+ capability: "reference_read",
284
+ roots: [args.root],
285
+ executables: [],
286
+ environment_names: [],
287
+ network: "none",
288
+ mount: false,
289
+ operation_identity: `import_reference_source:${args.root}`,
290
+ }, "read");
291
+ if (!authorized.ok) {
292
+ const error = authorized.error instanceof PermissionRequiredError
293
+ ? authorized.error
294
+ : new AnalysisProtocolError(authorized.error.message, {
295
+ cause: authorized.error,
296
+ });
297
+ return {
298
+ error: "Import failed",
299
+ ...projectAnalysisError(error),
300
+ };
301
+ }
256
302
  const imported = await importReferenceSource({
257
303
  root: args.root,
258
304
  caller: "rea-cli",
@@ -262,7 +308,10 @@ const registerReferenceSourceCommand = (cli, logger) => {
262
308
  });
263
309
  return imported.ok
264
310
  ? imported.value
265
- : { error: imported.error.code, message: imported.error.message };
311
+ : {
312
+ error: "Import failed",
313
+ ...projectReferenceSourceImportError(imported.error),
314
+ };
266
315
  }),
267
316
  });
268
317
  };
@@ -275,7 +324,17 @@ const registerArtifactCommands = (cli, logger) => {
275
324
  options: z.object({
276
325
  offset: z.number().int().min(0).default(0),
277
326
  limit: z.number().int().min(1).max(500).default(100),
327
+ integrityPolicy: z.enum(["fail", "record-and-continue"]).default("fail"),
328
+ integrityContinueApproved: z.boolean().default(false),
329
+ maxIntegrityMismatches: z.number().int().min(1).max(100).default(10),
330
+ nativeMountApproved: z.boolean().default(false),
278
331
  }),
332
+ alias: {
333
+ integrityPolicy: "integrity-policy",
334
+ integrityContinueApproved: "integrity-continue-approved",
335
+ maxIntegrityMismatches: "max-integrity-mismatches",
336
+ nativeMountApproved: "native-mount-approved",
337
+ },
279
338
  run: ({ args, options }) => logCliCommand(logger, "inventory-artifact", () => runProviderAnalysis(args.path, "inventory_artifact", {
280
339
  node_offset: options.offset,
281
340
  node_limit: options.limit,
@@ -283,6 +342,10 @@ const registerArtifactCommands = (cli, logger) => {
283
342
  occurrence_limit: options.limit,
284
343
  edge_offset: options.offset,
285
344
  edge_limit: options.limit,
345
+ integrity_policy: options.integrityPolicy,
346
+ integrity_continue_approved: options.integrityContinueApproved,
347
+ max_integrity_mismatches: options.maxIntegrityMismatches,
348
+ native_mount_approved: options.nativeMountApproved,
286
349
  }, logger)),
287
350
  });
288
351
  cli.command("extract-artifact", {