zig-mobile-runner 0.1.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 (225) hide show
  1. package/CHANGELOG.md +484 -0
  2. package/CONTRIBUTING.md +42 -0
  3. package/FEATURES.md +112 -0
  4. package/LICENSE +21 -0
  5. package/README.md +255 -0
  6. package/SECURITY.md +34 -0
  7. package/build.zig +38 -0
  8. package/build.zig.zon +7 -0
  9. package/clients/README.md +144 -0
  10. package/clients/go/README.md +24 -0
  11. package/clients/go/examples/fake-session/main.go +93 -0
  12. package/clients/go/go.mod +3 -0
  13. package/clients/go/zmr/client.go +432 -0
  14. package/clients/kotlin/README.md +35 -0
  15. package/clients/kotlin/build.gradle.kts +35 -0
  16. package/clients/kotlin/settings.gradle.kts +15 -0
  17. package/clients/kotlin/src/main/kotlin/dev/zmr/FakeSession.kt +86 -0
  18. package/clients/kotlin/src/main/kotlin/dev/zmr/ZmrClient.kt +67 -0
  19. package/clients/python/README.md +29 -0
  20. package/clients/python/examples/fake_session.py +48 -0
  21. package/clients/python/pyproject.toml +13 -0
  22. package/clients/python/zmr_client.py +202 -0
  23. package/clients/rust/Cargo.lock +107 -0
  24. package/clients/rust/Cargo.toml +10 -0
  25. package/clients/rust/README.md +19 -0
  26. package/clients/rust/examples/fake_session.rs +70 -0
  27. package/clients/rust/src/lib.rs +461 -0
  28. package/clients/swift/Package.swift +16 -0
  29. package/clients/swift/README.md +36 -0
  30. package/clients/swift/Sources/ZMRClient/ZMRClient.swift +114 -0
  31. package/clients/swift/Sources/ZMRFakeSession/main.swift +86 -0
  32. package/clients/typescript/README.md +34 -0
  33. package/clients/typescript/examples/fake-session.mjs +36 -0
  34. package/clients/typescript/index.d.ts +144 -0
  35. package/clients/typescript/index.mjs +192 -0
  36. package/clients/typescript/package.json +8 -0
  37. package/docs/adr/0001-agent-native-runner-boundary.md +31 -0
  38. package/docs/adr/0002-app-local-zmr-contract.md +39 -0
  39. package/docs/adr/0003-ios-simulator-xctest-shim.md +41 -0
  40. package/docs/adr/0004-benchmark-claims-and-baseline-collection.md +37 -0
  41. package/docs/adr/README.md +12 -0
  42. package/docs/ai-agents.md +156 -0
  43. package/docs/app-integration.md +316 -0
  44. package/docs/benchmarking.md +275 -0
  45. package/docs/client-installation.md +141 -0
  46. package/docs/clients.md +98 -0
  47. package/docs/config.md +175 -0
  48. package/docs/demo.md +259 -0
  49. package/docs/dsl.md +57 -0
  50. package/docs/install.md +233 -0
  51. package/docs/market-positioning.md +70 -0
  52. package/docs/npm.md +359 -0
  53. package/docs/protocol-fixtures/README.md +8 -0
  54. package/docs/protocol-fixtures/core-session.requests.jsonl +8 -0
  55. package/docs/protocol-fixtures/core-session.responses.jsonl +8 -0
  56. package/docs/protocol-versioning.md +65 -0
  57. package/docs/protocol.md +560 -0
  58. package/docs/publication.md +77 -0
  59. package/docs/release-audit.md +99 -0
  60. package/docs/release-candidate.md +111 -0
  61. package/docs/release-evidence.md +188 -0
  62. package/docs/release-notes-template.md +58 -0
  63. package/docs/roadmap.md +334 -0
  64. package/docs/scenario-authoring.md +88 -0
  65. package/docs/shipping.md +170 -0
  66. package/docs/trace-privacy.md +88 -0
  67. package/docs/troubleshooting.md +256 -0
  68. package/examples/android-app-auth-probe.json +89 -0
  69. package/examples/android-app-error-state.json +13 -0
  70. package/examples/android-app-login-smoke.json +192 -0
  71. package/examples/android-app-onboarding.json +12 -0
  72. package/examples/android-app-referral-deep-link.json +12 -0
  73. package/examples/android-shim-smoke.json +19 -0
  74. package/examples/demo-failure.json +12 -0
  75. package/examples/demo-fake.json +14 -0
  76. package/examples/ios-dev-client-open-link.json +26 -0
  77. package/examples/ios-dev-client-route-snapshot.json +24 -0
  78. package/examples/ios-shim-smoke.json +23 -0
  79. package/examples/ios-smoke.json +9 -0
  80. package/go.work +3 -0
  81. package/npm/agents.mjs +183 -0
  82. package/npm/app-config.mjs +95 -0
  83. package/npm/build-zmr.mjs +21 -0
  84. package/npm/commands.mjs +104 -0
  85. package/npm/generated-files.mjs +50 -0
  86. package/npm/index.mjs +75 -0
  87. package/npm/init-app.mjs +80 -0
  88. package/npm/package-scripts.mjs +72 -0
  89. package/npm/postinstall.mjs +21 -0
  90. package/npm/scaffold.mjs +179 -0
  91. package/npm/scenarios.mjs +93 -0
  92. package/npm/setup.mjs +69 -0
  93. package/npm/wizard.mjs +117 -0
  94. package/npm/zmr.mjs +23 -0
  95. package/package.json +114 -0
  96. package/prebuilds/darwin-arm64/zmr +0 -0
  97. package/prebuilds/darwin-x64/zmr +0 -0
  98. package/prebuilds/linux-arm64/zmr +0 -0
  99. package/prebuilds/linux-x64/zmr +0 -0
  100. package/schemas/README.md +26 -0
  101. package/schemas/action-result.schema.json +27 -0
  102. package/schemas/capabilities-output.schema.json +98 -0
  103. package/schemas/devices-output.schema.json +25 -0
  104. package/schemas/doctor-output.schema.json +51 -0
  105. package/schemas/explain-output.schema.json +51 -0
  106. package/schemas/import-output.schema.json +23 -0
  107. package/schemas/init-output.schema.json +71 -0
  108. package/schemas/json-rpc.schema.json +55 -0
  109. package/schemas/release-manifest.schema.json +43 -0
  110. package/schemas/release-readiness-output.schema.json +127 -0
  111. package/schemas/run-output.schema.json +43 -0
  112. package/schemas/scenario.schema.json +128 -0
  113. package/schemas/schemas-output.schema.json +26 -0
  114. package/schemas/semantic-snapshot.schema.json +116 -0
  115. package/schemas/snapshot.schema.json +60 -0
  116. package/schemas/trace-event.schema.json +14 -0
  117. package/schemas/trace-manifest.schema.json +59 -0
  118. package/schemas/validate-output.schema.json +42 -0
  119. package/schemas/version-output.schema.json +23 -0
  120. package/schemas/zmr-config.schema.json +75 -0
  121. package/scripts/android-emulator.sh +126 -0
  122. package/scripts/assert-ios-physical-ready.sh +213 -0
  123. package/scripts/benchmark-command.sh +307 -0
  124. package/scripts/benchmark.sh +359 -0
  125. package/scripts/benchmark_gate.py +117 -0
  126. package/scripts/benchmark_result_row.py +88 -0
  127. package/scripts/compare-benchmarks.py +288 -0
  128. package/scripts/create-android-demo-app.sh +342 -0
  129. package/scripts/create-ios-demo-app.sh +261 -0
  130. package/scripts/demo-android-real.sh +232 -0
  131. package/scripts/demo-ios-real.sh +270 -0
  132. package/scripts/demo.sh +464 -0
  133. package/scripts/device-matrix.sh +338 -0
  134. package/scripts/ensure-ios-shim-target.rb +237 -0
  135. package/scripts/install-android-shim.sh +281 -0
  136. package/scripts/install-ios-shim.sh +589 -0
  137. package/scripts/pilot-gate.sh +560 -0
  138. package/scripts/release-readiness.py +838 -0
  139. package/scripts/release-readiness.sh +91 -0
  140. package/scripts/run-android-pilot.sh +561 -0
  141. package/scripts/run-ios-pilot.sh +509 -0
  142. package/shims/android/README.md +21 -0
  143. package/shims/android/ZMRShimInstrumentedTest.java +152 -0
  144. package/shims/android/protocol.md +18 -0
  145. package/shims/ios/README.md +50 -0
  146. package/shims/ios/ZMRShim.swift +110 -0
  147. package/shims/ios/ZMRShimUITestCase.swift +475 -0
  148. package/shims/ios/protocol.md +74 -0
  149. package/skills/zmr-mobile-testing/SKILL.md +127 -0
  150. package/src/android.zig +344 -0
  151. package/src/android_device_info.zig +99 -0
  152. package/src/android_emulator.zig +154 -0
  153. package/src/android_screen_recording.zig +112 -0
  154. package/src/android_shell.zig +112 -0
  155. package/src/bundle.zig +124 -0
  156. package/src/bundle_redaction.zig +272 -0
  157. package/src/bundle_tar.zig +123 -0
  158. package/src/cli_devices.zig +97 -0
  159. package/src/cli_doctor.zig +114 -0
  160. package/src/cli_import.zig +70 -0
  161. package/src/cli_info.zig +39 -0
  162. package/src/cli_init.zig +72 -0
  163. package/src/cli_output.zig +467 -0
  164. package/src/cli_run.zig +259 -0
  165. package/src/cli_serve.zig +287 -0
  166. package/src/cli_trace.zig +111 -0
  167. package/src/cli_validate.zig +41 -0
  168. package/src/command.zig +211 -0
  169. package/src/config.zig +305 -0
  170. package/src/config_diagnostics.zig +212 -0
  171. package/src/config_paths.zig +49 -0
  172. package/src/device_registry.zig +37 -0
  173. package/src/doctor.zig +412 -0
  174. package/src/doctor_hints.zig +52 -0
  175. package/src/errors.zig +55 -0
  176. package/src/fake_device.zig +163 -0
  177. package/src/health.zig +28 -0
  178. package/src/importer.zig +343 -0
  179. package/src/importer_json.zig +100 -0
  180. package/src/importer_model.zig +103 -0
  181. package/src/ios.zig +399 -0
  182. package/src/ios_devices.zig +219 -0
  183. package/src/ios_lifecycle.zig +72 -0
  184. package/src/ios_shim.zig +242 -0
  185. package/src/ios_snapshot.zig +20 -0
  186. package/src/json_fields.zig +80 -0
  187. package/src/json_rpc.zig +150 -0
  188. package/src/json_rpc_methods.zig +318 -0
  189. package/src/json_rpc_observation.zig +31 -0
  190. package/src/json_rpc_params.zig +52 -0
  191. package/src/json_rpc_protocol.zig +110 -0
  192. package/src/json_rpc_trace.zig +73 -0
  193. package/src/main.zig +135 -0
  194. package/src/mcp.zig +234 -0
  195. package/src/mcp_protocol.zig +64 -0
  196. package/src/mcp_trace.zig +83 -0
  197. package/src/report.zig +346 -0
  198. package/src/report_html.zig +63 -0
  199. package/src/report_values.zig +27 -0
  200. package/src/run_options.zig +152 -0
  201. package/src/runner.zig +280 -0
  202. package/src/runner_actions.zig +109 -0
  203. package/src/runner_config.zig +6 -0
  204. package/src/runner_diagnostics.zig +268 -0
  205. package/src/runner_events.zig +170 -0
  206. package/src/runner_native.zig +88 -0
  207. package/src/runner_waits.zig +300 -0
  208. package/src/scaffold.zig +472 -0
  209. package/src/scenario.zig +346 -0
  210. package/src/scenario_fields.zig +50 -0
  211. package/src/schema_registry.zig +53 -0
  212. package/src/selector.zig +84 -0
  213. package/src/semantic.zig +171 -0
  214. package/src/trace.zig +315 -0
  215. package/src/trace_json.zig +340 -0
  216. package/src/trace_summary.zig +218 -0
  217. package/src/trace_summary_diagnostic.zig +202 -0
  218. package/src/types.zig +120 -0
  219. package/src/uiautomator.zig +164 -0
  220. package/src/validation.zig +187 -0
  221. package/src/version.zig +22 -0
  222. package/viewer/app.js +373 -0
  223. package/viewer/index.html +126 -0
  224. package/viewer/parser.js +233 -0
  225. package/viewer/styles.css +585 -0
