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
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  # REA: Reverse Engineer Anything
6
6
 
7
- ### One CLI and MCP server for coding agents to reverse engineer anything
7
+ ### One CLI and MCP server for agents to reverse engineer anything
8
8
 
9
9
  **See a feature you like. Understand how it works, down to the binary level.**
10
10
 
@@ -24,7 +24,7 @@
24
24
 
25
25
  ---
26
26
 
27
- See a feature in an app that you want in your own product? Give the app to your coding agent—even without its source code. With REA, the agent can investigate the feature, explain how it works, show its evidence, and build a version adapted to your stack and requirements.
27
+ See a feature in an app that you want in your own product? Give the app to your agent—even without its source code. With REA, the agent can investigate the feature, explain how it works, show its evidence, and build a version adapted to your stack and requirements.
28
28
 
29
29
  REA gives agents one consistent way to investigate software. Today that includes deep native analysis through Hopper, complete function dossiers, reproducible Evidence v2 records, and controlled process capture. The longer-term toolkit extends the same agent workflow to packaged apps, JavaScript bundles, websites, APIs, protocols, mobile artifacts, firmware, runtime behavior, and differences between versions.
30
30
 
@@ -73,7 +73,7 @@ REA shows how it reached its conclusions. It does not claim to recover original
73
73
  | | |
74
74
  | ------------------------ | ------------------------------------------------------------------------------------ |
75
75
  | **Built for agents** | Ask what an app does and let your agent inspect it instead of guessing. |
76
- | **CLI and MCP** | Run the same reverse-engineering capabilities from your terminal or coding agent. |
76
+ | **CLI and MCP** | Run the same reverse-engineering capabilities from your terminal or agent. |
77
77
  | **Complexity handled** | REA installs and manages the reverse-engineering tools behind the scenes. |
78
78
  | **From insight to code** | Understand a feature, then build your own version in the same coding session. |
79
79
  | **Local by design** | Analysis runs on your Mac. REA does not upload the app to a hosted analysis service. |
@@ -88,7 +88,7 @@ npm install --global rea-agents
88
88
  rea setup
89
89
  ```
90
90
 
91
- Installing the CLI does not update Homebrew, Node.js, npm, Hopper, or coding-agent configuration. `rea setup` detects what is already present, prints every proposed change, and asks before applying it.
91
+ Installing the CLI does not update Homebrew, Node.js, npm, Hopper, or agent configuration. `rea setup` detects what is already present, prints every proposed change, and asks before applying it.
92
92
 
93
93
  REA detects Claude Code, Claude Desktop, Codex, Cursor, Gemini CLI, Windsurf, and Devin. Registrations are additive, backup-first, and read back after writing. You can safely rerun setup.
94
94
 
@@ -100,7 +100,7 @@ curl -fsSL https://raw.githubusercontent.com/morluto/rea/main/install.sh | bash
100
100
 
101
101
  Pass installer options after `bash -s --`, for example `--dry-run`, `--no-setup`, or `--version 1.0.0`. The curl wrapper never installs prerequisites or configures integrations itself. See [Installation and setup](docs/installation.md) for its exact mutation boundary.
102
102
 
103
- ### With a coding agent — recommended
103
+ ### With an agent — recommended
104
104
 
105
105
  ```bash
106
106
  npx skills add morluto/rea
@@ -108,7 +108,7 @@ npx skills add morluto/rea
108
108
 
109
109
  Ask your agent to set up REA. It will check your Mac, explain anything it needs to install, ask for approval, and guide you through system prompts. After setup, restart the agent if it asks you to load the full REA toolset.
110
110
 
111
- Review the setup plan, approve it if appropriate, complete Hopper's one-time activation when prompted, then describe the app or feature you want to understand.
111
+ Review the setup plan, approve it if appropriate, then describe the app or feature you want to understand. Hopper can run in its free demo mode; if it shows a first-run prompt, choose the demo or enter an existing license.
112
112
 
113
113
  ### From Terminal — no installation
114
114
 
@@ -118,7 +118,7 @@ npx -y rea-agents doctor
118
118
  npx -y rea-agents analyze /Applications/Notes.app
119
119
  ```
120
120
 
121
- Review the setup plan before confirming it. Restart a configured coding agent so it loads REA.
121
+ Review the setup plan before confirming it. Restart a configured agent so it loads REA.
122
122
 
123
123
  ### From Terminal — install the `rea` command
124
124
 
@@ -157,13 +157,13 @@ If something is not working, run:
157
157
  npx -y rea-agents doctor
158
158
  ```
