zeno-mobile-runner 0.1.2 → 0.1.8

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 (89) hide show
  1. package/CHANGELOG.md +162 -3
  2. package/FEATURES.md +50 -7
  3. package/README.md +133 -7
  4. package/build.zig.zon +3 -3
  5. package/clients/README.md +60 -3
  6. package/clients/go/README.md +12 -0
  7. package/clients/go/zmr/client.go +142 -0
  8. package/clients/kotlin/README.md +18 -1
  9. package/clients/kotlin/build.gradle.kts +1 -1
  10. package/clients/kotlin/src/main/kotlin/dev/zmr/ZmrClient.kt +76 -1
  11. package/clients/python/README.md +19 -0
  12. package/clients/python/pyproject.toml +1 -1
  13. package/clients/python/zmr_client.py +33 -0
  14. package/clients/rust/Cargo.lock +1 -1
  15. package/clients/rust/Cargo.toml +1 -1
  16. package/clients/rust/README.md +25 -1
  17. package/clients/rust/src/lib.rs +201 -0
  18. package/clients/swift/README.md +18 -0
  19. package/clients/swift/Sources/ZMRClient/ZMRClient.swift +82 -0
  20. package/clients/typescript/README.md +16 -0
  21. package/clients/typescript/index.d.ts +12 -0
  22. package/clients/typescript/index.mjs +16 -0
  23. package/clients/typescript/package.json +1 -1
  24. package/docs/agent-discovery.md +202 -0
  25. package/docs/ai-agents.md +87 -6
  26. package/docs/benchmarking.md +10 -3
  27. package/docs/clients.md +10 -6
  28. package/docs/demo.md +4 -0
  29. package/docs/expo-smoke.md +79 -0
  30. package/docs/install.md +3 -2
  31. package/docs/npm.md +58 -4
  32. package/docs/production-readiness.md +123 -0
  33. package/docs/protocol-fixtures/core-session.responses.jsonl +1 -1
  34. package/docs/protocol.md +215 -16
  35. package/docs/scenario-authoring.md +3 -0
  36. package/docs/troubleshooting.md +1 -1
  37. package/npm/agents.mjs +16 -0
  38. package/npm/build-zmr.mjs +1 -1
  39. package/npm/commands.mjs +9 -5
  40. package/npm/postinstall.mjs +28 -2
  41. package/npm/verify-publish.mjs +36 -0
  42. package/package.json +2 -1
  43. package/prebuilds/darwin-arm64/zmr +0 -0
  44. package/prebuilds/darwin-x64/zmr +0 -0
  45. package/prebuilds/linux-arm64/zmr +0 -0
  46. package/prebuilds/linux-x64/zmr +0 -0
  47. package/schemas/README.md +4 -0
  48. package/schemas/discover-output.schema.json +83 -0
  49. package/schemas/draft-output.schema.json +58 -0
  50. package/schemas/explore-output.schema.json +94 -0
  51. package/schemas/inspect-output.schema.json +88 -0
  52. package/schemas/run-output.schema.json +2 -0
  53. package/scripts/install-ios-shim.sh +79 -14
  54. package/scripts/release-readiness.py +43 -0
  55. package/scripts/run-android-pilot.sh +35 -9
  56. package/scripts/run-ios-pilot.sh +11 -4
  57. package/shims/ios/ZMRShim.swift +3 -0
  58. package/shims/ios/ZMRShimUITestCase.swift +41 -11
  59. package/skills/zmr-mobile-testing/SKILL.md +28 -3
  60. package/src/cli_discover.zig +239 -0
  61. package/src/cli_draft.zig +924 -0
  62. package/src/cli_explore.zig +136 -0
  63. package/src/cli_inspect.zig +310 -0
  64. package/src/cli_output.zig +26 -2
  65. package/src/cli_run.zig +28 -0
  66. package/src/cli_trace.zig +8 -0
  67. package/src/errors.zig +9 -0
  68. package/src/ios.zig +11 -4
  69. package/src/ios_lifecycle.zig +36 -0
  70. package/src/ios_shim.zig +42 -0
  71. package/src/json_rpc_methods.zig +85 -11
  72. package/src/json_rpc_params.zig +8 -0
  73. package/src/json_rpc_protocol.zig +1 -1
  74. package/src/json_rpc_trace.zig +112 -0
  75. package/src/main.zig +24 -2
  76. package/src/mcp.zig +209 -6
  77. package/src/mcp_protocol.zig +29 -1
  78. package/src/mcp_trace.zig +126 -4
  79. package/src/report.zig +186 -0
  80. package/src/runner.zig +26 -4
  81. package/src/runner_actions.zig +10 -0
  82. package/src/runner_diagnostics.zig +31 -1
  83. package/src/runner_events.zig +70 -7
  84. package/src/runner_native.zig +17 -1
  85. package/src/runner_waits.zig +82 -19
  86. package/src/scaffold.zig +28 -12
  87. package/src/scenario.zig +32 -4
  88. package/src/schema_registry.zig +4 -0
  89. package/src/version.zig +1 -1