@@ -0,0 +1,560 @@
1
+ # ZMR JSON-RPC Protocol
2
+
3
+ ZMR exposes newline-delimited JSON-RPC 2.0 over stdio or localhost TCP in v1. Each request is one JSON object followed by `\n`. Each response is one JSON object followed by `\n`.
4
+
5
+ Current runner version: `0.1.0`.
6
+
7
+ Current protocol version: `2026-04-28`.
8
+
9
+ Public schemas:
10
+
11
+ - `schemas/json-rpc.schema.json`
12
+ - `schemas/scenario.schema.json`
13
+ - `schemas/snapshot.schema.json`
14
+ - `schemas/semantic-snapshot.schema.json`
15
+ - `schemas/action-result.schema.json`
16
+ - `schemas/trace-event.schema.json`
17
+ - `schemas/trace-manifest.schema.json`
18
+ - `schemas/doctor-output.schema.json`
19
+ - `schemas/init-output.schema.json`
20
+ - `schemas/import-output.schema.json`
21
+ - `schemas/devices-output.schema.json`
22
+ - `schemas/validate-output.schema.json`
23
+ - `schemas/version-output.schema.json`
24
+ - `schemas/capabilities-output.schema.json`
25
+ - `schemas/explain-output.schema.json`
26
+ - `schemas/run-output.schema.json`
27
+ - `schemas/release-manifest.schema.json`
28
+ - `schemas/release-readiness-output.schema.json`
29
+ - `schemas/schemas-output.schema.json`
30
+
31
+ Compatibility fixtures live under `docs/protocol-fixtures/`. The Zig unit suite
32
+ loads these JSONL requests and asserts exact response shapes for stable core
33
+ methods.
34
+
35
+ `zmr schemas --json` returns the same public schema index in machine-readable
36
+ form for setup scripts, generated clients, and editor integrations. The
37
+ response is covered by `schemas/schemas-output.schema.json`.
38
+
39
+ `zmr-release-readiness --json` checks one or more release/pilot evidence files
40
+ for dev-preview, production, or market-claim gates. The output is covered by
41
+ `schemas/release-readiness-output.schema.json`:
42
+
43
+ ```json
44
+ {"ok":false,"target":"market-claim","status":"blocked","evidence":"traces/release-candidate/run/evidence.jsonl","evidenceFiles":["traces/release-candidate/run/evidence.jsonl"],"passed":["local release gate","public Android emulator demo","public iOS simulator demo"],"satisfied":["local release gate","public Android demo","public iOS simulator demo"],"failed":[],"planned":[],"missing":["physical iOS readiness","Android hardware pilot","iOS simulator hardware pilot","iOS physical hardware pilot","competitive benchmark comparison"],"insufficient":[],"blocked":["physical iOS readiness","Android hardware pilot","iOS simulator hardware pilot","iOS physical hardware pilot","competitive benchmark comparison"],"requirements":[{"name":"local release gate","status":"satisfied","evidenceName":"local release gate"},{"name":"public Android demo","status":"satisfied","evidenceName":"public Android emulator demo"},{"name":"public iOS simulator demo","status":"satisfied","evidenceName":"public iOS simulator demo"},{"name":"physical iOS readiness","status":"missing","reason":"no matching passed evidence row"},{"name":"Android hardware pilot","status":"missing","reason":"no matching passed evidence row"},{"name":"iOS simulator hardware pilot","status":"missing","reason":"no matching passed evidence row"},{"name":"iOS physical hardware pilot","status":"missing","reason":"no matching passed evidence row"},{"name":"competitive benchmark comparison","status":"missing","reason":"no matching passed evidence row"}],"nextSteps":[{"requirement":"Android hardware pilot + iOS simulator hardware pilot","command":"zmr-pilot-gate --android --ios --android-app-root /path/to/mobile-app --android-app-id <android-app-id> --android-device <android-serial> --ios-app-root /path/to/mobile-app --ios-app-path /path/to/mobile-app/build/Debug-iphonesimulator/Sample.app --ios-app-id <ios-app-id> --ios-device booted --ios-shim /path/to/mobile-app/.zmr/ios-shim --runs 20 --min-pass-rate 100 --max-failures 0 --evidence-out /path/to/mobile-app/traces/zmr-pilots/evidence.jsonl","commands":["zmr-pilot-gate --android --ios --android-app-root /path/to/mobile-app --android-app-id <android-app-id> --android-device <android-serial> --ios-app-root /path/to/mobile-app --ios-app-path /path/to/mobile-app/build/Debug-iphonesimulator/Sample.app --ios-app-id <ios-app-id> --ios-device booted --ios-shim /path/to/mobile-app/.zmr/ios-shim --runs 20 --min-pass-rate 100 --max-failures 0 --evidence-out /path/to/mobile-app/traces/zmr-pilots/evidence.jsonl"],"covers":["Android hardware pilot","iOS simulator hardware pilot"]},{"requirement":"physical iOS readiness + iOS physical hardware pilot","command":"zmr-pilot-gate --ios --ios-device-type physical --ios-device <physical-device-id> --ios-app-root /path/to/mobile-app --ios-app-path /path/to/mobile-app/build/Release-iphoneos/Sample.ipa --ios-app-id <ios-app-id> --ios-shim /path/to/mobile-app/.zmr/ios-shim --runs 20 --min-pass-rate 100 --max-failures 0 --evidence-out /path/to/mobile-app/traces/zmr-pilots/evidence.jsonl","commands":["zmr-pilot-gate --ios --ios-device-type physical --ios-device <physical-device-id> --ios-app-root /path/to/mobile-app --ios-app-path /path/to/mobile-app/build/Release-iphoneos/Sample.ipa --ios-app-id <ios-app-id> --ios-shim /path/to/mobile-app/.zmr/ios-shim --runs 20 --min-pass-rate 100 --max-failures 0 --evidence-out /path/to/mobile-app/traces/zmr-pilots/evidence.jsonl"],"covers":["physical iOS readiness","iOS physical hardware pilot"]},{"requirement":"competitive benchmark comparison","command":"zmr-benchmark --zmr .zmr/android-smoke.json --platform <platform> --device <device-id> --app-id <app-id> --app-build <build-id-or-artifact> --runs 20 --trace-root traces/bench-comparison/zmr --results traces/bench-comparison/results.jsonl --replace --min-pass-rate 100 --max-failures 0 && zmr-benchmark-command --tool <baseline-name> --platform <platform> --device <device-id> --app-id <app-id> --scenario .zmr/android-smoke.json --app-build <build-id-or-artifact> --runs 20 --trace-root traces/bench-comparison/baseline --results traces/bench-comparison/results.jsonl -- <baseline command> && zmr-compare-benchmarks --results traces/bench-comparison/results.jsonl --candidate zmr --baseline <baseline-name> --min-candidate-pass-rate 100 --max-candidate-failures 0 --min-mean-speedup 1.25 --min-p95-speedup 1.25 --out traces/bench-comparison/report.md --evidence-out traces/bench-comparison/evidence.jsonl","commands":["zmr-benchmark --zmr .zmr/android-smoke.json --platform <platform> --device <device-id> --app-id <app-id> --app-build <build-id-or-artifact> --runs 20 --trace-root traces/bench-comparison/zmr --results traces/bench-comparison/results.jsonl --replace --min-pass-rate 100 --max-failures 0","zmr-benchmark-command --tool <baseline-name> --platform <platform> --device <device-id> --app-id <app-id> --scenario .zmr/android-smoke.json --app-build <build-id-or-artifact> --runs 20 --trace-root traces/bench-comparison/baseline --results traces/bench-comparison/results.jsonl -- <baseline command>","zmr-compare-benchmarks --results traces/bench-comparison/results.jsonl --candidate zmr --baseline <baseline-name> --min-candidate-pass-rate 100 --max-candidate-failures 0 --min-mean-speedup 1.25 --min-p95-speedup 1.25 --out traces/bench-comparison/report.md --evidence-out traces/bench-comparison/evidence.jsonl"],"covers":["competitive benchmark comparison"]}],"recommendedWording":"Do not publish the market claim yet. Missing evidence: physical iOS readiness, Android hardware pilot, iOS simulator hardware pilot, iOS physical hardware pilot, competitive benchmark comparison.","claimLimitations":["missing evidence"]}
45
+ ```
46
+
47
+ Zero-dependency TypeScript, standard-library Python, standard-library Go, and
48
+ Rust reference clients live under `clients/typescript/`, `clients/python/`,
49
+ `clients/go/`, and `clients/rust/`; all are exercised by the no-device demo.
50
+
51
+ ## Trace Event Contract
52
+
53
+ `zmr run ... --trace-dir <dir>` writes a run manifest to `<dir>/trace.json` and newline-delimited events to `<dir>/events.jsonl`. Each event has `seq`, `timestampMs`, `kind`, and `payload`.
54
+
55
+ `trace.json` is the stable bundle entrypoint for agents and viewers. It includes `schemaVersion`, `runnerVersion`, `protocolVersion`, `scenarioName`, `appId`, `status`, start/end/duration timestamps, failure metadata, `eventsPath`, `artifactsDir`, event/snapshot counts, partial failure count, and `reportPath` when `zmr report` has generated a single-trace HTML report.
56
+
57
+ `zmr export <trace-dir> --out <bundle.zmrtrace>` writes a deterministic tar archive with relative paths only. V1 bundles include `trace.json`, `events.jsonl`, optional `report.html`, and every regular file under `artifacts/`.
58
+
59
+ `zmr export <trace-dir> --out <bundle.zmrtrace> --redact` writes a shareable bundle without mutating the local trace directory. Redacted bundles replace PNG screenshots with placeholder frames, omit screen recording artifacts, scrub text artifacts for emails/tokens/sensitive JSON values, and add `redaction` metadata to the bundled `trace.json`. Add `--omit-screenshots` when the bundle should contain no screenshot artifacts at all.
60
+
61
+ The local static viewer lives at `viewer/index.html` and opens `.zmrtrace` bundles directly in the browser. It uses `trace.json` as the entrypoint, then renders the event timeline, replay controls for snapshot-linked frames, event payloads, side-by-side screenshots and UI trees, selectable node details, snapshot JSON, and artifact links.
62
+
63
+ Scenario traces start with `scenario.start` and terminate with `scenario.end`.
64
+
65
+ Successful terminal event:
66
+
67
+ ```json
68
+ {"kind":"scenario.end","payload":{"value":"flow name","status":"passed"}}
69
+ ```
70
+
71
+ Failed terminal event:
72
+
73
+ ```json
74
+ {"kind":"step.error","payload":{"index":3,"error":"WaitTimeout"}}
75
+ {"kind":"scenario.end","payload":{"value":"flow name","status":"failed","failedStepIndex":3,"error":"WaitTimeout"}}
76
+ ```
77
+
78
+ Clients should read the last `scenario.end` event as the authoritative trace outcome. The CLI still exits non-zero on failed runs.
79
+
80
+ `zmr run <scenario.json> --json` returns a terminal run summary after the
81
+ scenario completes. For traced runs it mirrors the authoritative `trace.json`
82
+ terminal fields, including trace paths, event/snapshot counts, failed step, and
83
+ stable error name. Traced summaries also include `nextCommands` so agents can
84
+ immediately render an HTML report, explain the failure, or export a redacted
85
+ trace bundle. Failed scenarios still exit non-zero after writing the JSON
86
+ summary. The response is covered by `schemas/run-output.schema.json`:
87
+
88
+ ```json
89
+ {"ok":false,"status":"failed","scenario":"login smoke","appId":"com.example.mobiletest","traceDir":"traces/login-smoke","eventsPath":"events.jsonl","artifactsDir":"artifacts","durationMs":100,"eventCount":4,"snapshotCount":1,"failedStepIndex":2,"error":"WaitTimeout","nextCommands":["zmr report traces/login-smoke --out traces/login-smoke/report.html","zmr explain traces/login-smoke --json","zmr export traces/login-smoke --out traces/login-smoke.zmrtrace --redact"]}
90
+ ```
91
+
92
+ When a run captures useful artifacts but a non-terminal enrichment fails, such
93
+ as an iOS screenshot succeeding while XCTest hierarchy extraction fails, the
94
+ terminal status is `partial` and `partialFailureCount` is greater than zero.
95
+ The event stream includes a structured event such as
96
+ `observe.snapshot.semanticExtraction` with `artifactStatus: "captured"` and
97
+ `semanticStatus: "failed"`. `zmr run --json` also includes the last
98
+ `partialFailure` object so agents can report that visual proof exists while
99
+ semantic extraction failed:
100
+
101
+ ```json
102
+ {"ok":false,"status":"partial","scenario":"ios screenshot smoke","appId":"com.example.mobiletest","traceDir":"traces/ios-smoke","eventsPath":"events.jsonl","artifactsDir":"artifacts","durationMs":120,"eventCount":5,"snapshotCount":1,"partialFailureCount":1,"partialFailure":{"kind":"observe.snapshot.semanticExtraction","status":"failed","artifactStatus":"captured","semanticStatus":"failed","error":"CommandFailed","screenshotArtifact":"traces/ios-smoke/artifacts/snapshot-1.png","source":"ios-xctest-shim"}}
103
+ ```
104
+
105
+ Selector miss and wait timeout payloads include diagnostic fields intended for agents and humans: `visibleTexts`, `hiddenCandidates`, `disabledCandidates`, `offscreenCandidates`, and `nearestTextMatches`. Native selector waits, including iOS XCTest-shim waits, capture one final snapshot on timeout and include the same diagnostic fields with `strategy: "nativeSelector"` when snapshot capture is available. Tap actions only target nodes that are visible, enabled, and inside the viewport; disabled or offscreen exact selector matches are reported as diagnostics instead of being tapped.
106
+
107
+ For terminal triage without opening the HTML report or static viewer, run:
108
+
109
+ ```bash
110
+ zmr explain traces/android-app-auth-probe
111
+ ```
112
+
113
+ The text summary includes the terminal status, failed step, stable error, last diagnostic event, snapshot id or screenshot artifact, active app context, visible text, and nearest text matches.
114
+
115
+ `zmr explain <trace-dir> --json` or `zmr explain --json <trace-dir>` returns
116
+ the same failure triage fields in a stable machine-readable shape for agents
117
+ and CI. It also includes `traceDir` and `nextCommands` for rendering an HTML
118
+ report or exporting a redacted bundle. The response is covered by
119
+ `schemas/explain-output.schema.json`:
120
+
121
+ ```json
122
+ {"ok":true,"traceDir":"traces/login-smoke","scenario":"login smoke","status":"failed","appId":"com.example.mobiletest","durationMs":100,"eventCount":4,"snapshotCount":1,"failedStepIndex":2,"error":"WaitTimeout","diagnostic":{"kind":"wait.visible","status":"timeout","snapshotId":"snapshot-7","activePackage":"com.example.mobiletest","activeActivity":".MainActivity","visibleTexts":["Sign in","Try again"],"nearestTextMatches":["Dashboards (score 1)"]},"lastEvent":"scenario.end","nextCommands":["zmr report traces/login-smoke --out traces/login-smoke/report.html","zmr export traces/login-smoke --out traces/login-smoke.zmrtrace --redact"]}
123
+ ```
124
+
125
+ For partial visual captures, `diagnostic.kind` is
126
+ `observe.snapshot.semanticExtraction` and includes `artifactStatus`,
127
+ `semanticStatus`, `error`, `screenshotArtifact`, and `source`.
128
+
129
+ ## Version Output Contract
130
+
131
+ `zmr version --json` returns runner and protocol compatibility metadata for
132
+ installers, setup scripts, and generated clients. The response is covered by
133
+ `schemas/version-output.schema.json`:
134
+
135
+ ```json
136
+ {"name":"zmr","version":"0.1.0","protocolVersion":"2026-04-28","minimumCompatibleProtocolVersion":"2026-04-28","stability":"dev-preview","breakingChangePolicy":"version-and-changelog"}
137
+ ```
138
+
139
+ ## Capabilities Output Contract
140
+
141
+ `runner.capabilities` returns the live protocol, platform support, transport,
142
+ and method inventory for JSON-RPC clients. The result object is covered by
143
+ `schemas/capabilities-output.schema.json`. Clients should use
144
+ `platformSupport` rather than parsing prose docs when deciding whether Android,
145
+ iOS simulator, or physical iOS workflows are available.
146
+
147
+ ```json
148
+ {"name":"zmr","version":"0.1.0","protocolVersion":"2026-04-28","protocol":{"version":"2026-04-28","minimumCompatibleVersion":"2026-04-28","stability":"dev-preview","breakingChangePolicy":"version-and-changelog"},"platforms":["android","ios"],"platformSupport":{"android":{"status":"supported","deviceTypes":["emulator","physical"],"automation":["adb","uiautomator","android-shim"]},"ios":{"status":"supported","deviceTypes":["simulator","physical"],"automation":["simctl","devicectl","xctest-shim"],"physicalDevices":true}},"iosPreview":false,"transports":["stdio","tcp"],"methods":["runner.capabilities","device.list","observe.snapshot","observe.semanticSnapshot"]}
149
+ ```
150
+
151
+ ## Doctor Output Contract
152
+
153
+ `zmr doctor --json` returns setup diagnostics for local tooling. With
154
+ `--config`, it also validates configured Android/iOS smoke scenario files. The
155
+ response is covered by `schemas/doctor-output.schema.json`:
156
+
157
+ ```json
158
+ {"ok":false,"checks":[{"name":"android-shim","status":"missing","errorCode":"setup.android_shim.not_found","detail":"./.zmr/android-shim: FileNotFound","hint":"Run npx zmr-install-android-shim in the app repo or update tools.androidShimPath in .zmr/config.json."}]}
159
+ ```
160
+
161
+ ```json
162
+ {"ok":false,"checks":[{"name":"config","status":"warning","errorCode":"config.empty_string","detail":".zmr/config.json: ConfigFieldMustBeNonEmptyString","fieldPath":"$.scripts.android","hint":"Fix the config file or regenerate it with npx zmr-wizard, then run zmr doctor --strict --json --config .zmr/config.json."}]}
163
+ ```
164
+
165
+ ```json
166
+ {"ok":false,"checks":[{"name":"ios-smoke-scenario","status":"missing","errorCode":"scenario.file_not_found","detail":"./missing-ios-smoke.json: FileNotFound","hint":"Run npx zmr-wizard, create the iOS smoke scenario, or update ios.smokeScenario in .zmr/config.json."}]}
167
+ ```
168
+
169
+ Physical iOS readiness is reported independently from simulator readiness:
170
+
171
+ ```json
172
+ {"ok":false,"checks":[{"name":"ios-physical-devices","status":"warning","errorCode":"setup.ios.no_physical_devices","detail":"0 physical iOS device(s)","hint":"Connect and trust an iPhone, enable Developer Mode, confirm zmr devices --json --platform ios --ios-device-type physical reports ready:true, then pass --ios-device-type physical --device <physical-device-id>."}]}
173
+ ```
174
+
175
+ If physical devices are listed but none are connected/available, the same check
176
+ uses `setup.ios.no_ready_physical_devices` and includes the listed count plus a
177
+ state breakdown:
178
+
179
+ ```json
180
+ {"ok":false,"checks":[{"name":"ios-physical-devices","status":"warning","errorCode":"setup.ios.no_ready_physical_devices","detail":"0 ready physical iOS device(s); 2 listed (disconnected=1, unavailable=1)","hint":"Connect and trust an iPhone, enable Developer Mode, confirm zmr devices --json --platform ios --ios-device-type physical reports ready:true, then pass --ios-device-type physical --device <physical-device-id>."}]}
181
+ ```
182
+
183
+ Healthy checks omit `hint` and `errorCode`. Missing or warning checks include a
184
+ remediation hint that agents and setup scripts can surface directly. Warning
185
+ and missing checks include `errorCode` when the failure has a stable public
186
+ code. Config checks also include `fieldPath` when ZMR can identify the invalid
187
+ app-local config field. `zmr doctor --strict --json` keeps the same output
188
+ shape, but exits non-zero when any check is not `ok`; use it for CI gates that
189
+ should fail before device orchestration starts.
190
+
191
+ ## Init Output Contract
192
+
193
+ `zmr init --json`, `zmr-init --json`, and `zmr-wizard --json` return
194
+ machine-readable bootstrap output for setup scripts and AI agents. The response
195
+ is covered by `schemas/init-output.schema.json`. In app mode it lists every
196
+ generated app-local file, direct paths for the config/scenario/matrix/agent
197
+ files, first-run commands, and the generated script names:
198
+
199
+ ```json
200
+ {"ok":true,"mode":"app","dir":".","appId":"com.example.mobiletest","created":["./.zmr/config.json","./.zmr/android-smoke.json","./.zmr/ios-smoke.json","./.zmr/device-matrix.json","./.zmr/AGENTS.md"],"configPath":"./.zmr/config.json","androidScenarioPath":"./.zmr/android-smoke.json","iosScenarioPath":"./.zmr/ios-smoke.json","deviceMatrixPath":"./.zmr/device-matrix.json","agentInstructionsPath":"./.zmr/AGENTS.md","next":"zmr doctor --strict --json --config ./.zmr/config.json","nextCommands":["zmr doctor --strict --json --config ./.zmr/config.json","zmr schemas --json","zmr validate --json ./.zmr/android-smoke.json","zmr validate --json ./.zmr/ios-smoke.json"],"smokeCommands":["zmr run ./.zmr/android-smoke.json --device emulator-5554 --trace-dir ./traces/zmr-android","zmr run ./.zmr/ios-smoke.json --platform ios --device booted --trace-dir ./traces/zmr-ios"],"scriptCount":16,"scriptNames":["doctor","schemas","validate","android","androidReport","androidReliability","ios","iosReport","iosReliability","matrix","pilotGate","readiness","serve","mcp","explain","exportTrace"]}
201
+ ```
202
+
203
+ Agents should read `agentInstructionsPath` for the app-local operating note and
204
+ run `nextCommands` in order before attempting longer smoke, matrix, or pilot
205
+ runs. After validation passes and devices are available, agents can run
206
+ `smokeCommands` for direct Android and iOS smoke runs.
207
+
208
+ When Expo dev-client scenarios are generated, app mode also includes
209
+ `androidDevClientScenarioPath` and `iosDevClientScenarioPath` and includes those
210
+ files in the generated validation command.
211
+
212
+ Single-scenario mode reports the created scenario and next validation command:
213
+
214
+ ```json
215
+ {"ok":true,"mode":"scenario","appId":"com.example.mobiletest","created":["zmr-scenario.json"],"next":"zmr validate zmr-scenario.json","nextCommands":["zmr validate --json zmr-scenario.json","zmr run zmr-scenario.json --json --trace-dir traces/zmr-run"]}
216
+ ```
217
+
218
+ ## Import Output Contract
219
+
220
+ `zmr import flow-yaml <flow.yaml> --out .zmr/imported.json --json` converts a
221
+ supported subset of mobile-flow YAML into native ZMR scenario JSON. The
222
+ response is covered by `schemas/import-output.schema.json`:
223
+
224
+ ```json
225
+ {"ok":true,"format":"flow-yaml","source":"flows/login.yaml","out":".zmr/login-smoke.json","name":"Imported login smoke","appId":"com.example.mobiletest","stepCount":10,"next":"zmr validate .zmr/login-smoke.json","nextCommands":["zmr validate --json .zmr/login-smoke.json","zmr run .zmr/login-smoke.json --json --trace-dir traces/zmr-run"]}
226
+ ```
227
+
228
+ The importer is intentionally a migration helper, not a runtime dependency.
229
+ After import, agents and CI should treat the generated `.zmr/*.json` file as
230
+ the source of truth and run `nextCommands` in order.
231
+
232
+ ## Validate Output Contract
233
+
234
+ `zmr validate <scenario.json> --json` returns machine-readable scenario
235
+ preflight diagnostics without touching a device. The response is covered by
236
+ `schemas/validate-output.schema.json`:
237
+
238
+ ```json
239
+ {"ok":true,"path":"examples/demo-fake.json","name":"ZMR fake Android auth probe demo","appId":"com.example.mobiletest","stepCount":4,"nextCommands":["zmr run examples/demo-fake.json --json --trace-dir traces/zmr-run"]}
240
+ ```
241
+
242
+ Invalid scenarios exit non-zero after writing the JSON object:
243
+
244
+ ```json
245
+ {"ok":false,"path":"bad.json","errorCode":"scenario.invalid","message":"scenario is invalid","fieldPath":"$.steps"}
246
+ ```
247
+
248
+ ## Devices Output Contract
249
+
250
+ `zmr devices --json` returns a stable platform, count, and device list for setup
251
+ scripts that need to choose a target before starting `zmr run` or `zmr serve`.
252
+ Each device includes `ready`, a portable boolean for agent setup logic. The
253
+ response is covered by `schemas/devices-output.schema.json`:
254
+
255
+ ```json
256
+ {"platform":"android","count":1,"devices":[{"serial":"emulator-5554","state":"device","ready":true}]}
257
+ ```
258
+
259
+ For iOS simulators, use `zmr devices --json --platform ios`; states come from
260
+ `simctl`, for example `Booted`. For physical iOS devices, use:
261
+
262
+ ```bash
263
+ zmr devices --json --platform ios --ios-device-type physical
264
+ ```
265
+
266
+ Physical-device states come from `devicectl` connection data, for example
267
+ `connected`, `disconnected`, or `paired`. For iOS physical devices, `ready` is
268
+ true only for `connected` or `available`.
269
+ The `serial` field is the commandable CoreDevice identifier to pass back to
270
+ `--device`.
271
+
272
+ ## Start
273
+
274
+ ```bash
275
+ zmr serve --transport stdio --device emulator-5554 --app-id com.example.mobiletest --trace-dir traces/agent-session
276
+ zmr serve --transport tcp --port 8765 --device emulator-5554 --app-id com.example.mobiletest --trace-dir traces/agent-session
277
+ zmr serve --transport stdio --platform ios --device <sim-udid> --app-id com.example.mobiletest --trace-dir traces/ios-agent-session
278
+ zmr serve --transport stdio --platform ios --ios-device-type physical --device <physical-device-id> --app-id com.example.mobiletest --trace-dir traces/ios-physical-agent-session
279
+ zmr mcp --config .zmr/config.json --trace-dir traces/mcp-agent-session
280
+ ```
281
+
282
+ `runner.capabilities` reports `platforms: ["android","ios"]`, `platformSupport.ios.status: "supported"`, and legacy `iosPreview: false`. Android supports emulators and connected devices. iOS simulators use `simctl` for discovery, install, launch, stop, clear-state-by-uninstall, deep links, screenshots, logs, and snapshots. Physical iOS devices use `devicectl` for discovery, install, launch, deep-link launch, clear-state-by-uninstall, and best-effort stop. Selector-grade `ui.*` methods on iOS require a configured XCTest/XCUIAutomation shim command; without one they return `IosXCTestShimRequired`. With the shim configured, single-field `ui.tap`, selector-scoped `ui.type`, selector-scoped `ui.eraseText`, `wait.*`, and `assert.*` can execute directly through XCTest. Compound selectors continue to use the portable snapshot-matching fallback. iOS snapshot responses are bounded to common XCTest element families so traces stay usable on large apps. Physical iOS screenshot artifacts use the XCTest shim; physical-device log capture is intentionally limited in this release.
283
+
284
+ ## Core Methods
285
+
286
+ - `runner.capabilities`
287
+ - `device.list`
288
+ - `session.create`
289
+ - `session.close`
290
+ - `app.install` with `{ "path": "/path/app.apk" }` on Android or `{ "path": "/path/App.app" }` on iOS
291
+ - `app.launch`
292
+ - `app.stop`
293
+ - `app.openLink` with `{ "url": "exampleapp://e2e-auth?probe=1" }`
294
+ - `app.clearState`
295
+ - `observe.snapshot`
296
+ - `observe.semanticSnapshot`
297
+ - `ui.tap` with `{ "selector": { "text": "Sign in" } }`
298
+ - `ui.type` with `{ "text": "hello" }`, optionally with `selector`
299
+ - `ui.eraseText` with `{ "maxChars": 80 }`, optionally with `selector`
300
+ - `ui.hideKeyboard`
301
+ - `ui.swipe` with `{ "x1": 500, "y1": 1600, "x2": 500, "y2": 400, "durationMs": 300 }`
302
+ - `ui.pressBack`
303
+ - `ui.scrollUntilVisible` with `{ "selector": { "id": "invite-card" }, "direction": "down", "timeoutMs": 20000 }`
304
+ - `wait.until` with `{ "visible": { "textContains": "Home" }, "timeoutMs": 10000 }`
305
+ - `wait.any` with `{ "selectors": [{ "text": "A" }, { "textContains": "B" }], "timeoutMs": 10000 }`
306
+ - `wait.gone` with `{ "selector": { "textContains": "Loading" }, "timeoutMs": 10000 }`
307
+ - `assert.visible`
308
+ - `assert.notVisible`
309
+ - `assert.healthy`
310
+ - `trace.events`
311
+ - `trace.export`
312
+
313
+ `runner.capabilities` returns both legacy `protocolVersion` and a structured `protocol` object. Clients should treat `protocol.version` as the compatibility key for method and payload shape, and should reject servers older than `protocol.minimumCompatibleVersion` unless they intentionally support that older shape. Before `v1.0.0`, `protocol.stability` is `dev-preview` and breaking changes require both a protocol version bump and changelog entry.
314
+
315
+ ## Request And Response Shape
316
+
317
+ Every request is newline-delimited JSON:
318
+
319
+ ```json
320
+ {"jsonrpc":"2.0","id":1,"method":"app.launch","params":{}}
321
+ ```
322
+
323
+ Successful responses use `result`:
324
+
325
+ ```json
326
+ {"jsonrpc":"2.0","id":1,"result":true}
327
+ ```
328
+
329
+ Errors use JSON-RPC numeric codes plus an optional stable `publicCode` for agent/client handling:
330
+
331
+ ```json
332
+ {"jsonrpc":"2.0","id":1,"error":{"code":-32000,"message":"MissingParam","publicCode":"cli.missing_param"}}
333
+ ```
334
+
335
+ Parse and malformed-request errors may omit `publicCode` because the request was not valid enough to classify at the method layer.
336
+
337
+ ## Method Examples
338
+
339
+ ### `runner.capabilities`
340
+
341
+ Request:
342
+
343
+ ```json
344
+ {"jsonrpc":"2.0","id":1,"method":"runner.capabilities","params":{}}
345
+ ```
346
+
347
+ Response:
348
+
349
+ ```json
350
+ {"jsonrpc":"2.0","id":1,"result":{"name":"zmr","version":"0.1.0","protocolVersion":"2026-04-28","protocol":{"version":"2026-04-28","minimumCompatibleVersion":"2026-04-28","stability":"dev-preview","breakingChangePolicy":"version-and-changelog"},"platforms":["android","ios"],"platformSupport":{"android":{"status":"supported","deviceTypes":["emulator","physical"],"automation":["adb","uiautomator","android-shim"]},"ios":{"status":"supported","deviceTypes":["simulator","physical"],"automation":["simctl","devicectl","xctest-shim"],"physicalDevices":true}},"iosPreview":false,"transports":["stdio","tcp"],"methods":["runner.capabilities","device.list","session.create","session.close","app.install","app.launch","app.stop","app.openLink","app.clearState","observe.snapshot","observe.semanticSnapshot","ui.tap","ui.type","ui.eraseText","ui.hideKeyboard","ui.swipe","ui.pressBack","ui.scrollUntilVisible","wait.until","wait.any","wait.gone","assert.visible","assert.notVisible","assert.healthy","trace.events","trace.export"]}}
351
+ ```
352
+
353
+ ### `trace.events`
354
+
355
+ Returns live trace events from a `zmr serve --trace-dir <dir>` session after a
356
+ sequence cursor. This is the event-streaming surface for long-running agents:
357
+ poll with the returned `nextSeq` value to receive only new events. Without a
358
+ live trace directory it returns an empty stream with `traceDir: null`.
359
+
360
+ Request:
361
+
362
+ ```json
363
+ {"jsonrpc":"2.0","id":24,"method":"trace.events","params":{"afterSeq":0,"limit":100}}
364
+ ```
365
+
366
+ Response:
367
+
368
+ ```json
369
+ {"jsonrpc":"2.0","id":24,"result":{"traceDir":"traces/agent-session","afterSeq":0,"nextSeq":2,"latestSeq":2,"events":[{"seq":1,"timestampMs":1777794787560,"kind":"rpc.request","payload":{"method":"session.create","id":1}},{"seq":2,"timestampMs":1777794787561,"kind":"rpc.response","payload":{"method":"session.create","id":1}}]}}
370
+ ```
371
+
372
+ `limit` defaults to `100` and is capped at `1000`. `latestSeq` is the current
373
+ server-side event counter; `nextSeq` is the last returned event and can be
374
+ passed back as `afterSeq`.
375
+
376
+ ### `device.list`
377
+
378
+ Response:
379
+
380
+ ```json
381
+ {"jsonrpc":"2.0","id":2,"result":[{"serial":"emulator-5554","state":"device","ready":true}]}
382
+ ```
383
+
384
+ For `--platform ios`, states come from `simctl`, for example `Booted`.
385
+
386
+ ### `observe.snapshot`
387
+
388
+ Response shape:
389
+
390
+ ```json
391
+ {
392
+ "jsonrpc": "2.0",
393
+ "id": 3,
394
+ "result": {
395
+ "id": "snapshot-1",
396
+ "timestampMs": 1777367889379,
397
+ "viewport": { "width": 720, "height": 1280 },
398
+ "displayDensityDpi": 420,
399
+ "activePackage": "com.example.mobiletest",
400
+ "activeActivity": ".MainActivity",
401
+ "screenshotArtifact": null,
402
+ "treeArtifact": null,
403
+ "focusedNodeId": null,
404
+ "logDelta": null,
405
+ "nodes": []
406
+ }
407
+ }
408
+ ```
409
+
410
+ ### `observe.semanticSnapshot`
411
+
412
+ Returns an agent-optimized view of the same observation. It normalizes
413
+ platform-specific UI classes into roles, emits the best selector for each node,
414
+ adds center points, and marks the recommended action when a node is actionable.
415
+ The result is covered by `schemas/semantic-snapshot.schema.json`.
416
+
417
+ ```json
418
+ {"jsonrpc":"2.0","id":3,"method":"observe.semanticSnapshot","params":{}}
419
+ ```
420
+
421
+ ```json
422
+ {"jsonrpc":"2.0","id":3,"result":{"id":"snapshot-1","timestampMs":1777367889379,"viewport":{"width":720,"height":1280},"activePackage":"com.example.mobiletest","activeActivity":".MainActivity","focusedNodeId":null,"nodes":[{"id":"submit","role":"button","name":"Sign in","selector":{"resourceId":"submit"},"source":{"className":"android.widget.Button","resourceId":"submit","text":"Sign in","contentDesc":null},"bounds":{"x":80,"y":470,"width":560,"height":70,"centerX":360,"centerY":505},"enabled":true,"visible":true,"selected":false,"interactive":true,"recommendedAction":"tap"}],"summary":{"nodeCount":1,"interactiveCount":1,"visibleText":["Sign in"]}}}
423
+ ```
424
+
425
+ ## MCP Tool Surface
426
+
427
+ `zmr mcp` speaks the Model Context Protocol over stdio. It uses the same app
428
+ config, device adapters, selectors, waits, and trace writer as `zmr serve`, but
429
+ exposes tool calls for agent runtimes that prefer MCP over raw JSON-RPC.
430
+
431
+ ```bash
432
+ zmr mcp --config .zmr/config.json --trace-dir traces/mcp-agent
433
+ ```
434
+
435
+ Core tools are `snapshot`, `semantic_snapshot`, `tap`, `type`, `press_back`,
436
+ `open_link`, `wait_visible`, `trace_events`, and `trace_export`. The MCP
437
+ protocol handshake is intentionally standard, while the tool names and payloads
438
+ are versioned with the ZMR runner and public schemas.
439
+
440
+ ### `ui.tap`
441
+
442
+ Request:
443
+
444
+ ```json
445
+ {"jsonrpc":"2.0","id":4,"method":"ui.tap","params":{"selector":{"text":"Sign in"}}}
446
+ ```
447
+
448
+ Response:
449
+
450
+ ```json
451
+ {"jsonrpc":"2.0","id":4,"result":true}
452
+ ```
453
+
454
+ ### `ui.type`
455
+
456
+ Request:
457
+
458
+ ```json
459
+ {"jsonrpc":"2.0","id":5,"method":"ui.type","params":{"selector":{"id":"email-login-email-input"},"text":"person@example.com"}}
460
+ ```
461
+
462
+ ### Waits And Assertions
463
+
464
+ ```json
465
+ {"jsonrpc":"2.0","id":6,"method":"wait.until","params":{"visible":{"textContains":"Home"},"timeoutMs":10000}}
466
+ {"jsonrpc":"2.0","id":7,"method":"assert.notVisible","params":{"selector":{"textContains":"Loading"},"timeoutMs":5000}}
467
+ ```
468
+
469
+ ### `trace.export`
470
+
471
+ When `zmr serve` is started with `--trace-dir`, live JSON-RPC sessions use the
472
+ same trace manifest, event stream, snapshot artifacts, and `.zmrtrace` bundle
473
+ format as scenario runs. Each request records `rpc.request` and `rpc.response`
474
+ or `rpc.error`; snapshot, wait, and UI methods also record their domain events.
475
+
476
+ Request:
477
+
478
+ ```json
479
+ {"jsonrpc":"2.0","id":8,"method":"trace.export","params":{"out":"traces/agent-session-redacted.zmrtrace","redact":true,"omitScreenshots":true}}
480
+ ```
481
+
482
+ Response:
483
+
484
+ ```json
485
+ {"jsonrpc":"2.0","id":8,"result":{"traceDir":"traces/agent-session","out":"traces/agent-session-redacted.zmrtrace","redacted":true,"omitScreenshots":true}}
486
+ ```
487
+
488
+ `omitScreenshots` implies redacted export semantics, even when `redact` is
489
+ omitted or false.
490
+
491
+ If the server was not started with `--trace-dir`, `trace.export` returns a
492
+ result with `traceDir: null` and a message explaining how to enable live traces.
493
+
494
+ ## Public Error Codes
495
+
496
+ Current stable public codes:
497
+
498
+ - `cli.missing_scenario`
499
+ - `cli.missing_device`
500
+ - `cli.missing_trace_dir`
501
+ - `cli.missing_app_id`
502
+ - `cli.missing_adb_path`
503
+ - `cli.missing_xcrun_path`
504
+ - `cli.missing_zig_path`
505
+ - `cli.missing_platform`
506
+ - `cli.unknown_flag`
507
+ - `cli.missing_param`
508
+ - `cli.unsupported_platform`
509
+ - `cli.unsupported_transport`
510
+ - `scenario.file_not_found`
511
+ - `scenario.invalid`
512
+ - `selector.invalid`
513
+ - `runner.wait_timeout`
514
+ - `runner.assertion_failed`
515
+ - `runner.selector_not_found`
516
+ - `device.command_failed`
517
+ - `ios.xctest_shim_required`
518
+ - `internal.error`
519
+
520
+ ## Selectors
521
+
522
+ Selectors can combine fields. All provided fields must match the same visible node.
523
+
524
+ ```json
525
+ {
526
+ "id": "email-login-submit-button",
527
+ "resourceId": "email-login-submit-button",
528
+ "text": "Sign in",
529
+ "textContains": "Sign",
530
+ "contentDesc": "Account",
531
+ "contentDescContains": "Acc",
532
+ "className": "android.widget.TextView"
533
+ }
534
+ ```
535
+
536
+ ## Example
537
+
538
+ ```json
539
+ {"jsonrpc":"2.0","id":1,"method":"runner.capabilities","params":{}}
540
+ {"jsonrpc":"2.0","id":2,"method":"app.openLink","params":{"url":"exampleapp://e2e-auth?probe=1"}}
541
+ {"jsonrpc":"2.0","id":3,"method":"wait.until","params":{"visible":{"text":"E2E auth probe"},"timeoutMs":30000}}
542
+ {"jsonrpc":"2.0","id":4,"method":"observe.snapshot","params":{}}
543
+ ```
544
+
545
+ ## Scenario-Only Flow Primitives
546
+
547
+ Scenario JSON supports additional orchestration primitives for agent-grade mobile flows:
548
+
549
+ - `waitAny`
550
+ - `waitNotVisible`
551
+ - `whenVisible`
552
+ - `repeat`
553
+ - `scrollUntilVisible`
554
+ - `assertNoneVisible`
555
+ - `assertHealthy`
556
+ - `eraseText`
557
+ - `hideKeyboard`
558
+ - `"optional": true` on any step
559
+
560
+ These are intentionally explicit JSON structures instead of YAML conditionals, so agents can generate, validate, and mutate flows without parsing a second language.
@@ -0,0 +1,77 @@
1
+ # Public GitHub Publication
2
+
3
+ This is the maintainer checklist for uploading ZMR to a public GitHub repository
4
+ and making it usable by other mobile app codebases.
5
+
6
+ ## Public Repo State
7
+
8
+ Before pushing a branch or tag:
9
+
10
+ ```bash
11
+ git status --short
12
+ bash tests/public-safety-test.sh
13
+ ./scripts/release-gate.sh
14
+ npm pack --dry-run
15
+ ```
16
+
17
+ Expected evidence:
18
+
19
+ - `git status --short` shows only intentional source, docs, workflow, schema,
20
+ shim, client, and test files.
21
+ - `bash tests/public-safety-test.sh` passes.
22
+ - `./scripts/release-gate.sh` passes locally.
23
+ - `npm pack --dry-run` lists only public package contents.
24
+
25
+ Do not commit generated traces, release archives, npm tarballs, local build
26
+ outputs, app credentials, private app identifiers, simulator logs, or raw visual
27
+ artifacts. `.gitignore` excludes `traces/`, `dist/`, `zig-out/`, Zig caches,
28
+ `prebuilds/`, `node_modules/`, and generated tarballs by default.
29
+
30
+ ## Repository Setup
31
+
32
+ 1. Create the public GitHub repository.
33
+ 2. Push the source branch.
34
+ 3. Confirm CI runs `./scripts/release-gate.sh`.
35
+ 4. Configure branch protection for `main`.
36
+ 5. Add `NPM_TOKEN` only when npm publish should be automated.
37
+ 6. For the current dev-preview package, create and push tag `v0.1.0`.
38
+
39
+ The release workflow builds release archives, generates checksums, verifies
40
+ packaged binaries, builds the npm tarball with prebuilt binaries, uploads GitHub
41
+ release assets, publishes artifact attestations, and publishes to npm when
42
+ `NPM_TOKEN` is configured.
43
+
44
+ ## App Integration Smoke
45
+
46
+ In a separate mobile app checkout:
47
+
48
+ ```bash
49
+ npm install --save-dev zig-mobile-runner
50
+ npx zmr-wizard --app-id com.example.mobiletest --package-json
51
+ npx zmr doctor --strict --json --config .zmr/config.json
52
+ npm run zmr:android
53
+ npm run zmr:ios
54
+ ```
55
+
56
+ Use `zmr-pilot-gate` for maintainer release evidence on machines with real app
57
+ builds and devices:
58
+
59
+ ```bash
60
+ npx zmr-pilot-gate \
61
+ --android \
62
+ --ios \
63
+ --android-app-root . \
64
+ --android-app-id com.example.mobiletest \
65
+ --android-device emulator-5554 \
66
+ --ios-app-root . --ios-app-path ./build/Debug-iphonesimulator/Sample.app \
67
+ --ios-app-id com.example.mobiletest \
68
+ --ios-device booted \
69
+ --ios-shim ./.zmr/ios-shim \
70
+ --runs 20 \
71
+ --min-pass-rate 100 \
72
+ --max-failures 0 \
73
+ --evidence-out traces/zmr-pilots/evidence.jsonl
74
+ ```
75
+
76
+ Publish reliability claims only from sanitized, app-agnostic summaries. Keep
77
+ private scenarios and raw traces in the private app repository.