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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rea-agents",
3
- "version": "1.2.0",
4
- "description": "Reverse engineer anything from your terminal or coding agent with one CLI and MCP server.",
3
+ "version": "1.4.0",
4
+ "description": "Reverse engineer anything from your terminal or agent with one CLI and MCP server.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -14,7 +14,7 @@
14
14
  "keywords": [
15
15
  "reverse-engineering",
16
16
  "binary-analysis",
17
- "coding-agents",
17
+ "agents",
18
18
  "agent-skills",
19
19
  "cli",
20
20
  "mcp-server",
@@ -43,11 +43,13 @@
43
43
  "scripts/rea.mjs",
44
44
  "scripts/prepare-node-pty.mjs",
45
45
  "scripts/rebuild-native.mjs",
46
+ "scripts/hopper-demo-x11.py",
46
47
  "skills",
47
48
  "README.md",
48
49
  "LICENSE"
49
50
  ],
50
51
  "scripts": {
52
+ "prebuild": "node scripts/generate-package-metadata.mjs",
51
53
  "build": "tsc -p tsconfig.build.json",
52
54
  "check": "npm run typecheck && npm run lint && npm run format:check && npm test",
53
55
  "format": "prettier --write .",
@@ -69,12 +71,14 @@
69
71
  "typecheck": "tsc --noEmit",
70
72
  "verify:hopper": "npm run build && node scripts/verify-real-hopper.mjs",
71
73
  "verify:hopper:linux": "npm run build && node scripts/verify-real-hopper-linux.mjs",
74
+ "verify:setup:linux": "npm run build && node scripts/verify-linux-setup-e2e.mjs",
72
75
  "build:fixtures": "node scripts/build-conformance-fixtures.mjs",
73
76
  "verify:fixtures": "npm run build:fixtures && node scripts/verify-conformance-fixtures.mjs",
74
77
  "verify:package": "npm run build && node scripts/verify-package.mjs",
75
78
  "verify:published": "node scripts/verify-published-package.mjs",
76
79
  "config:print": "node scripts/print-mcp-config.mjs",
77
- "docs:generate": "typedoc",
80
+ "docs:generate": "npm run build && typedoc && node scripts/generate-error-schema.mjs",
81
+ "docs:error-schema": "npm run build && node scripts/generate-error-schema.mjs",
78
82
  "prepack": "npm run build",
79
83
  "prepare": "husky",
80
84
  "rebuild:native": "node scripts/rebuild-native.mjs"
@@ -87,7 +91,7 @@
87
91
  "@babel/parser": "8.0.4",
88
92
  "@babel/types": "8.0.4",
89
93
  "@electron/asar": "4.2.0",
90
- "@modelcontextprotocol/server": "2.0.0-beta.3",
94
+ "@modelcontextprotocol/server": "2.0.0-beta.4",
91
95
  "@xterm/addon-serialize": "0.14.0",
92
96
  "@xterm/headless": "6.0.0",
93
97
  "@zip.js/zip.js": "2.8.26",
@@ -108,7 +112,7 @@
108
112
  },