159
159
 
160
- `rea doctor --json` is read-only and distinguishes unsupported hosts, missing dependencies, a missing local analysis engine, configuration drift, and healthy checks. Fresh-install activation is reported by setup because Hopper does not expose a reliable noninteractive activation probe.
160
+ `rea doctor --json` is read-only and distinguishes unsupported hosts, missing dependencies, a missing local analysis engine, configuration drift, and healthy checks. Paid-license activation is optional: on Linux, REA runs the supported Hopper demo build on a private Xvfb display and selects Hopper's offered demo mode for each analysis session.
161
161
 
162
162
  ### Linux installation and troubleshooting
163
163
 
164
- On macOS, approved setup downloads Hopper's official DMG, verifies it, and installs the app into `~/Applications` without Homebrew or administrator privileges. It opens Hopper once for activation; no manual drag-and-drop is required.
164
+ On macOS, approved setup downloads Hopper's official DMG, verifies it, and installs the app into `~/Applications` without Homebrew or administrator privileges. Hopper may show its demo or license prompt when first opened; no manual drag-and-drop is required.
165
165
 
166
- On Ubuntu 24.04+, Fedora 41+, and 64-bit Arch Linux, approved setup downloads the matching official Hopper package, restricts downloads to Hopper's public origin, verifies the published size and checksum, and invokes `apt-get`, `dnf`, or `pacman`. When REA is not already running as root, `pkexec` presents the system authorization prompt. REA never invokes `sudo`.
166
+ On Ubuntu 24.04+, Fedora 41+, and 64-bit Arch Linux, approved setup downloads the pinned official Hopper 6.4.2 package, restricts downloads to Hopper's public origin, verifies the published size and checksum, and invokes `apt-get`, `dnf`, or `pacman` to install Hopper and the Xvfb, Python, X11, and XTEST packages used by demo sessions. When REA is not already running as root, `pkexec` presents the system authorization prompt. REA never invokes `sudo`. Demo sessions run on an isolated 1280×1024 Xvfb display. REA verifies the exact supported Hopper binary, its owned process ancestry, the expected dialog geometry, and bridge state before selecting `Try the Demo`; any mismatch fails closed.
167
167
 
168
168
  The normal Linux launcher is `/opt/hopper/bin/Hopper`. If Hopper was installed elsewhere:
169
169
 
@@ -178,7 +178,7 @@ If doctor reports a missing analysis engine even though the file exists, inspect
178
178
  ldd /opt/hopper/bin/Hopper | grep 'not found'
179
179
  ```
180
180
 
181
- Install the missing distribution packages and rerun `rea setup`. Hopper is a desktop application: real analysis requires an active `DISPLAY` or `WAYLAND_DISPLAY`, plus one-time license activation. The curl installer places the `rea` command in `~/.local/bin` on Linux; add that directory to future shell `PATH` values if it is not already present.
181
+ Install the missing distribution packages and rerun `rea setup`. Linux demo automation requires `Xvfb`, Python 3, `libX11.so.6`, and `libXtst.so.6`; approved setup installs those direct runtime dependencies and does not interact with the user's desktop display. Hopper's free demo supports analysis with vendor-defined limits, and a paid license is optional. The curl installer places the `rea` command in `~/.local/bin` on Linux; add that directory to future shell `PATH` values if it is not already present.
182
182
 
183
183
  REA defaults `HOPPER_LAUNCHER_PATH` to `/Applications/Hopper Disassembler.app/Contents/MacOS/hopper` on macOS and `/opt/hopper/bin/Hopper` on Linux. Explicit configuration always takes precedence.
184
184
 
@@ -191,25 +191,40 @@ rea uninstall --purge-data # also removes only ~/.rea/cache and ~/.rea/state
191
191
 
192
192
  Uninstall preserves Hopper, Node.js, evidence, captures, external evidence roots, unrelated skills, and other MCP servers. It refuses malformed client configuration and never follows purge-data symlinks.
193
193
 
194
- ### CLI or coding agent?
194
+ ### CLI or agent?
195
195
 
196
- | If you want to… | Use |
197
- | --------------------------------------------------------- | -------------------------------------------------------------- |
198
- | Ask an agent to investigate an app and build a feature | Install the skill, then talk to your agent |
199
- | Inspect or decompile one part of an app from the Terminal | `rea analyze` or `rea decompile` |
200
- | Validate, canonicalize, or compare Evidence v2 bundles | `rea evidence-import`, `rea evidence-export`, or `rea compare` |
201
- | Import source as historical reference | `rea import-reference-source` |
202
- | Capture or compare controlled process behavior | `rea capture-process` or `rea compare-process-captures` |
196
+ | If you want to… | Use |
197
+ | --------------------------------------------------------------- | ------------------------------------------------------------------------- |
198
+ | Ask an agent to investigate an app and build a feature | Install the skill, then talk to your agent |
199
+ | Inspect or decompile one part of an app from the Terminal | `rea analyze` or `rea decompile` |
200
+ | Validate, canonicalize, or compare Evidence v2 bundles | `rea evidence-import`, `rea evidence-export`, or `rea compare` |
201
+ | Run or resume a persistent two-version artifact analysis | `rea investigate-versions` |
202
+ | Reuse immutable analysis results without relaunching a provider | Pass `--snapshot /approved/path/analysis.json` to a deep-analysis command |
203
+ | Import source as historical reference | `rea import-reference-source` |
204
+ | Capture or compare controlled process behavior | `rea capture-process` or `rea compare-process-captures` |
203
205
 
204
206
  Filesystem evidence commands and MCP file tools are disabled until the operator approves absolute roots:
205
207
 
206
208
  ```bash