@@ -6,6 +6,28 @@ public enum ZMRError: Error {
6
6
  case rpcError([String: Any])
7
7
  }
8
8
 
9
+ public struct TraceDiscoverOptions {
10
+ public var includeActions: Bool
11
+ public var validate: Bool
12
+ public var force: Bool
13
+ public var name: String?
14
+ public var appId: String?
15
+
16
+ public init(
17
+ includeActions: Bool = false,
18
+ validate: Bool = false,
19
+ force: Bool = false,
20
+ name: String? = nil,
21
+ appId: String? = nil
22
+ ) {
23
+ self.includeActions = includeActions
24
+ self.validate = validate
25
+ self.force = force
26
+ self.name = name
27
+ self.appId = appId
28
+ }
29
+ }
30
+
9
31
  public final class ZMRClient {
10
32
  private let process: Process
11
33
  private let input: FileHandle
@@ -98,6 +120,66 @@ public final class ZMRClient {
98
120
  return result
99
121
  }
100
122
 
123
+ public func validateScenario(path: String) throws -> [String: Any] {
124
+ guard let result = try call("scenario.validate", params: ["path": path]) as? [String: Any] else {
125
+ throw ZMRError.invalidResponse
126
+ }
127
+ return result
128
+ }
129
+
130
+ public func discoverTrace(out: String, options: TraceDiscoverOptions = TraceDiscoverOptions()) throws -> [String: Any] {
131
+ var params: [String: Any] = ["out": out]
132
+ if options.includeActions {
133
+ params["includeActions"] = true
134
+ }
135
+ if options.validate {
136
+ params["validate"] = true
137
+ }
138
+ if options.force {
139
+ params["force"] = true
140
+ }
141
+ if let name = options.name {
142
+ params["name"] = name
143
+ }
144
+ if let appId = options.appId {
145
+ params["appId"] = appId
146
+ }
147
+ guard let result = try call("trace.discover", params: params) as? [String: Any] else {
148
+ throw ZMRError.invalidResponse
149
+ }
150
+ return result
151
+ }
152
+
153
+ public func exploreTrace(out: String, goal: String, options: TraceDiscoverOptions = TraceDiscoverOptions()) throws -> [String: Any] {
154
+ var params: [String: Any] = ["out": out, "goal": goal]
155
+ if options.includeActions {
156
+ params["includeActions"] = true
157
+ }
158
+ if options.validate {
159
+ params["validate"] = true
160
+ }
161
+ if options.force {
162
+ params["force"] = true
163
+ }
164
+ if let name = options.name {
165
+ params["name"] = name
166
+ }
167
+ if let appId = options.appId {
168
+ params["appId"] = appId
169
+ }
170
+ guard let result = try call("trace.explore", params: params) as? [String: Any] else {
171
+ throw ZMRError.invalidResponse
172
+ }
173
+ return result
174
+ }
175
+
176
+ public func explainTrace() throws -> [String: Any] {
177
+ guard let result = try call("trace.explain", params: [:]) as? [String: Any] else {
178
+ throw ZMRError.invalidResponse
179
+ }
180
+ return result
181
+ }
182
+
101
183
  private func readLineData() throws -> Data {
102
184
  var data = Data()
103
185
  while true {
@@ -22,8 +22,24 @@ try {
22
22
  await zmr.waitUntil({ text: "E2E auth probe" }, { timeoutMs: 30000 });
23
23
  const snapshot = await zmr.snapshot();
24
24
  const events = await zmr.traceEvents(0, { limit: 100 });
25
+ const explanation = await zmr.explainTrace();
26
+ const discovered = await zmr.discoverTrace(".zmr/discovered/agent-session.json", {
27
+ includeActions: true,
28
+ validate: true,
29
+ force: true,
30
+ });
31
+ const explored = await zmr.exploreTrace(".zmr/discovered/agent-goal.json", "find a stable login smoke", {
32
+ includeActions: true,
33
+ validate: true,
34
+ force: true,
35
+ });
36
+ const validation = await zmr.validateScenario(discovered.out);
25
37
  console.log(snapshot.nodes);
26
38
  console.log(events.events.length);
39
+ console.log(explanation.status);
40
+ console.log(discovered.out);
41
+ console.log(explored.reviewRequired);
42
+ console.log(validation.ok);
27
43
  await zmr.exportTrace("traces/agent-session-redacted.zmrtrace", { redact: true, omitScreenshots: true });
28
44
  } finally {
29
45
  await zmr.close();
@@ -99,6 +99,14 @@ export interface Capabilities {
99
99
  methods: string[];
100
100
  }
101
101
 
102
+ export interface TraceDiscoverOptions {
103
+ includeActions?: boolean;
104
+ validate?: boolean;
105
+ force?: boolean;
106
+ name?: string;
107
+ appId?: string;
108
+ }
109
+
102
110
  export interface DeviceInfo {
103
111
  serial: string;
104
112
  state: string;
@@ -130,8 +138,12 @@ export interface ZmrClient {
130
138
  assertVisible(selector: Selector, options?: { timeoutMs?: number }): Promise<boolean>;
131
139
  assertNotVisible(selector: Selector, options?: { timeoutMs?: number }): Promise<boolean>;
132
140
  assertHealthy(options?: { timeoutMs?: number }): Promise<boolean>;
141
+ validateScenario(path: string): Promise<Record<string, unknown>>;
133
142
  exportTrace(out: string, options?: { redact?: boolean; omitScreenshots?: boolean }): Promise<Record<string, unknown>>;
134
143
  traceEvents(afterSeq?: number, options?: { limit?: number }): Promise<Record<string, unknown>>;
144
+ explainTrace(): Promise<Record<string, unknown>>;
145
+ discoverTrace(out: string, options?: TraceDiscoverOptions): Promise<Record<string, unknown>>;
146
+ exploreTrace(out: string, goal: string, options?: TraceDiscoverOptions): Promise<Record<string, unknown>>;
135
147
  close(): Promise<void>;
136
148
  }
137
149
 
@@ -146,6 +146,10 @@ export class ZmrClient {
146
146
  return this.request("assert.healthy", options);
147
147
  }
148
148
 
149
+ validateScenario(path) {
150
+ return this.request("scenario.validate", { path });
151
+ }
152
+
149
153
  exportTrace(out, options = {}) {
150
154
  return this.request("trace.export", { out, ...options });
151
155
  }
@@ -154,6 +158,18 @@ export class ZmrClient {
154
158
  return this.request("trace.events", { afterSeq, ...options });
155
159
  }
156
160
 
161
+ explainTrace() {
162
+ return this.request("trace.explain", {});
163
+ }
164
+
165
+ discoverTrace(out, options = {}) {
166
+ return this.request("trace.discover", { out, ...options });
167
+ }
168
+
169
+ exploreTrace(out, goal, options = {}) {
170
+ return this.request("trace.explore", { out, goal, ...options });
171
+ }
172
+
157
173
  async close() {
158
174
  if (this.#closed) return;
159
175
  this.#closed = true;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zmr/client",
3
- "version": "0.1.2",
3
+ "version": "0.1.8",
4
4
  "type": "module",
5
5
  "main": "index.mjs",
6
6
  "types": "index.d.ts",
@@ -0,0 +1,202 @@
1
+ # Agent Discovery
2
+
3
+ ZMR supports agent-led discovery today through its JSON-RPC and MCP interfaces,
4
+ trace events, semantic snapshot artifacts, guarded trace exploration, in-band
5
+ trace discovery, and offline scenario drafting. An external agent can observe
6
+ the app, choose typed actions, inspect trace events, ask ZMR to write a small
7
+ repeatable scenario from the trace, and then edit it as it learns a flow.
8
+
9
+ `zmr explore` is the built-in review-first exploration command. It is
10
+ trace-backed, not an unbounded crawler: it does not launch devices, invent
11
+ missing actions, discover credentials, or commit files. Keep autonomous
12
+ planning in the agent, and keep ZMR as the deterministic mobile control plane.
13
+
14
+ ## Recommended Loop
15
+
16
+ 1. Validate local setup:
17
+
18
+ ```bash
19
+ zmr inspect --json --dir .
20
+ zmr doctor --json --config .zmr/config.json
21
+ zmr validate --json .zmr/ios-smoke.json
22
+ ```
23
+
24
+ 2. Start a live session:
25
+
26
+ ```bash
27
+ zmr serve --transport stdio --config .zmr/config.json --trace-dir traces/zmr-agent
28
+ ```
29
+
30
+ Agents that speak MCP can use:
31
+
32
+ ```bash
33
+ zmr mcp --config .zmr/config.json --trace-dir traces/zmr-agent
34
+ ```
35
+
36
+ 3. Call `runner.capabilities`, then `session.create`.
37
+ 4. Call `observe.semanticSnapshot` before choosing an action.
38
+ 5. Choose one typed action, such as `ui.tap`, `ui.type`, `app.openLink`, or
39
+ `wait.until`.
40
+ 6. Observe again and inspect `trace.events`.
41
+ 7. If you used `zmr run --json --trace-dir`, read `nextCommands`; traced run
42
+ summaries include HTML/JUnit report output and the matching
43
+ `zmr discover --from-trace` command.
44
+ 8. If you want the CLI run itself to write the candidate, use:
45
+
46
+ ```bash
47
+ zmr run .zmr/login-smoke.json \
48
+ --trace-dir traces/zmr-agent \
49
+ --discover-out .zmr/discovered/replay-smoke.json \
50
+ --json
51
+ ```
52
+
53
+ The run response embeds `discovery`, the same JSON payload returned by
54
+ `zmr discover --json`, including `replay` coverage metadata for converted
55
+ and skipped trace actions.
56
+ 9. Generate a reviewable scenario candidate from the trace. For CLI-driven
57
+ agent loops, prefer `zmr explore` so the goal and guardrails travel with the
58
+ machine-readable result:
59
+
60
+ ```bash
61
+ zmr explore --from-trace traces/zmr-agent \
62
+ --out .zmr/discovered/login-smoke.json \
63
+ --goal "find a stable login smoke" \
64
+ --include-actions \
65
+ --validate \
66
+ --json
67
+ ```
68
+
69
+ The output is covered by `schemas/explore-output.schema.json` and includes
70
+ `autonomous:false`, `reviewRequired:true`, `guardrails`, replay coverage,
71
+ validation, and deterministic next commands.
72
+
73
+ 10. Use live trace exploration when the agent should keep the goal attached to
74
+ the generated draft. JSON-RPC agents can call `trace.explore`:
75
+
76
+ ```json
77
+ {"jsonrpc":"2.0","id":7,"method":"trace.explore","params":{"out":".zmr/discovered/login-smoke.json","goal":"find a stable login smoke","includeActions":true,"validate":true,"force":true}}
78
+ ```
79
+
80
+ MCP agents can call `trace_explore` with `out`, `goal`,
81
+ `includeActions`, `validate`, and `force`. The response includes
82
+ `autonomous:false`, `reviewRequired:true`, and `guardrails`.
83
+
84
+ 11. Use the lower-level trace discovery primitive when the agent already owns
85
+ goal tracking. JSON-RPC agents can
86
+ call `trace.discover`:
87
+
88
+ ```json
89
+ {"jsonrpc":"2.0","id":7,"method":"trace.discover","params":{"out":".zmr/discovered/replay-smoke.json","includeActions":true,"validate":true,"force":true}}
90
+ ```
91
+
92
+ MCP agents can call `trace_discover` with the same `out`,
93
+ `includeActions`, `validate`, and `force` arguments. The offline CLI
94
+ equivalent is:
95
+
96
+ ```bash
97
+ zmr discover --from-trace traces/zmr-agent \
98
+ --out .zmr/discovered/replay-smoke.json \
99
+ --include-actions \
100
+ --validate \
101
+ --json
102
+ ```
103
+
104
+ `zmr discover` writes a scenario from trace evidence and, with
105
+ `--validate`, immediately proves that the generated file is syntactically
106
+ runnable by ZMR. It is still review-first: it does not crawl, invent missing
107
+ actions, discover credentials, or commit the scenario.
108
+ Read the `replay` object before trusting coverage: `eventCount` is the
109
+ trace action event count considered for replay, `stepCount` is the number of
110
+ generated replay steps, and `skippedEventCount` is the number of events left
111
+ out.
112
+
113
+ 11. After editing a generated scenario, validate it in-band with JSON-RPC:
114
+
115
+ ```json
116
+ {"jsonrpc":"2.0","id":8,"method":"scenario.validate","params":{"path":".zmr/discovered/replay-smoke.json"}}
117
+ ```
118
+
119
+ MCP agents can call `scenario_validate` with the same `path` argument. The
120
+ result matches `zmr validate --json`, including field paths and source
121
+ locations for invalid files.
122
+
123
+ 12. Use the lower-level draft primitive when you want separate surface and
124
+ replay files. For a conservative surface-smoke scenario:
125
+
126
+ ```bash
127
+ zmr draft --from-trace traces/zmr-agent \
128
+ --out .zmr/discovered/surface-smoke.json \
129
+ --json
130
+ ```
131
+
132
+ The draft contains `launch`, `snapshot`, and `assertVisible` steps from
133
+ stable visible selectors. It does not tap, type, crawl, or commit anything.
134
+ If the trace contains successful typed actions and you want a replayable
135
+ starting point, include those supported events explicitly:
136
+
137
+ ```bash
138
+ zmr draft --from-trace traces/zmr-agent \
139
+ --out .zmr/discovered/replay-smoke.json \
140
+ --include-actions \
141
+ --json
142
+ ```
143
+
144
+ Replay drafts include only supported events with stable replay data, such as
145
+ launch, deep links, selector taps, selector text entry, back, keyboard hiding,
146
+ coordinate-complete swipes, selector/timeout-preserving waits, and
147
+ direction/timeout-preserving selector scrolls, selector/timeout-preserving
148
+ `assertVisible` and `assertNotVisible`, `assertNoneVisible` selector arrays,
149
+ and timed `assertHealthy` checks. Native selector wait traces also retain
150
+ timeout context for successful waits and timeout diagnostics.
151
+ Unsupported events stay out of the scenario and are reported as warnings.
152
+
153
+ 13. Edit the draft, discovery, or exploration output into a candidate flow, for example
154
+ `.zmr/discovered/login-smoke.json`, by copying only steps that were observed
155
+ and understood.
156
+ 14. Validate the candidate scenario:
157
+
158
+ ```bash
159
+ zmr validate --json .zmr/discovered/login-smoke.json
160
+ ```
161
+
162
+ 15. Re-run it deterministically:
163
+
164
+ ```bash
165
+ zmr run .zmr/discovered/login-smoke.json \
166
+ --platform ios \
167
+ --device booted \
168
+ --trace-dir traces/zmr-login-smoke \
169
+ --json
170
+ ```
171
+
172
+ 16. Export a redacted bundle before sharing artifacts:
173
+
174
+ ```bash
175
+ zmr export traces/zmr-login-smoke \
176
+ --out traces/zmr-login-smoke-redacted.zmrtrace \
177
+ --redact
178
+ ```
179
+
180
+ ## Guardrails
181
+
182
+ - Set a step budget and a time budget before discovery starts.
183
+ - Restrict discovery to known app ids, deep-link schemes, and test accounts.
184
+ - Do not ask an agent to discover credentials or secrets.
185
+ - Prefer accessibility identifiers, resource ids, stable labels, and exact text
186
+ over coordinates.
187
+ - Require human review before committing generated tests.
188
+ - Treat `zmr explore` output as a starting point, not as a production-ready
189
+ flow.
190
+ - Treat `zmr discover` output as a starting point, not as a production-ready
191
+ flow.
192
+ - Treat `zmr draft` output as a starting point, not as a production-ready flow.
193
+ - Use `--include-actions` only after reviewing the trace events that produced
194
+ the replay draft.
195
+ - Redact traces before sharing them outside the local team.
196
+
197
+ ## Current Shape
198
+
199
+ `zmr explore` is the first shipped goal-carrying command in this loop. It still
200
+ requires an existing trace because the current product direction is to keep
201
+ scenario generation explicit, reviewable, and trace-backed before any future
202
+ goal-driven crawler can safely act inside an app.
package/docs/ai-agents.md CHANGED
@@ -1,13 +1,15 @@
1
1
  # AI Agent Guide
2
2
 
3
3
  ZMR is built for external agents. The runner provides device state, typed
4
- actions, waits, assertions, and trace export; the agent decides the next step.
4
+ actions, waits, assertions, trace explanation, and trace export; the agent
5
+ decides the next step.
5
6
 
6
7
  ## Agent Setup Loop
7
8
 
8
9
  Start inside the app checkout:
9
10
 
10
11
  ```bash
12
+ zmr inspect --json --dir .
11
13
  zmr doctor --json --config .zmr/config.json
12
14
  zmr validate --json .zmr/android-smoke.json
13
15
  zmr validate --json .zmr/ios-smoke.json
@@ -18,6 +20,10 @@ Use `zmr doctor --strict --json` in CI or setup flows that should fail on any
18
20
  warning. Prefer JSON output for automation because it includes stable error
19
21
  codes, field paths, and remediation hints.
20
22
 
23
+ Use `zmr inspect --json --dir .` first when an agent enters a repo. It is a
24
+ read-only handoff with config status, generated agent instruction status,
25
+ platform smoke scenario paths, safe next commands, and explicit claim limits.
26
+
21
27
  ## Live JSON-RPC Session
22
28
 
23
29
  Agents should prefer `zmr serve` for interactive work:
@@ -35,8 +41,15 @@ Recommended flow:
35
41
  4. Choose one typed action or assertion.
36
42
  5. Let ZMR settle, then observe again.
37
43
  6. Poll `trace.events` during long runs.
38
- 7. Call `trace.export` with `redact: true` before sharing artifacts.
39
- 8. Call `session.close`.
44
+ 7. Call `trace.explain` when you need the active trace status, failure
45
+ diagnostic, or next commands.
46
+ 8. Call `trace.explore` when you want a review-required scenario candidate for
47
+ a stated goal from the active trace.
48
+ 9. Call `trace.discover` when you want a lower-level reviewable scenario
49
+ candidate from the active trace and the agent already owns goal tracking.
50
+ 10. Call `scenario.validate` after editing generated scenario files.
51
+ 11. Call `trace.export` with `redact: true` before sharing artifacts.
52
+ 12. Call `session.close`.
40
53
 
41
54
  Do not parse screenshots or terminal text when the same fact is available from
42
55
  snapshot nodes, action results, CLI JSON, or trace events.
@@ -47,6 +60,14 @@ For iOS visual captures, `artifactStatus: "captured"` with
47
60
  XCTest hierarchy extraction failed. Use `zmr explain --json <trace-dir>` for
48
61
  the same diagnostic shape after the run.
49
62
 
63
+ For traced CLI runs, `zmr run --json` also returns `nextCommands` with the
64
+ HTML/JUnit report, explain, `zmr discover --from-trace`, and redacted export
65
+ handoffs.
66
+ Agents should prefer those commands over reconstructing trace paths from text.
67
+ When an agent should create the reviewable scenario in the same process, pass
68
+ `--discover-out .zmr/discovered/<name>.json`; the run JSON will include a
69
+ `discovery` object with validation results and `replay` coverage metadata.
70
+
50
71
  ## MCP Session
51
72
 
52
73
  Agents that support the Model Context Protocol can use ZMR directly as a local
@@ -61,13 +82,73 @@ The MCP server exposes mobile-specific tools:
61
82
  - `snapshot`: raw ZMR observation JSON
62
83
  - `semantic_snapshot`: normalized roles, names, selectors, bounds, and
63
84
  recommended actions
64
- - `tap`, `type`, `press_back`, and `open_link`
65
- - `wait_visible`
66
- - `trace_events` and `trace_export`
85
+ - `install_app`, `launch_app`, `stop_app`, and `clear_state`
86
+ - `tap`, `type`, `erase_text`, `hide_keyboard`, `swipe`, `press_back`,
87
+ `open_link`, and `scroll_until_visible`
88
+ - `wait_visible`, `wait_not_visible`, and `wait_any`
89
+ - `assert_visible`, `assert_not_visible`, and `assert_healthy`
90
+ - `scenario_validate`
91
+ - `trace_events`, `trace_explain`, `trace_explore`, `trace_discover`, and
92
+ `trace_export`
67
93
 
68
94
  Prefer `semantic_snapshot` for action planning. It avoids forcing an agent to
69
95
  infer intent from platform-specific Android/UI Automator or XCTest class names.
70
96
 
97
+ ## Agent-Led Discovery
98
+
99
+ Agents can use ZMR to discover flows and draft scenarios by looping over
100
+ `observe.semanticSnapshot`, one typed action, trace events, and scenario
101
+ validation. After a session has produced trace artifacts, call JSON-RPC
102
+ `trace.explain` or MCP `trace_explain` for in-band triage, then call JSON-RPC
103
+ `trace.explore` or MCP `trace_explore` when the generated draft should carry a
104
+ stated goal and guardrails. Use JSON-RPC `trace.discover` or MCP
105
+ `trace_discover` for the lower-level trace-backed draft when the agent already
106
+ owns goal tracking. Use JSON-RPC `scenario.validate` or MCP
107
+ `scenario_validate` after edits. The CLI command is the offline equivalent:
108
+
109
+ ```bash
110
+ zmr discover --from-trace traces/zmr-agent \
111
+ --out .zmr/discovered/replay-smoke.json \
112
+ --include-actions \
113
+ --validate \
114
+ --json
115
+ ```
116
+
117
+ `zmr discover` is review-first. It writes from trace evidence, validates the
118
+ generated scenario when asked, and returns next commands for deterministic
119
+ reruns. It does not crawl, discover credentials, or commit tests. The JSON
120
+ `replay` object lets agents compare trace action events considered for replay,
121
+ generated replay steps, and skipped events before making coverage claims.
122
+
123
+ Use `zmr draft` when you want the lower-level split workflow. It writes
124
+ `launch`, `snapshot`, and conservative `assertVisible` checks by default. For
125
+ traces produced by an agent session with successful typed actions, add
126
+ `--include-actions` to generate a replay draft from supported events before the
127
+ final snapshot assertions:
128
+
129
+ ```bash
130
+ zmr draft --from-trace traces/zmr-agent \
131
+ --out .zmr/discovered/replay-smoke.json \
132
+ --include-actions \
133
+ --json
134
+ zmr validate --json .zmr/discovered/replay-smoke.json
135
+ ```
136
+
137
+ Unsupported or underspecified events are skipped with warnings instead of being
138
+ guessed. Supported replay steps preserve selector and timeout data for waits,
139
+ selector and timeout data for `assertVisible` and `assertNotVisible`, selector
140
+ arrays for `assertNoneVisible`, and timeouts for `assertHealthy` when the trace
141
+ records them. See [Agent Discovery](agent-discovery.md) for the
142
+ recommended reviewable loop.
143
+
144
+ CLI agents can use `zmr explore --from-trace <trace-dir> --out <scenario.json>
145
+ --goal <goal> --include-actions --validate --json` when the goal should travel
146
+ with the generated scenario candidate. The result includes `autonomous:false`,
147
+ `reviewRequired:true`, `guardrails`, replay coverage, validation, and next
148
+ commands. ZMR still does not ship an unbounded autonomous crawler or test
149
+ writer in this developer preview. Keep autonomous planning outside the runner,
150
+ then commit only reviewed scenario JSON.
151
+
71
152
  ## Scenario File Workflow
72
153
 
73
154
  For repeatable tests, generate or edit `.zmr/*.json` scenarios:
@@ -1,6 +1,8 @@
1
1
  # Benchmarking
2
2
 
3
- ZMR benchmark output is intentionally simple: each run appends one JSON object to `results.jsonl`, and `zmr report` turns that directory into a local HTML report.
3
+ ZMR benchmark output is intentionally simple: each run appends one JSON object
4
+ to `results.jsonl`, and `zmr report` turns that directory into local HTML and
5
+ optional JUnit XML artifacts.
4
6
 
5
7
  ## Single Tool Benchmark
6
8
 
@@ -29,7 +31,9 @@ or p95 duration misses the configured threshold.
29
31
  Generate a report:
30
32
 
31
33
  ```bash
32
- zmr report traces/bench-<timestamp> --out traces/bench-<timestamp>/report.html
34
+ zmr report traces/bench-<timestamp> \
35
+ --out traces/bench-<timestamp>/report.html \
36
+ --junit traces/bench-<timestamp>/junit.xml
33
37
  ```
34
38
 
35
39
  ## Pilot Wrapper
@@ -62,7 +66,9 @@ Use `--screen-record` when investigating visual flakes:
62
66
  --max-failures 0
63
67
  ```
64
68
 
65
- For `--runs 1`, the script exports normal and redacted `.zmrtrace` bundles. For `--runs > 1`, it writes benchmark directories and HTML reports.
69
+ For `--runs 1`, the script exports normal and redacted `.zmrtrace` bundles.
70
+ For `--runs > 1`, the pilot wrappers and generated app reliability scripts
71
+ write benchmark directories with HTML and JUnit reports.
66
72
 
67
73
  The iOS pilot wrapper supports the same repeated-run gates:
68
74
 
@@ -128,6 +134,7 @@ Benchmark reports include:
128
134
  - terminal trace status
129
135
  - failed step index and error when available
130
136
  - links to each run's `events.jsonl`
137
+ - optional JUnit XML with one testcase per benchmark row for CI test reports
131
138
 
132
139
  Before making public performance claims, run the same scenario repeatedly on a clean emulator image and include the raw `results.jsonl` plus the redacted trace bundle for any failure.
133
140
 
package/docs/clients.md CHANGED
@@ -21,7 +21,11 @@ Then it sends JSON-RPC methods such as:
21
21
  - `wait.until`
22
22
  - `assert.visible`
23
23
  - `assert.healthy`
24
+ - `scenario.validate`
24
25
  - `trace.events`
26
+ - `trace.explain`
27
+ - `trace.explore`
28
+ - `trace.discover`
25
29
  - `trace.export`
26
30
 
27
31
  Use clients when an AI agent, service, or test harness wants to drive ZMR
@@ -37,12 +41,12 @@ even when normal page text is also present.
37
41
 
38
42
  | Language | Files | Why it looks this way |
39
43
  | --- | --- | --- |
40
- | TypeScript | `clients/typescript/index.mjs`, `index.d.ts` | ESM runtime plus type declarations, no build step required |
41
- | Python | `clients/python/zmr_client.py`, `pyproject.toml` | Standard-library importable module that can be vendored or pip-installed from source |
42
- | Go | `clients/go/zmr/client.go` | Normal Go package inside a module |
43
- | Rust | `clients/rust/src/lib.rs` | Cargo library crate convention |
44
- | Swift | `clients/swift/Sources/ZMRClient/ZMRClient.swift` | SwiftPM package for macOS host-side tools |
45
- | Kotlin | `clients/kotlin/src/main/kotlin/dev/zmr/ZmrClient.kt` | Gradle/Kotlin source package for JVM host-side tools |
44
+ | TypeScript | `clients/typescript/index.mjs`, `index.d.ts` | ESM runtime plus type declarations, including `explainTrace`, `exploreTrace`, `discoverTrace`, and `validateScenario` helpers |
45
+ | Python | `clients/python/zmr_client.py`, `pyproject.toml` | Standard-library importable module with `explain_trace`, `explore_trace`, `discover_trace`, and `validate_scenario` helpers |
46
+ | Go | `clients/go/zmr/client.go` | Normal Go package inside a module, including `ExplainTrace`, `ExploreTrace`, `DiscoverTrace`, and `ValidateScenario` helpers |
47
+ | Rust | `clients/rust/src/lib.rs` | Cargo library crate convention, including `explain_trace`, `explore_trace`, `discover_trace`, and `validate_scenario` helpers |
48
+ | Swift | `clients/swift/Sources/ZMRClient/ZMRClient.swift` | SwiftPM package for macOS host-side tools, including `explainTrace`, `exploreTrace`, `discoverTrace`, and `validateScenario` helpers |
49
+ | Kotlin | `clients/kotlin/src/main/kotlin/dev/zmr/ZmrClient.kt` | Gradle/Kotlin source package for JVM host-side tools, including `explainTrace`, `exploreTrace`, `discoverTrace`, and `validateScenario` helpers |
46
50
 
47
51
  Rust has `src/lib.rs` because Cargo expects a library crate there. The other
48
52
  clients do have equivalent entry points; they are just idiomatic for their
package/docs/demo.md CHANGED
@@ -124,7 +124,9 @@ The script builds `zmr` when needed, validates both sample scenarios, installs t
124
124
  For each single run it writes:
125
125
 
126
126
  - `auth/report.html`
127
+ - `auth/junit.xml`
127
128
  - `login-smoke/report.html`
129
+ - `login-smoke/junit.xml`
128
130
  - `auth.zmrtrace`
129
131
  - `auth-redacted.zmrtrace`
130
132
  - `login-smoke.zmrtrace`
@@ -233,9 +235,11 @@ Build the app for an iOS simulator, boot a simulator, then run:
233
235
  For each run it writes:
234
236
 
235
237
  - `ios-smoke/report.html`
238
+ - `ios-smoke/junit.xml`
236
239
  - `ios-smoke.zmrtrace`
237
240
  - `ios-smoke-redacted.zmrtrace`
238
241
  - `ios-shim-smoke/report.html` when `--ios-shim` is set
242
+ - `ios-shim-smoke/junit.xml` when `--ios-shim` is set
239
243
  - `ios-shim-smoke.zmrtrace` when `--ios-shim` is set
240
244
  - `ios-shim-smoke-redacted.zmrtrace` when `--ios-shim` is set
241
245