109
113
  "devDependencies": {
110
114
  "@fast-check/vitest": "0.4.1",
111
- "@modelcontextprotocol/client": "2.0.0-beta.3",
115
+ "@modelcontextprotocol/client": "2.0.0-beta.4",
112
116
  "@types/node": "24.10.1",
113
117
  "@types/write-file-atomic": "4.0.3",
114
118
  "@types/ws": "8.18.1",
@@ -0,0 +1,349 @@
1
+ #!/usr/bin/env python3
2
+ """Inspect or activate Hopper's version-pinned demo dialog on a private X display."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import ctypes
8
+ import hashlib
9
+ import os
10
+ from pathlib import Path
11
+ import secrets
12
+ import subprocess
13
+ import time
14
+
15
+
16
+ # Keep this digest aligned with LinuxHopper.ts and rerun real-Hopper verification
17
+ # before accepting another vendor build.
18
+ SUPPORTED_HOPPER_SHA256 = {
19
+ "0294ced141cc373468ee22d8343e7dac41980cb05a937994ca81c9f09afe7ded"
20
+ }
21
+ EXPECTED_SCREEN = (1280, 1024)
22
+ EXPECTED_DIALOG = (189, 370, 901, 284)
23
+ DEMO_CLICK = (305, 632)
24
+ PRIVATE_DISPLAY_UNAVAILABLE = 70
25
+ X11_AUTHORIZATION_FAILED = 71
26
+ UNSUPPORTED_HOPPER_BUILD = 72
27
+ INVALID_LAUNCH_COMMAND = 73
28
+ PROCESS_OWNERSHIP_MISMATCH = 74
29
+ HOPPER_EXITED_DURING_STARTUP = 75
30
+ UNSUPPORTED_DEMO_DIALOG = 76
31
+ UNEXPECTED_DISPLAY_GEOMETRY = 77
32
+ X11_INPUT_FAILED = 78
33
+ RUNTIME_DEPENDENCY_UNAVAILABLE = 79
34
+
35
+
36
+ class AdapterFailure(Exception):
37
+ def __init__(self, code: int, message: str) -> None:
38
+ super().__init__(message)
39
+ self.code = code
40
+
41
+
42
+ class XWindowAttributes(ctypes.Structure):
43
+ _fields_ = [
44
+ ("x", ctypes.c_int),
45
+ ("y", ctypes.c_int),
46
+ ("width", ctypes.c_int),
47
+ ("height", ctypes.c_int),
48
+ ("border_width", ctypes.c_int),
49
+ ("depth", ctypes.c_int),
50
+ ("visual", ctypes.c_void_p),
51
+ ("root", ctypes.c_ulong),
52
+ ("class_", ctypes.c_int),
53
+ ("bit_gravity", ctypes.c_int),
54
+ ("win_gravity", ctypes.c_int),
55
+ ("backing_store", ctypes.c_int),
56
+ ("backing_planes", ctypes.c_ulong),
57
+ ("backing_pixel", ctypes.c_ulong),
58
+ ("save_under", ctypes.c_int),
59
+ ("colormap", ctypes.c_ulong),
60
+ ("map_installed", ctypes.c_int),
61
+ ("map_state", ctypes.c_int),
62
+ ("all_event_masks", ctypes.c_long),
63
+ ("your_event_mask", ctypes.c_long),
64
+ ("do_not_propagate_mask", ctypes.c_long),
65
+ ("override_redirect", ctypes.c_int),
66
+ ("screen", ctypes.c_void_p),
67
+ ]
68
+
69
+
70
+ def parse_args() -> argparse.Namespace:
71
+ parser = argparse.ArgumentParser()
72
+ parser.add_argument("--hopper", required=True)
73
+ parser.add_argument("--socket", required=True)
74
+ parser.add_argument("command", nargs=argparse.REMAINDER)
75
+ return parser.parse_args()
76
+
77
+
78
+ def digest(path: Path) -> str:
79
+ value = hashlib.sha256()
80
+ with path.open("rb") as file:
81
+ for chunk in iter(lambda: file.read(1024 * 1024), b""):
82
+ value.update(chunk)
83
+ return value.hexdigest()
84
+
85
+
86
+ def descendants(pid: int) -> set[int]:
87
+ found = {pid}
88
+ changed = True
89
+ while changed:
90
+ changed = False
91
+ for stat in Path("/proc").glob("[0-9]*/stat"):
92
+ try:
93
+ fields = stat.read_text().split()
94
+ child = int(fields[0])
95
+ parent = int(fields[3])
96
+ except (OSError, ValueError, IndexError):
97
+ continue
98
+ if parent in found and child not in found:
99
+ found.add(child)
100
+ changed = True
101
+ return found
102
+
103
+
104
+ def hopper_is_owned(leader: int, hopper: Path) -> bool:
105
+ expected = hopper.resolve()
106
+ for pid in descendants(leader):
107
+ try:
108
+ if Path(f"/proc/{pid}/exe").resolve() == expected:
109
+ return True
110
+ except OSError:
111
+ continue
112
+ return False
113
+
114
+
115
+ def windows(x11: ctypes.CDLL, display: int, root: int) -> list[tuple[int, int, int, int, int]]:
116
+ found: list[tuple[int, int, int, int, int]] = []
117
+ pending = [root]
118
+ while pending:
119
+ window = pending.pop()
120
+ attributes = XWindowAttributes()
121
+ if x11.XGetWindowAttributes(display, window, ctypes.byref(attributes)):
122
+ if attributes.map_state == 2:
123
+ found.append(
124
+ (window, attributes.x, attributes.y, attributes.width, attributes.height)
125
+ )
126
+ root_return = ctypes.c_ulong()
127
+ parent_return = ctypes.c_ulong()
128
+ children = ctypes.POINTER(ctypes.c_ulong)()
129
+ count = ctypes.c_uint()
130
+ if x11.XQueryTree(
131
+ display,
132
+ window,
133
+ ctypes.byref(root_return),
134
+ ctypes.byref(parent_return),
135
+ ctypes.byref(children),
136
+ ctypes.byref(count),
137
+ ):
138
+ pending.extend(children[index] for index in range(count.value))
139
+ if children:
140
+ x11.XFree(children)
141
+ return found
142
+
143
+
144
+ def configure_x11(x11: ctypes.CDLL, xtst: ctypes.CDLL) -> None:
145
+ window_pointer = ctypes.POINTER(ctypes.c_ulong)
146
+ x11.XOpenDisplay.argtypes = [ctypes.c_char_p]
147
+ x11.XOpenDisplay.restype = ctypes.c_void_p
148
+ x11.XDefaultRootWindow.argtypes = [ctypes.c_void_p]
149
+ x11.XDefaultRootWindow.restype = ctypes.c_ulong
150
+ x11.XDisplayWidth.argtypes = [ctypes.c_void_p, ctypes.c_int]
151
+ x11.XDisplayWidth.restype = ctypes.c_int
152
+ x11.XDisplayHeight.argtypes = [ctypes.c_void_p, ctypes.c_int]
153
+ x11.XDisplayHeight.restype = ctypes.c_int
154
+ x11.XGetWindowAttributes.argtypes = [
155
+ ctypes.c_void_p,
156
+ ctypes.c_ulong,
157
+ ctypes.POINTER(XWindowAttributes),
158
+ ]
159
+ x11.XGetWindowAttributes.restype = ctypes.c_int
160
+ x11.XQueryTree.argtypes = [
161
+ ctypes.c_void_p,
162
+ ctypes.c_ulong,
163
+ window_pointer,
164
+ window_pointer,
165
+ ctypes.POINTER(window_pointer),
166
+ ctypes.POINTER(ctypes.c_uint),
167
+ ]
168
+ x11.XQueryTree.restype = ctypes.c_int
169
+ x11.XFree.argtypes = [ctypes.c_void_p]
170
+ x11.XFree.restype = ctypes.c_int
171
+ x11.XFlush.argtypes = [ctypes.c_void_p]
172
+ x11.XFlush.restype = ctypes.c_int
173
+ x11.XCloseDisplay.argtypes = [ctypes.c_void_p]
174
+ x11.XCloseDisplay.restype = ctypes.c_int
175
+ xtst.XTestFakeMotionEvent.argtypes = [
176
+ ctypes.c_void_p,
177
+ ctypes.c_int,
178
+ ctypes.c_int,
179
+ ctypes.c_int,
180
+ ctypes.c_ulong,
181
+ ]
182
+ xtst.XTestFakeMotionEvent.restype = ctypes.c_int
183
+ xtst.XTestFakeButtonEvent.argtypes = [
184
+ ctypes.c_void_p,
185
+ ctypes.c_uint,
186
+ ctypes.c_int,
187
+ ctypes.c_ulong,
188
+ ]
189
+ xtst.XTestFakeButtonEvent.restype = ctypes.c_int
190
+
191
+
192
+ def start_xvfb(session: Path) -> tuple[subprocess.Popen[bytes], str, Path]:
193
+ authority = session / "Xauthority"
194
+ for _ in range(20):
195
+ display_number = 100 + secrets.randbelow(900)
196
+ display_name = f":{display_number}"
197
+ if Path(f"/tmp/.X11-unix/X{display_number}").exists():
198
+ continue
199
+ cookie = secrets.token_hex(16)
200
+ try:
201
+ subprocess.run(
202
+ [
203
+ "/usr/bin/xauth", "-f", str(authority), "add",
204
+ display_name, ".", cookie,
205
+ ],
206
+ check=True,
207
+ stdout=subprocess.DEVNULL,
208
+ stderr=subprocess.DEVNULL,
209
+ )
210
+ except (OSError, subprocess.CalledProcessError) as error:
211
+ raise AdapterFailure(
212
+ X11_AUTHORIZATION_FAILED, "private X authorization failed"
213
+ ) from error
214
+ authority.chmod(0o600)
215
+ process = subprocess.Popen(
216
+ [
217
+ "/usr/bin/Xvfb",
218
+ display_name,
219
+ "-screen",
220
+ "0",
221
+ "1280x1024x24",
222
+ "-nolisten",
223
+ "tcp",
224
+ "-auth",
225
+ str(authority),
226
+ ],
227
+ stdout=subprocess.DEVNULL,
228
+ stderr=subprocess.DEVNULL,
229
+ )
230
+ deadline = time.monotonic() + 2
231
+ socket = Path(f"/tmp/.X11-unix/X{display_number}")
232
+ while time.monotonic() < deadline and process.poll() is None:
233
+ if socket.exists():
234
+ return process, display_name, authority
235
+ time.sleep(0.05)
236
+ if process.poll() is None:
237
+ process.terminate()
238
+ process.wait()
239
+ raise AdapterFailure(PRIVATE_DISPLAY_UNAVAILABLE, "private X display unavailable")
240
+
241
+
242
+ def main() -> int:
243
+ args = parse_args()
244
+ hopper = Path(args.hopper)
245
+ socket = Path(args.socket)
246
+ if digest(hopper) not in SUPPORTED_HOPPER_SHA256:
247
+ raise AdapterFailure(UNSUPPORTED_HOPPER_BUILD, "unsupported Hopper binary")
248
+ command = args.command[1:] if args.command[:1] == ["--"] else args.command
249
+ if not command or Path(command[0]).resolve() != hopper.resolve():
250
+ raise AdapterFailure(INVALID_LAUNCH_COMMAND, "invalid Hopper launch command")
251
+ xvfb, display_name, authority = start_xvfb(socket.parent)
252
+ child: subprocess.Popen[bytes] | None = None
253
+ display: int | None = None
254
+ try:
255
+ hopper_environment = dict(os.environ)
256
+ for name in ("WAYLAND_DISPLAY", "WAYLAND_SOCKET"):
257
+ hopper_environment.pop(name, None)
258
+ hopper_environment.update(
259
+ {
260
+ "DISPLAY": display_name,
261
+ "XAUTHORITY": str(authority),
262
+ "GDK_BACKEND": "x11",
263
+ "QT_QPA_PLATFORM": "xcb",
264
+ "XDG_SESSION_TYPE": "x11",
265
+ }
266
+ )
267
+ child = subprocess.Popen(command, env=hopper_environment)
268
+ leader = os.getpid()
269
+ os.environ["XAUTHORITY"] = str(authority)
270
+
271
+ try:
272
+ x11 = ctypes.CDLL("libX11.so.6")
273
+ xtst = ctypes.CDLL("libXtst.so.6")
274
+ except OSError as error:
275
+ raise AdapterFailure(
276
+ RUNTIME_DEPENDENCY_UNAVAILABLE, "X11 runtime unavailable"
277
+ ) from error
278
+ configure_x11(x11, xtst)
279
+ display = x11.XOpenDisplay(display_name.encode())
280
+ if not display:
281
+ raise AdapterFailure(PRIVATE_DISPLAY_UNAVAILABLE, "private X display unavailable")
282
+ root = x11.XDefaultRootWindow(display)
283
+ if (
284
+ x11.XDisplayWidth(display, 0),
285
+ x11.XDisplayHeight(display, 0),
286
+ ) != EXPECTED_SCREEN:
287
+ raise AdapterFailure(
288
+ UNEXPECTED_DISPLAY_GEOMETRY, "unexpected private display geometry"
289
+ )
290
+
291
+ deadline = time.monotonic() + 10
292
+ observed: list[tuple[int, int, int, int, int]] = []
293
+ while time.monotonic() < deadline:
294
+ if socket.exists():
295
+ x11.XCloseDisplay(display)
296
+ display = None
297
+ return child.wait()
298
+ if not hopper_is_owned(leader, hopper):
299
+ if child.poll() is None:
300
+ time.sleep(0.1)
301
+ continue
302
+ raise AdapterFailure(
303
+ HOPPER_EXITED_DURING_STARTUP, "owned Hopper process exited"
304
+ )
305
+ observed = windows(x11, display, root)
306
+ if any(window[1:] == EXPECTED_DIALOG for window in observed):
307
+ break
308
+ time.sleep(0.1)
309
+ else:
310
+ if not hopper_is_owned(leader, hopper):
311
+ raise AdapterFailure(
312
+ PROCESS_OWNERSHIP_MISMATCH, "Hopper process ownership mismatch"
313
+ )
314
+ raise AdapterFailure(
315
+ UNSUPPORTED_DEMO_DIALOG, "expected Hopper demo dialog not found"
316
+ )
317
+
318
+ input_results = (
319
+ xtst.XTestFakeMotionEvent(display, -1, DEMO_CLICK[0], DEMO_CLICK[1], 0),
320
+ xtst.XTestFakeButtonEvent(display, 1, True, 0),
321
+ xtst.XTestFakeButtonEvent(display, 1, False, 0),
322
+ )
323
+ if not all(input_results):
324
+ raise AdapterFailure(X11_INPUT_FAILED, "private X input failed")
325
+ x11.XFlush(display)
326
+ x11.XCloseDisplay(display)
327
+ display = None
328
+ return child.wait()
329
+ finally:
330
+ if display is not None:
331
+ x11.XCloseDisplay(display)
332
+ if child is not None and child.poll() is None:
333
+ child.terminate()
334
+ child.wait()
335
+ if xvfb.poll() is None:
336
+ xvfb.terminate()
337
+ try:
338
+ xvfb.wait(timeout=2)
339
+ except subprocess.TimeoutExpired:
340
+ xvfb.kill()
341
+ xvfb.wait()
342
+
343
+
344
+ if __name__ == "__main__":
345
+ try:
346
+ raise SystemExit(main())
347
+ except AdapterFailure as error:
348
+ print(str(error), file=os.sys.stderr)
349
+ raise SystemExit(error.code) from None
package/scripts/rea.mjs CHANGED
@@ -11,9 +11,12 @@ const isMcpMode =
11
11
  args[0] === "--mcp" || (args.length === 1 && args[0] === "mcp");
12
12
 
13
13
  if (isMcpMode) {
14
- const { run } = await import("../dist/main.js");
15
- process.exitCode = await run();
14
+ const { runEntrypoint } = await import("../dist/main.js");
15
+ await runEntrypoint();
16
16
  } else {
17
17
  const { createCli } = await import("../dist/cli.js");
18
- await createCli().serve(args);
18
+ const { sanitizeCliOutput } = await import("../dist/cliOutput.js");
19
+ await createCli().serve(args, {
20
+ stdout: (output) => process.stdout.write(sanitizeCliOutput(output)),
21
+ });
19
22
  }
@@ -2,7 +2,7 @@
2
2
  name: rea-analysis
3
3
  description: Reverse engineer apps with REA. Explore how features work, then build a version tailored to your project.
4
4
  metadata:
5
- version: "11"
5
+ version: "12"
6
6
  tool_count: 68
7
7
  ---
8
8
 
@@ -24,7 +24,7 @@ Notes is only a documentation example. Never select an app unless the user names
24
24
  2. If setup is needed, tell the user REA needs to install its local binary-analysis tools. Do not lead with implementation details or assume the user knows reverse-engineering products.
25
25
  3. Before installing external software, obtain approval and identify what will be installed. If deeper analysis needs Hopper, describe it as REA's local analysis engine and note that it is a separate Mac app with its own license. Then run `npx -y rea-agents setup --yes`.
26
26
  4. If macOS or an installer requests human input, tell the user exactly what needs attention. After they finish, rerun setup and doctor.
27
- 5. If setup registers a new MCP server, tell the user to restart their coding agent to load all REA tools. Direct CLI commands remain available before restart.
27
+ 5. If setup registers a new MCP server, tell the user to restart their agent to load all REA tools. Direct CLI commands remain available before restart.
28
28
 
29
29
  ## Locate the app
30
30
 
@@ -109,6 +109,13 @@ bundle digests to anchor paginated reports.
109
109
  Use `find_changed_behavior` to combine existing comparison Evidence. Runtime
110
110
  process differences are observed changes; artifact and function differences
111
111
  remain static candidates, not causal proof. Supply complete comparison pages.
112
+ For an automatic two-version artifact investigation, use its
113
+ `investigation_run` mode with explicit write approval and a workspace beneath
114
+ `REA_EVIDENCE_ROOTS_JSON`. It checkpoints inventory, comparison, and report
115
+ Evidence in monotonic CAS-linked revisions. Repeating the same content and
116
+ budgets resumes or reuses the run. This automatic mode does not execute either
117
+ version or perform fuzzy function matching, so its behavior status remains
118
+ unknown without separate controlled runtime Evidence.
112
119
 
113
120
  Use `build_call_path` with explicit `analyze_function` Evidence groups from one
114
121
  artifact and provider. Select endpoints by exact address. Missing dossiers,