207
209
  export REA_EVIDENCE_ROOTS_JSON='["/absolute/path/to/evidence"]'
210
+ export REA_INVESTIGATION_INPUT_ROOTS_JSON='["/absolute/path/to/releases"]'
208
211
  rea evidence-import /absolute/path/to/evidence/bundle.json
209
212
  rea evidence-export /absolute/path/to/evidence/bundle.json /absolute/path/to/evidence/canonical.json
210
213
  rea compare /absolute/path/to/evidence/left.json /absolute/path/to/evidence/right.json
214
+ rea investigate-versions /absolute/path/to/releases/v1 /absolute/path/to/releases/v2 /absolute/path/to/evidence/releases.json --yes --workspace-name releases
211
215
  ```
212
216
 
217
+ `investigate-versions` inventories both versions, checkpoints their observed
218
+ Evidence, derives an artifact comparison, and records a changed-behavior report.
219
+ Both input paths must resolve beneath `REA_INVESTIGATION_INPUT_ROOTS_JSON`;
220
+ workspace files remain independently restricted by `REA_EVIDENCE_ROOTS_JSON`.
221
+ The workspace uses deterministic content identities and monotonic CAS-linked
222
+ revisions, so the same request resumes an interrupted run or reuses a completed
223
+ run without replacing earlier investigations. It currently compares static
224
+ artifact structure only; it does not execute either version, and its report
225
+ keeps every difference labeled as a behavior candidate. See
226
+ [Persistent investigation workspaces](docs/investigation-workspaces.md).
227
+
213
228
  Historical source import requires a separate allowlist and never treats source as current behavioral authority:
214
229
 
215
230
  ```bash
@@ -219,6 +234,30 @@ rea import-reference-source /absolute/path/to/source
219
234
 
220
235
  Exports never replace an existing file unless `--overwrite` is explicit. Imports are size/depth bounded, validate every Evidence v2 ID and manifest, and never execute bundle content.
221
236
 
237
+ Provider-neutral analysis snapshots persist successful, immutable REA calls and
238
+ their Evidence v2 records. They are exact caches rather than Hopper databases:
239
+ REA reuses an entry only when the binary digest, format, architecture, operation
240
+ parameters, loader arguments, and provider identity match. Custom Hopper loader
241
+ overrides disable snapshots because their provider configuration cannot be
242
+ replayed safely. Cursor-dependent and mutating calls are never cached. Snapshot
243
+ files can contain proprietary analysis results and local
244
+ paths, so REA keeps them local, writes them with owner-only permissions, and
245
+ requires a separate approved root:
246
+
247
+ ```bash
248
+ export REA_ANALYSIS_SNAPSHOT_ROOTS_JSON='["/absolute/path/to/analysis"]'
249
+ rea analyze /absolute/path/to/app --snapshot /absolute/path/to/analysis/app.json
250
+ # The same exact query can now be answered from the snapshot.
251
+ rea analyze /absolute/path/to/app --snapshot /absolute/path/to/analysis/app.json
252
+ ```
253
+
254
+ Exact CLI evidence replays happen before any provider starts. In MCP sessions,
255
+ pass `snapshot_path` to `open_binary` to import a snapshot atomically while
256
+ opening its matching target; MCP providers may still start before a cached call
257
+ is replayed. Pass `snapshot_path` and, when required, `overwrite: true` to
258
+ `close_binary` to save atomically before Hopper resources are released. If the
259
+ save fails, REA deliberately leaves the session open.
260
+
222
261
  ## One prompt, a full investigation
223
262
 
224
263
  ```text
@@ -246,7 +285,7 @@ REA handles the app analysis in steps 1–5. The agent performs step 6 with its
246
285
  - Reconstruct an app's authentication, storage, update, or networking flow.
247
286
  - Recover enough structure to document an undocumented format or interface.
248
287
  - Trace a suspicious behavior from a string or symbol to the code that implements it.
249
- - Compare implementation paths across two app versions by switching targets in one session.
288
+ - Run, checkpoint, resume, and reuse a content-addressed artifact investigation across two versions.
250
289
  - Turn recovered behavior into product features, tests, migration notes, ports, or interoperable replacements.
251
290
  - Analyze Swift and Objective-C metadata without manually untangling every mangled symbol.
252
291
  - Leave names, comments, and bookmarks in Hopper so human and agent analysis reinforce each other.
@@ -273,6 +312,7 @@ REA is already useful for native application investigation on macOS:
273
312
  - Search and trace features across symbols, strings, metadata, references, and call paths.
274
313
  - Record every successful result as deterministic Evidence v2 with artifact and provider identity, confidence, authority, limitations, and locations.
275
314
  - Export and import evidence bundles across sessions.
315
+ - Persist automatic cross-version artifact runs as canonical, lock-protected workspaces with tamper-evident revision commitments.
276
316
  - Capture approved PTY scenarios as Process Capture v4 Evidence, including committed run manifests, raw and rendered terminal frames, scripted interactions, descendant settlement, named filesystem checkpoints, deterministic command shims, and loopback HTTP/WebSocket exchanges.
277
317
  - Compare complete artifact inventories by stable path, content, metadata, and relations; incomplete evidence never implies equivalence.
278
318
  - Compare explicit function dossiers across text, calls, references, strings, and address-normalized CFG topology with per-facet unknowns.
@@ -283,6 +323,7 @@ REA is already useful for native application investigation on macOS:
283
323
  - Verify finite behavioral and structural reconstruction specifications with pass, fail, and unknown kept distinct.
284
324
  - Track residual unknowns through immutable CAS revisions, evidence-qualified resolution, contradictions, probes, and validated dependency relationships.
285
325
  - With explicit `unknown_registry_approved: true`, record bounded trace/capture residuals, typed provider unavailability, and capture disagreements automatically.
326
+ - Start six [guided MCP workflows](docs/mcp-prompts.md) with live, session-aware completion for documents, procedures, providers, evidence, captures, artifact IDs, and active unknowns.
286
327
 
287
328
  Hopper is the first provider, not the boundary of the project. Some current workflows still require Hopper and macOS; every evidence record identifies the provider and limitations behind its result.
288
329
 
@@ -297,15 +338,15 @@ REA is growing into a toolkit for understanding software across static artifacts
297
338
  5. **Runtime observation** — approval-gated LLDB, Frida, system logs, process and filesystem observers, and native API tracing.
298
339
  6. **More static-analysis providers** — native platform utilities first, followed by Ghidra, IDA/Hex-Rays, Binary Ninja, Rizin, LIEF, and other engines behind provider-neutral capabilities.
299
340
  7. **More targets and platforms** — Windows-native providers and ConPTY verification, Linux parity, websites and APIs, mobile artifacts, firmware, document formats, and other software-defined systems.
300
- 8. **Differential reconstruction** — compare artifacts, functions, bundles, protocols, UIs, and process captures; track residual unknowns; verify a reconstruction against observed behavior.
341
+ 8. **Differential reconstruction expansion** — add automatic function matching, protocol/UI comparison, controlled replay, residual-unknown planning, and reconstruction verification to persistent version runs.
301
342
 
302
343
  Roadmap items describe direction, not shipped support. New providers must produce the same evidence and safety metadata as existing capabilities before they become part of the public workflow. Once REA has multiple optional toolchains, setup can become capability-selective; the consent rules for that future work are recorded in the [installation roadmap](docs/roadmap.md).
303
344
 
304
345
  See the [static-analysis provider evaluation](docs/provider-evaluation.md) for the current research matrix and admission gate.
305
346
 
306
- ## Using REA with other coding agents
347
+ ## Using REA with other agents
307
348
 
308
- Setup currently configures Claude Desktop and Cursor automatically. Any coding agent that supports local MCP servers can use REA with the configuration below.
349
+ Setup currently configures Claude Desktop and Cursor automatically. Any agent that supports local MCP servers can use REA with the configuration below.
309
350
 
310
351
  ### Manual MCP configuration
311
352
 
@@ -320,11 +361,16 @@ Setup currently configures Claude Desktop and Cursor automatically. Any coding a
320
361
  }
321
362
  ```
322
363
 
364
+ MCP clients that support prompts can also discover six ordered investigation
365
+ workflows through `prompts/list`. Their optional identifier arguments use the
366
+ current session for bounded `completion/complete` suggestions; see
367
+ [Guided MCP prompts and completion](docs/mcp-prompts.md).
368
+
323
369
  ## How it works
324
370
 
325
371
  ```mermaid
326
372
  flowchart LR
327
- Agent["Coding agent"] --> REA["REA<br/>CLI + MCP"]
373
+ Agent["Agent"] --> REA["REA<br/>CLI + MCP"]
328
374
  Terminal --> REA
329
375
  REA --> Workspace["Investigation workspace<br/>evidence + artifacts + captures"]
330
376
  Workspace --> Router["Capability router"]
@@ -339,7 +385,7 @@ flowchart LR
339
385
  Artifact --> Target
340
386
  ```
341
387
 
342
- The CLI and MCP server use the same application workflows and evidence contracts. A provider declares which capabilities it supports and the side effects those capabilities may have. Terminal commands are short-lived; an MCP session can retain an active target and evidence ledger across an investigation.
388
+ The CLI and MCP server use the same application workflows and evidence contracts. A provider declares which capabilities it supports and the side effects those capabilities may have. Terminal commands are short-lived; an MCP session can retain an active target and evidence ledger across an investigation. Approved persistent workspaces keep canonical Evidence and resumable run checkpoints across both process and session lifetimes.
343
389
 
344
390
  ## CLI
345
391
 
@@ -354,6 +400,7 @@ npx -y rea-agents function /Applications/Notes.app 0x1000
354
400
  npx -y rea-agents xrefs /Applications/Notes.app 0x1000
355
401
  npx -y rea-agents trace /Applications/Notes.app "offline"
356
402
  npx -y rea-agents compare /absolute/path/to/left-evidence.json /absolute/path/to/right-evidence.json
403
+ npx -y rea-agents investigate-versions /path/to/v1 /path/to/v2 /absolute/path/to/evidence/releases.json --yes
357
404
  npx -y rea-agents capabilities
358
405
  npx -y rea-agents providers
359
406
  ```
@@ -373,6 +420,27 @@ rea mcp
373
420
 
374
421
  REA accepts a Mac `.app` folder directly. If an agent cannot find an app by name, tell it where the app is installed.
375
422
 
423
+ ### CLI exit status
424
+
425
+ | Status | Meaning |
426
+ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
427
+ | `0` | The requested operation completed. Truthful unknowns, warnings, and bounded or truncated evidence remain successful results. |
428
+ | `1` | Arguments, policy, permission, provider analysis, integrity checking, cancellation, timeout, setup, diagnostics, update, uninstall, output encoding, or output writing prevented completion. Structured output identifies the failure category when REA could encode it. |
429
+ | `128 + N` | The process ended from signal `N`, where the shell or runtime preserves the conventional signal-derived status. |
430
+
431
+ `setup` returns `1` for `planned`, `needs_confirmation`, or `needs_human`
432
+ because configuration is not ready; rerun it after approval or remediation.
433
+ `doctor` returns `1` when any check is unhealthy. Output format, full envelopes,
434
+ filters, and token controls never change the operation status.
435
+
436
+ When REA feeds a shell pipeline, enable `pipefail` so a downstream formatter
437
+ cannot hide its failure:
438
+
439
+ ```bash
440
+ set -o pipefail
441
+ rea inventory-artifact ./app.asar --json | jq . > inventory.json
442
+ ```
443
+
376
444
  ## Current Hopper provider
377
445
 
378
446
  REA starts Hopper when needed; Hopper does not need to be running first. Hopper's launcher internally activates the application, so opening a target may bring Hopper to the foreground. REA asks macOS to start Hopper hidden and in the background when possible, but cannot guarantee that it will remain behind the current application.
@@ -442,7 +510,7 @@ No. Setup can install Hopper for you, but Hopper remains separate software with
442
510
  <details>
443
511
  <summary><strong>Does REA upload the app?</strong></summary>
444
512
 
445
- REA has no hosted analysis service. Current providers analyze artifacts and capture behavior locally. Your coding agent or model provider may have its own data policy, so review that separately.
513
+ REA has no hosted analysis service. Current providers analyze artifacts and capture behavior locally. Your agent or model provider may have its own data policy, so review that separately.
446
514
 
447
515
  </details>
448
516
 
@@ -456,7 +524,7 @@ No decompiler can guarantee the original source. REA gives an agent pseudocode,
456
524
  <details>
457
525
  <summary><strong>Which agents can use REA?</strong></summary>
458
526
 
459
- Any coding agent that can run a local MCP server can use the manual configuration. Setup currently detects and configures Claude Desktop and Cursor automatically.
527
+ Any agent that can run a local MCP server can use the manual configuration. Setup currently detects and configures Claude Desktop and Cursor automatically.
460
528
 
461
529
  </details>
462
530
 
@@ -20,6 +20,22 @@ MAX_SEARCH_PATTERN_LENGTH = 256
20
20
  MAX_SEARCH_VALUE_LENGTH = 4096
21
21
 
22
22
 
23
+ def _session_document():
24
+ """Find only the document opened for this authenticated REA session."""
25
+ target = os.path.realpath(REA_TARGET_PATH)
26
+ for document in Document.getAllDocuments():
27
+ paths = (document.getExecutableFilePath(), document.getDatabaseFilePath())
28
+ for path in paths:
29
+ if path and os.path.realpath(path) == target:
30
+ return document
31
+ return None
32
+
33
+
34
+ MAX_REGEX_BACKTRACKING_PATHS = 10000
35
+ MAX_REGEX_CANDIDATE_LENGTH = 4096
36
+ MAX_REGEX_SEARCH_WORK_UNITS = 1000000
37
+
38
+
23
39
  def _hex(value):
24
40
  return "0x%x" % value
25
41
 
@@ -216,33 +232,150 @@ def _search_inventory(document, kind):
216
232
  return inventory
217
233
 
218
234
 
235
+ def _checked_regex_paths(left, right, operation):
236
+ """Apply one path-count operation without crossing the static work budget."""
237
+ if operation == "add":
238
+ exceeded = left > MAX_REGEX_BACKTRACKING_PATHS - right
239
+ result = left + right
240
+ else:
241
+ exceeded = right != 0 and left > MAX_REGEX_BACKTRACKING_PATHS // right
242
+ result = left * right
243
+ if exceeded or result > MAX_REGEX_BACKTRACKING_PATHS:
244
+ raise ValueError(
245
+ "Regex exceeds the %d-path backtracking budget"
246
+ % MAX_REGEX_BACKTRACKING_PATHS
247
+ )
248
+ return result
249
+
250
+
251
+ def _repeat_regex_paths(child_paths, minimum, maximum):
252
+ """Count every bounded repetition path, including alternative child paths."""
253
+ paths = 0
254
+ repeated_paths = 1
255
+ for count in range(maximum + 1):
256
+ if count >= minimum:
257
+ paths = _checked_regex_paths(paths, repeated_paths, "add")
258
+ if count < maximum:
259
+ repeated_paths = _checked_regex_paths(
260
+ repeated_paths, child_paths, "multiply"
261
+ )
262
+ return paths
263
+
264
+
265
+ def _validate_regex_class(items):
266
+ """Accept only constant-time character-class operations."""
267
+ allowed = {
268
+ sre_parse.CATEGORY,
269
+ sre_parse.LITERAL,
270
+ sre_parse.NEGATE,
271
+ sre_parse.RANGE,
272
+ }
273
+ if any(operation not in allowed for operation, _ in items):
274
+ raise ValueError("Regex operation is not supported by the bounded matcher")
275
+
276
+
219
277
  def _validate_regex_node(node, inside_repeat=False):
220
- """Reject regex structures with disproportionate or non-local evaluation cost."""
278
+ """Return capped path and step bounds for Python regex evaluation."""
279
+ leaf_operations = {
280
+ sre_parse.ANY,
281
+ sre_parse.AT,
282
+ sre_parse.CATEGORY,
283
+ sre_parse.LITERAL,
284
+ sre_parse.NOT_LITERAL,
285
+ }
221
286
  forbidden = {
222
287
  sre_parse.ASSERT,
223
288
  sre_parse.ASSERT_NOT,
224
289
  sre_parse.GROUPREF,
225
290
  sre_parse.GROUPREF_EXISTS,
226
291
  }
292
+ for name in ("GROUPREF_IGNORE", "GROUPREF_LOC_IGNORE", "GROUPREF_UNI_IGNORE"):
293
+ operation = getattr(sre_parse, name, None)
294
+ if operation is not None:
295
+ forbidden.add(operation)
227
296
  repeat_tokens = {sre_parse.MAX_REPEAT, sre_parse.MIN_REPEAT}
228
297
  possessive = getattr(sre_parse, "POSSESSIVE_REPEAT", None)
229
298
  if possessive is not None:
230
299
  repeat_tokens.add(possessive)
300
+ atomic = getattr(sre_parse, "ATOMIC_GROUP", None)
301
+
302
+ paths = 1
303
+ steps = 0
231
304
  for operation, argument in node:
232
305
  if operation in forbidden:
233
306
  raise ValueError("Regex lookarounds and backreferences are not supported")
234
- if operation in repeat_tokens:
307
+ if operation in leaf_operations:
308
+ operation_paths = 1
309
+ operation_steps = 1
310
+ elif operation == sre_parse.IN:
311
+ _validate_regex_class(argument)
312
+ operation_paths = 1
313
+ operation_steps = 1
314
+ elif operation in repeat_tokens:
235
315
  if inside_repeat:
236
316
  raise ValueError("Nested regex repetitions are not supported")
237
317
  minimum, maximum, child = argument
238
318
  if maximum == sre_parse.MAXREPEAT or maximum > 1000:
239
- raise ValueError("Unbounded or excessive regex repetitions are not supported")
240
- _validate_regex_node(child, True)
319
+ raise ValueError(
320
+ "Unbounded or excessive regex repetitions are not supported"
321
+ )
322
+ child_paths, child_steps = _validate_regex_node(child, True)
323
+ operation_paths = _repeat_regex_paths(
324
+ child_paths, minimum, maximum
325
+ )
326
+ operation_steps = maximum * child_steps
241
327
  elif operation == sre_parse.SUBPATTERN:
242
- _validate_regex_node(argument[-1], inside_repeat)
328
+ operation_paths, operation_steps = _validate_regex_node(
329
+ argument[-1], inside_repeat
330
+ )
243
331
  elif operation == sre_parse.BRANCH:
332
+ operation_paths = 0
333
+ operation_steps = 0
244
334
  for branch in argument[1]:
245
- _validate_regex_node(branch, inside_repeat)
335
+ branch_paths, branch_steps = _validate_regex_node(
336
+ branch, inside_repeat
337
+ )
338
+ operation_paths = _checked_regex_paths(
339
+ operation_paths,
340
+ branch_paths,
341
+ "add",
342
+ )
343
+ operation_steps = max(operation_steps, branch_steps)
344
+ elif atomic is not None and operation == atomic:
345
+ operation_paths, operation_steps = _validate_regex_node(
346
+ argument, inside_repeat
347
+ )
348
+ else:
349
+ raise ValueError("Regex operation is not supported by the bounded matcher")
350
+ paths = _checked_regex_paths(paths, operation_paths, "multiply")
351
+ steps += operation_steps
352
+ return paths, steps
353
+
354
+
355
+ def _bounded_regex_matcher(expression, backtracking_paths, steps_per_path):
356
+ """Create a matcher with per-candidate and cumulative work bounds."""
357
+ remaining_work = MAX_REGEX_SEARCH_WORK_UNITS
358
+ work_per_character = backtracking_paths * max(steps_per_path, 1)
359
+
360
+ def matches(value):
361
+ nonlocal remaining_work
362
+ if not isinstance(value, str):
363
+ raise ValueError("Regex candidates must be strings")
364
+ if len(value) > MAX_REGEX_CANDIDATE_LENGTH:
365
+ raise ValueError(
366
+ "Regex candidate exceeds the %d-character safety limit"
367
+ % MAX_REGEX_CANDIDATE_LENGTH
368
+ )
369
+ required_work = work_per_character * max(len(value), 1)
370
+ if required_work > remaining_work:
371
+ raise ValueError(
372
+ "Regex search exceeds the %d-unit work budget"
373
+ % MAX_REGEX_SEARCH_WORK_UNITS
374
+ )
375
+ remaining_work -= required_work
376
+ return expression.search(value) is not None
377
+
378
+ return matches
246
379
 
247
380
 
248
381
  def _search_page(document, kind, params):
@@ -268,11 +401,13 @@ def _search_page(document, kind, params):
268
401
  else:
269
402
  try:
270
403
  parsed = sre_parse.parse(pattern)
271
- _validate_regex_node(parsed)
404
+ backtracking_paths, steps_per_path = _validate_regex_node(parsed)
272
405
  expression = re.compile(pattern, 0 if case_sensitive else re.IGNORECASE)
273
- except re.error as error:
406
+ except (re.error, OverflowError) as error:
274
407
  raise ValueError("Invalid regex pattern") from error
275
- matches = lambda value: expression.search(value) is not None
408
+ matches = _bounded_regex_matcher(
409
+ expression, backtracking_paths, steps_per_path
410
+ )
276
411
 
277
412
  selected = []
278
413
  total = 0
@@ -493,8 +628,29 @@ def _dispatch(method, params):
493
628
  global _selected_document
494
629
  if method == "health":
495
630
  return {"name": "REA Hopper bridge", "version": "1.0.0", "run_id": REA_RUN_ID}
496
- if method == "shutdown":
497
- return {"shutdown": True}
631
+ if method in ("shutdown", "shutdown_document"):
632
+ document = _session_document()
633
+ if document is None:
634
+ return {"shutdown": True, "analysis_stopped": True, "document_closed": True}
635
+ if document.backgroundProcessActive():
636
+ document.requestBackgroundProcessStop()
637
+ if method == "shutdown" and REA_OWNS_PROCESS_LIFETIME:
638
+ return {
639
+ "shutdown": True,
640
+ "analysis_stopped": not document.backgroundProcessActive(),
641
+ "document_closed": False,
642
+ "cleanup_required": True,
643
+ }
644
+ if document.backgroundProcessActive():
645
+ document.waitForBackgroundProcessToEnd()
646
+ document.closeDocument()
647
+ analysis_stopped = not document.backgroundProcessActive()
648
+ document_closed = _session_document() is None
649
+ return {
650
+ "shutdown": True,
651
+ "analysis_stopped": analysis_stopped,
652
+ "document_closed": document_closed,
653
+ }
498
654
  if method == "list_documents":
499
655
  return [document.getDocumentName() for document in Document.getAllDocuments()]
500
656
  if method == "current_document":
@@ -604,9 +760,9 @@ def _dispatch(method, params):
604
760
  if method == "procedure_pseudo_code":
605
761
  return procedure.decompile()
606
762
  if method == "procedure_callers":
607
- return [_procedure_name(item) for item in procedure.getAllCallerProcedures()]
763
+ return sorted((_hex(item.getEntryPoint()) for item in procedure.getAllCallerProcedures()), key=lambda value: int(value, 16))
608
764
  if method == "procedure_callees":
609
- return [_procedure_name(item) for item in procedure.getAllCalleeProcedures()]
765
+ return sorted((_hex(item.getEntryPoint()) for item in procedure.getAllCalleeProcedures()), key=lambda value: int(value, 16))
610
766
  if method == "procedure_info":
611
767
  blocks = list(procedure.basicBlockIterator())
612
768
  length = sum(max(0, block.getEndingAddress() - block.getStartingAddress()) for block in blocks)
@@ -657,7 +813,9 @@ def _serve_connection(connection):
657
813
  if not isinstance(request["token"], str) or not hmac.compare_digest(request["token"], REA_TOKEN):
658
814
  raise PermissionError("Invalid bridge capability")
659
815
  result = _dispatch(request["method"], request["params"])
660
- should_stop = request["method"] == "shutdown"
816
+ should_stop = request["method"] == "shutdown_document" or (
817
+ request["method"] == "shutdown" and not result.get("cleanup_required", False)
818
+ )
661
819
  response = {"id": request_id, "result": _json_safe(result)}
662
820
  except Exception as error:
663
821
  response = {"id": request_id if isinstance(request_id, int) else 0, "error": {"code": -32000, "message": str(error)[:512], "type": _diagnostic_type(error)}}