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
@@ -0,0 +1,83 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://zmr.dev/schemas/discover-output.schema.json",
4
+ "title": "ZMR Discover Output",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": [
8
+ "ok",
9
+ "mode",
10
+ "schemaVersion",
11
+ "runnerVersion",
12
+ "protocolVersion",
13
+ "out",
14
+ "traceDir",
15
+ "sourceSnapshot",
16
+ "name",
17
+ "appId",
18
+ "selectorCount",
19
+ "stepCount",
20
+ "replay",
21
+ "warnings",
22
+ "validated",
23
+ "validation",
24
+ "nextCommands"
25
+ ],
26
+ "properties": {
27
+ "ok": { "type": "boolean" },
28
+ "mode": { "const": "discover" },
29
+ "schemaVersion": { "const": 1 },
30
+ "runnerVersion": { "type": "string", "minLength": 1 },
31
+ "protocolVersion": { "type": "string", "minLength": 1 },
32
+ "out": { "type": "string", "minLength": 1 },
33
+ "traceDir": { "type": "string", "minLength": 1 },
34
+ "sourceSnapshot": { "type": "string", "minLength": 1 },
35
+ "name": { "type": "string", "minLength": 1 },
36
+ "appId": { "type": ["string", "null"] },
37
+ "selectorCount": { "type": "integer", "minimum": 0 },
38
+ "stepCount": { "type": "integer", "minimum": 2 },
39
+ "replay": {
40
+ "type": "object",
41
+ "additionalProperties": false,
42
+ "required": ["enabled", "eventCount", "stepCount", "skippedEventCount"],
43
+ "properties": {
44
+ "enabled": { "type": "boolean" },
45
+ "eventCount": { "type": "integer", "minimum": 0 },
46
+ "stepCount": { "type": "integer", "minimum": 0 },
47
+ "skippedEventCount": { "type": "integer", "minimum": 0 }
48
+ }
49
+ },
50
+ "warnings": {
51
+ "type": "array",
52
+ "items": { "type": "string" }
53
+ },
54
+ "validated": { "type": "boolean" },
55
+ "validation": {
56
+ "anyOf": [
57
+ { "type": "null" },
58
+ {
59
+ "type": "object",
60
+ "additionalProperties": false,
61
+ "required": ["ok", "path"],
62
+ "properties": {
63
+ "ok": { "type": "boolean" },
64
+ "path": { "type": "string", "minLength": 1 },
65
+ "name": { "type": "string", "minLength": 1 },
66
+ "appId": { "type": ["string", "null"] },
67
+ "stepCount": { "type": "integer", "minimum": 0 },
68
+ "errorCode": { "type": "string", "minLength": 1 },
69
+ "message": { "type": "string", "minLength": 1 },
70
+ "fieldPath": { "type": "string", "minLength": 1 },
71
+ "line": { "type": "integer", "minimum": 1 },
72
+ "column": { "type": "integer", "minimum": 1 }
73
+ }
74
+ }
75
+ ]
76
+ },
77
+ "nextCommands": {
78
+ "type": "array",
79
+ "minItems": 1,
80
+ "items": { "type": "string", "minLength": 1 }
81
+ }
82
+ }
83
+ }
@@ -0,0 +1,58 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://zmr.dev/schemas/draft-output.schema.json",
4
+ "title": "ZMR Draft Output",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": [
8
+ "ok",
9
+ "mode",
10
+ "schemaVersion",
11
+ "runnerVersion",
12
+ "protocolVersion",
13
+ "out",
14
+ "traceDir",
15
+ "sourceSnapshot",
16
+ "name",
17
+ "appId",
18
+ "selectorCount",
19
+ "stepCount",
20
+ "replay",
21
+ "warnings",
22
+ "nextCommands"
23
+ ],
24
+ "properties": {
25
+ "ok": { "type": "boolean" },
26
+ "mode": { "const": "draft" },
27
+ "schemaVersion": { "const": 1 },
28
+ "runnerVersion": { "type": "string", "minLength": 1 },
29
+ "protocolVersion": { "type": "string", "minLength": 1 },
30
+ "out": { "type": "string", "minLength": 1 },
31
+ "traceDir": { "type": "string", "minLength": 1 },
32
+ "sourceSnapshot": { "type": "string", "minLength": 1 },
33
+ "name": { "type": "string", "minLength": 1 },
34
+ "appId": { "type": ["string", "null"] },
35
+ "selectorCount": { "type": "integer", "minimum": 0 },
36
+ "stepCount": { "type": "integer", "minimum": 2 },
37
+ "replay": {
38
+ "type": "object",
39
+ "additionalProperties": false,
40
+ "required": ["enabled", "eventCount", "stepCount", "skippedEventCount"],
41
+ "properties": {
42
+ "enabled": { "type": "boolean" },
43
+ "eventCount": { "type": "integer", "minimum": 0 },
44
+ "stepCount": { "type": "integer", "minimum": 0 },
45
+ "skippedEventCount": { "type": "integer", "minimum": 0 }
46
+ }
47
+ },
48
+ "warnings": {
49
+ "type": "array",
50
+ "items": { "type": "string" }
51
+ },
52
+ "nextCommands": {
53
+ "type": "array",
54
+ "minItems": 1,
55
+ "items": { "type": "string", "minLength": 1 }
56
+ }
57
+ }
58
+ }
@@ -0,0 +1,94 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://zmr.dev/schemas/explore-output.schema.json",
4
+ "title": "ZMR Explore Output",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": [
8
+ "ok",
9
+ "mode",
10
+ "schemaVersion",
11
+ "runnerVersion",
12
+ "protocolVersion",
13
+ "autonomous",
14
+ "reviewRequired",
15
+ "guardrails",
16
+ "out",
17
+ "traceDir",
18
+ "sourceSnapshot",
19
+ "name",
20
+ "appId",
21
+ "selectorCount",
22
+ "stepCount",
23
+ "replay",
24
+ "warnings",
25
+ "validated",
26
+ "validation",
27
+ "nextCommands"
28
+ ],
29
+ "properties": {
30
+ "ok": { "type": "boolean" },
31
+ "mode": { "const": "explore" },
32
+ "schemaVersion": { "const": 1 },
33
+ "runnerVersion": { "type": "string", "minLength": 1 },
34
+ "protocolVersion": { "type": "string", "minLength": 1 },
35
+ "goal": { "type": "string", "minLength": 1 },
36
+ "autonomous": { "const": false },
37
+ "reviewRequired": { "const": true },
38
+ "guardrails": {
39
+ "type": "array",
40
+ "minItems": 1,
41
+ "items": { "type": "string", "minLength": 1 }
42
+ },
43
+ "out": { "type": "string", "minLength": 1 },
44
+ "traceDir": { "type": "string", "minLength": 1 },
45
+ "sourceSnapshot": { "type": "string", "minLength": 1 },
46
+ "name": { "type": "string", "minLength": 1 },
47
+ "appId": { "type": ["string", "null"] },
48
+ "selectorCount": { "type": "integer", "minimum": 0 },
49
+ "stepCount": { "type": "integer", "minimum": 2 },
50
+ "replay": {
51
+ "type": "object",
52
+ "additionalProperties": false,
53
+ "required": ["enabled", "eventCount", "stepCount", "skippedEventCount"],
54
+ "properties": {
55
+ "enabled": { "type": "boolean" },
56
+ "eventCount": { "type": "integer", "minimum": 0 },
57
+ "stepCount": { "type": "integer", "minimum": 0 },
58
+ "skippedEventCount": { "type": "integer", "minimum": 0 }
59
+ }
60
+ },
61
+ "warnings": {
62
+ "type": "array",
63
+ "items": { "type": "string" }
64
+ },
65
+ "validated": { "type": "boolean" },
66
+ "validation": {
67
+ "anyOf": [
68
+ { "type": "null" },
69
+ {
70
+ "type": "object",
71
+ "additionalProperties": false,
72
+ "required": ["ok", "path"],
73
+ "properties": {
74
+ "ok": { "type": "boolean" },
75
+ "path": { "type": "string", "minLength": 1 },
76
+ "name": { "type": "string", "minLength": 1 },
77
+ "appId": { "type": ["string", "null"] },
78
+ "stepCount": { "type": "integer", "minimum": 0 },
79
+ "errorCode": { "type": "string", "minLength": 1 },
80
+ "message": { "type": "string", "minLength": 1 },
81
+ "fieldPath": { "type": "string", "minLength": 1 },
82
+ "line": { "type": "integer", "minimum": 1 },
83
+ "column": { "type": "integer", "minimum": 1 }
84
+ }
85
+ }
86
+ ]
87
+ },
88
+ "nextCommands": {
89
+ "type": "array",
90
+ "minItems": 1,
91
+ "items": { "type": "string", "minLength": 1 }
92
+ }
93
+ }
94
+ }
@@ -0,0 +1,88 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://zmr.dev/schemas/inspect-output.schema.json",
4
+ "title": "ZMR Inspect Output",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": [
8
+ "ok",
9
+ "status",
10
+ "schemaVersion",
11
+ "runnerVersion",
12
+ "protocolVersion",
13
+ "dir",
14
+ "configPath",
15
+ "configExists",
16
+ "agentInstructionsPath",
17
+ "agentInstructionsExists",
18
+ "platforms",
19
+ "recommendedCommands",
20
+ "claimsPolicy",
21
+ "limitations"
22
+ ],
23
+ "properties": {
24
+ "ok": { "type": "boolean" },
25
+ "status": { "enum": ["ready", "needs-setup"] },
26
+ "schemaVersion": { "const": 1 },
27
+ "runnerVersion": { "type": "string", "minLength": 1 },
28
+ "protocolVersion": { "type": "string", "minLength": 1 },
29
+ "dir": { "type": "string", "minLength": 1 },
30
+ "configPath": { "type": "string", "minLength": 1 },
31
+ "configExists": { "type": "boolean" },
32
+ "agentInstructionsPath": { "type": "string", "minLength": 1 },
33
+ "agentInstructionsExists": { "type": "boolean" },
34
+ "platforms": {
35
+ "type": "array",
36
+ "items": { "$ref": "#/$defs/platform" }
37
+ },
38
+ "recommendedCommands": {
39
+ "type": "array",
40
+ "items": { "type": "string", "minLength": 1 }
41
+ },
42
+ "claimsPolicy": {
43
+ "type": "array",
44
+ "items": { "type": "string", "minLength": 1 }
45
+ },
46
+ "limitations": {
47
+ "type": "array",
48
+ "items": { "type": "string", "minLength": 1 }
49
+ }
50
+ },
51
+ "$defs": {
52
+ "platform": {
53
+ "type": "object",
54
+ "additionalProperties": false,
55
+ "required": [
56
+ "name",
57
+ "enabled",
58
+ "defaultDevice",
59
+ "smokeScenario",
60
+ "smokeScenarioExists",
61
+ "traceDir"
62
+ ],
63
+ "properties": {
64
+ "name": { "enum": ["android", "ios"] },
65
+ "enabled": { "type": "boolean" },
66
+ "defaultDevice": {
67
+ "anyOf": [
68
+ { "type": "string", "minLength": 1 },
69
+ { "type": "null" }
70
+ ]
71
+ },
72
+ "smokeScenario": {
73
+ "anyOf": [
74
+ { "type": "string", "minLength": 1 },
75
+ { "type": "null" }
76
+ ]
77
+ },
78
+ "smokeScenarioExists": { "type": "boolean" },
79
+ "traceDir": {
80
+ "anyOf": [
81
+ { "type": "string", "minLength": 1 },
82
+ { "type": "null" }
83
+ ]
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
@@ -34,6 +34,8 @@
34
34
  "failedStepIndex": { "type": "integer", "minimum": 0 },
35
35
  "error": { "type": "string", "minLength": 1 },
36
36
  "reportPath": { "type": "string", "minLength": 1 },
37
+ "discovery": { "$ref": "https://zmr.dev/schemas/discover-output.schema.json" },
38
+ "discoveryError": { "type": "string", "minLength": 1 },
37
39
  "nextCommands": {
38
40
  "type": "array",
39
41
  "minItems": 1,
@@ -167,6 +167,10 @@ mkdir -p "$APP_ROOT"
167
167
  APP_ROOT="$(cd "$APP_ROOT" && pwd)"
168
168
  mkdir -p "$APP_ROOT/.zmr" "$APP_ROOT/.zmr/shims/ios"
169
169
  rm -f "$APP_ROOT/.zmr/ios-shim-state/build-for-testing.ready"
170
+ rm -f "$APP_ROOT/.zmr/ios-shim-state/destination.id"
171
+ rm -f "$APP_ROOT/.zmr/ios-shim-state/xcodebuild.pid"
172
+ rm -f "$APP_ROOT/.zmr/ios-shim-state/xcodebuild.log"
173
+ rm -rf "$APP_ROOT/.zmr/ios-shim-state/server"
170
174
  cp "$ROOT/shims/ios/ZMRShim.swift" "$APP_ROOT/.zmr/shims/ios/ZMRShim.swift"
171
175
  cp "$ROOT/shims/ios/ZMRShimUITestCase.swift" "$APP_ROOT/.zmr/ZMRShimUITestCase.swift"
172
176
  cp "$ROOT/scripts/ensure-ios-shim-target.rb" "$APP_ROOT/.zmr/ensure-ios-shim-target.rb"
@@ -216,6 +220,7 @@ STATE_DIR="$APP_ROOT/.zmr/ios-shim-state"
216
220
  SERVER_DIR="\$STATE_DIR/server"
217
221
  PID_FILE="\$STATE_DIR/xcodebuild.pid"
218
222
  READY_FILE="\$SERVER_DIR/ready"
223
+ DESTINATION_ID_FILE="\$STATE_DIR/destination.id"
219
224
  BUILD_READY_FILE="\$STATE_DIR/build-for-testing.ready"
220
225
  LOG_FILE="\$STATE_DIR/xcodebuild.log"
221
226
  STDIN_FILE="\$(mktemp)"
@@ -241,6 +246,52 @@ tail_log() {
241
246
  fi
242
247
  }
243
248
 
249
+ tail_log_path() {
250
+ local log_file="\$1"
251
+ if [[ -f "\$log_file" ]]; then
252
+ tail -120 "\$log_file" >&2
253
+ fi
254
+ }
255
+
256
+ run_xcodebuild_with_timeout() {
257
+ local label="\$1"
258
+ local timeout_seconds="\$2"
259
+ local log_file="\$3"
260
+ shift 3
261
+
262
+ local pid started_at next_progress progress_seconds elapsed
263
+ progress_seconds="\${ZMR_IOS_SHIM_PROGRESS_SECONDS:-30}"
264
+ "\$@" >"\$log_file" 2>&1 &
265
+ pid="\$!"
266
+ started_at="\$SECONDS"
267
+ next_progress=\$((started_at + progress_seconds))
268
+
269
+ while kill -0 "\$pid" 2>/dev/null; do
270
+ elapsed=\$((SECONDS - started_at))
271
+ if (( elapsed >= timeout_seconds )); then
272
+ echo "timed out waiting for \$label after \${timeout_seconds}s" >&2
273
+ kill -TERM "\$pid" 2>/dev/null || true
274
+ sleep 2
275
+ if kill -0 "\$pid" 2>/dev/null; then
276
+ kill -KILL "\$pid" 2>/dev/null || true
277
+ fi
278
+ wait "\$pid" 2>/dev/null || true
279
+ tail_log_path "\$log_file"
280
+ exit 1
281
+ fi
282
+ if (( SECONDS >= next_progress )); then
283
+ echo "still waiting for \$label after \${elapsed}s; log: \$log_file" >&2
284
+ next_progress=\$((SECONDS + progress_seconds))
285
+ fi
286
+ sleep 1
287
+ done
288
+
289
+ if ! wait "\$pid"; then
290
+ tail_log_path "\$log_file"
291
+ exit 1
292
+ fi
293
+ }
294
+
244
295
  resolve_destination() {
245
296
  local destination_id="$DEVICE"
246
297
  if [[ "\$destination_id" == "booted" ]]; then
@@ -248,7 +299,20 @@ resolve_destination() {
248
299
  echo "physical iOS shim requires an explicit --device id" >&2
249
300
  exit 2
250
301
  fi
302
+ if [[ -f "\$DESTINATION_ID_FILE" ]]; then
303
+ destination_id="\$(cat "\$DESTINATION_ID_FILE" 2>/dev/null || true)"
304
+ if [[ -n "\$destination_id" ]] && xcrun simctl list devices available | grep -F "\$destination_id" >/dev/null 2>&1; then
305
+ # Reuse the first resolved booted simulator so build-for-testing and
306
+ # test-without-building target the same simulator even if it shuts down
307
+ # while Xcode is compiling the shim target.
308
+ printf '%s' "\$destination_id"
309
+ return 0
310
+ fi
311
+ fi
251
312
  destination_id="\$(xcrun simctl list devices booted | sed -n 's/.*(\([0-9A-Fa-f-][0-9A-Fa-f-]*\)) (Booted).*/\1/p' | head -n 1)"
313
+ if [[ -n "\$destination_id" ]]; then
314
+ printf '%s' "\$destination_id" > "\$DESTINATION_ID_FILE"
315
+ fi
252
316
  fi
253
317
  if [[ -z "\$destination_id" ]]; then
254
318
  echo "no booted iOS simulator found" >&2
@@ -272,9 +336,13 @@ is_server_running() {
272
336
  if [[ ! -f "\$PID_FILE" ]]; then
273
337
  return 1
274
338
  fi
275
- local pid
339
+ local pid command
276
340
  pid="\$(cat "\$PID_FILE" 2>/dev/null || true)"
277
- [[ -n "\$pid" ]] && kill -0 "\$pid" 2>/dev/null
341
+ if [[ -z "\$pid" ]] || ! kill -0 "\$pid" 2>/dev/null; then
342
+ return 1
343
+ fi
344
+ command="\$(ps -p "\$pid" -o command= 2>/dev/null || true)"
345
+ [[ "\$command" == *xcodebuild* && "\$command" == *ZMRShimUITests* ]]
278
346
  }
279
347
 
280
348
  run_oneshot() {
@@ -332,18 +400,15 @@ build_for_testing() {
332
400
  destination_id="\$(destination_spec)"
333
401
  build_log="\$STATE_DIR/xcodebuild.build.log"
334
402
 
335
- if ! xcodebuild build-for-testing \\
336
- "\${XCODEBUILD_ARGS[@]}" \\
337
- -scheme "$SCHEME" \\
338
- -configuration "$CONFIGURATION" \\
339
- -destination "\$destination_id" \\
340
- ZMR_SHIM_MODE="server" \\
341
- ZMR_SHIM_SERVER_DIR="\$SERVER_DIR" \\
342
- ZMR_APP_BUNDLE_ID="$BUNDLE_ID" \\
343
- >"\$build_log" 2>&1; then
344
- tail -120 "\$build_log" >&2
345
- exit 1
346
- fi
403
+ run_xcodebuild_with_timeout "iOS shim build-for-testing" "\${ZMR_IOS_SHIM_BUILD_TIMEOUT_SECONDS:-5400}" "\$build_log" \\
404
+ xcodebuild build-for-testing \\
405
+ "\${XCODEBUILD_ARGS[@]}" \\
406
+ -scheme "$SCHEME" \\
407
+ -configuration "$CONFIGURATION" \\
408
+ -destination "\$destination_id" \\
409
+ ZMR_SHIM_MODE="server" \\
410
+ ZMR_SHIM_SERVER_DIR="\$SERVER_DIR" \\
411
+ ZMR_APP_BUNDLE_ID="$BUNDLE_ID"
347
412
 
348
413
  touch "\$BUILD_READY_FILE"
349
414
  }
@@ -423,6 +423,38 @@ def benchmark_threshold_reason(row):
423
423
  return "requires " + ", ".join(reasons)
424
424
 
425
425
 
426
+ agent_workflow_fields = (
427
+ "agentWorkflow",
428
+ "mcp",
429
+ "jsonRpc",
430
+ "semanticSnapshot",
431
+ "typedActions",
432
+ "traceEvents",
433
+ "traceExplain",
434
+ "traceExplore",
435
+ "traceDiscover",
436
+ "scenarioValidation",
437
+ "redactedExport",
438
+ )
439
+
440
+
441
+ def agent_workflow_pass(row):
442
+ command = row.get("command")
443
+ if row.get("name") == "local release gate" and isinstance(command, str) and "release-gate.sh" in command:
444
+ return True
445
+ return all(row.get(field) is True for field in agent_workflow_fields)
446
+
447
+
448
+ def agent_workflow_reason(row):
449
+ command = row.get("command")
450
+ if row.get("name") == "local release gate" and (not isinstance(command, str) or "release-gate.sh" not in command):
451
+ return "requires release-gate.sh or structured agentWorkflow evidence"
452
+ missing = [field for field in agent_workflow_fields if row.get(field) is not True]
453
+ if missing:
454
+ return "requires release-gate.sh or structured agentWorkflow evidence: " + ", ".join(missing)
455
+ return "requires release-gate.sh or structured agentWorkflow evidence"
456
+
457
+
426
458
  def row_satisfies(label, row):
427
459
  if row.get("status") != "passed":
428
460
  return False
@@ -436,6 +468,8 @@ def row_satisfies(label, row):
436
468
  return physical_ios_device_value(row) is not None
437
469
  if label == "competitive benchmark comparison":
438
470
  return benchmark_thresholds_pass(row)
471
+ if label == "agent workflow smoke":
472
+ return agent_workflow_pass(row)
439
473
  return True
440
474
 
441
475
 
@@ -485,6 +519,8 @@ def requirement_status(label, names):
485
519
  reason = "requires concrete physical device identifier evidence"
486
520
  elif label == "competitive benchmark comparison":
487
521
  reason = benchmark_threshold_reason(row)
522
+ elif label == "agent workflow smoke":
523
+ reason = agent_workflow_reason(row)
488
524
  return {
489
525
  "name": label,
490
526
  "status": "insufficient",
@@ -508,6 +544,7 @@ requirements = [
508
544
 
509
545
  if target in ("production", "market-claim"):
510
546
  requirements.extend([
547
+ ("agent workflow smoke", ("agent workflow smoke", "local release gate")),
511
548
  ("physical iOS readiness", "physical iOS readiness"),
512
549
  ("Android hardware pilot", "Android hardware pilot"),
513
550
  ("iOS simulator hardware pilot", "iOS simulator hardware pilot"),
@@ -582,6 +619,7 @@ next_step_commands = {
582
619
  "local release gate": ["./scripts/release-candidate.sh --mode local"],
583
620
  "public Android demo": ["zmr-demo-android --runs 5"],
584
621
  "public iOS simulator demo": ["zmr-demo-ios --runs 5"],
622
+ "agent workflow smoke": ["./scripts/release-gate.sh"],
585
623
  "physical iOS readiness": [physical_ios_pilot_command(default_pilot_evidence)],
586
624
  "Android hardware pilot": [grouped_simulator_pilot_command(default_pilot_evidence)],
587
625
  "iOS simulator hardware pilot": [grouped_simulator_pilot_command(default_pilot_evidence)],
@@ -690,6 +728,11 @@ def append_grouped_next_steps(blocked_items):
690
728
  append_next_step(next_steps, label, commands, present)
691
729
  handled.update(present)
692
730
 
731
+ maybe_group(
732
+ ["local release gate", "agent workflow smoke"],
733
+ "local release gate + agent workflow smoke",
734
+ ["./scripts/release-candidate.sh --mode local"],
735
+ )
693
736
  maybe_group(
694
737
  ["Android hardware pilot", "iOS simulator hardware pilot"],
695
738
  "Android hardware pilot + iOS simulator hardware pilot",
@@ -171,6 +171,11 @@ run() {
171
171
  fi
172
172
  }
173
173
 
174
+ run_zmr_report() {
175
+ local trace_dir="$1"
176
+ run "$ZMR_BIN" report "$trace_dir" --out "$trace_dir/report.html" --junit "$trace_dir/junit.xml"
177
+ }
178
+
174
179
  capture() {
175
180
  if [[ "$DRY_RUN" -eq 1 ]]; then
176
181
  echo ""
@@ -505,7 +510,7 @@ if [[ "$RUNS" -eq 1 ]]; then
505
510
  SINGLE_TRACE="$TRACE_ROOT/scenario"
506
511
  run rm -rf "$SINGLE_TRACE"
507
512
  run_zmr_android_scenario "$SCENARIO" --device "$DEVICE" --app-id "$APP_ID" --trace-dir "$SINGLE_TRACE"
508
- run "$ZMR_BIN" report "$SINGLE_TRACE" --out "$SINGLE_TRACE/report.html"
513
+ run_zmr_report "$SINGLE_TRACE"
509
514
  run "$ZMR_BIN" export "$SINGLE_TRACE" --out "$TRACE_ROOT/scenario.zmrtrace"
510
515
  run "$ZMR_BIN" export "$SINGLE_TRACE" --out "$TRACE_ROOT/scenario-redacted.zmrtrace" --redact
511
516
  else
@@ -513,11 +518,11 @@ if [[ "$RUNS" -eq 1 ]]; then
513
518
  LOGIN_TRACE="$TRACE_ROOT/login-smoke"
514
519
  run rm -rf "$AUTH_TRACE" "$LOGIN_TRACE"
515
520
  run_zmr_android_scenario examples/android-app-auth-probe.json --device "$DEVICE" --app-id "$APP_ID" --trace-dir "$AUTH_TRACE"
516
- run "$ZMR_BIN" report "$AUTH_TRACE" --out "$AUTH_TRACE/report.html"
521
+ run_zmr_report "$AUTH_TRACE"
517
522
  run "$ZMR_BIN" export "$AUTH_TRACE" --out "$TRACE_ROOT/auth.zmrtrace"
518
523
  run "$ZMR_BIN" export "$AUTH_TRACE" --out "$TRACE_ROOT/auth-redacted.zmrtrace" --redact
519
524
  run_zmr_android_scenario examples/android-app-login-smoke.json --device "$DEVICE" --app-id "$APP_ID" --trace-dir "$LOGIN_TRACE"
520
- run "$ZMR_BIN" report "$LOGIN_TRACE" --out "$LOGIN_TRACE/report.html"
525
+ run_zmr_report "$LOGIN_TRACE"
521
526
  run "$ZMR_BIN" export "$LOGIN_TRACE" --out "$TRACE_ROOT/login-smoke.zmrtrace"
522
527
  run "$ZMR_BIN" export "$LOGIN_TRACE" --out "$TRACE_ROOT/login-smoke-redacted.zmrtrace" --redact
523
528
  fi
@@ -531,12 +536,12 @@ else
531
536
  fi
532
537
  if [[ -n "$SCENARIO" ]]; then
533
538
  run_android_benchmark --zmr "$SCENARIO" --device "$DEVICE" --app-id "$APP_ID" --runs "$RUNS" --trace-root "$TRACE_ROOT/bench-scenario" "${benchmark_gate_args[@]}"
534
- run "$ZMR_BIN" report "$TRACE_ROOT/bench-scenario" --out "$TRACE_ROOT/bench-scenario/report.html"
539
+ run_zmr_report "$TRACE_ROOT/bench-scenario"
535
540
  else
536
541
  run_android_benchmark --zmr examples/android-app-auth-probe.json --device "$DEVICE" --app-id "$APP_ID" --runs "$RUNS" --trace-root "$TRACE_ROOT/bench-auth" "${benchmark_gate_args[@]}"
537
- run "$ZMR_BIN" report "$TRACE_ROOT/bench-auth" --out "$TRACE_ROOT/bench-auth/report.html"
542
+ run_zmr_report "$TRACE_ROOT/bench-auth"
538
543
  run_android_benchmark --zmr examples/android-app-login-smoke.json --device "$DEVICE" --app-id "$APP_ID" --runs "$RUNS" --trace-root "$TRACE_ROOT/bench-login-smoke" "${benchmark_gate_args[@]}"
539
- run "$ZMR_BIN" report "$TRACE_ROOT/bench-login-smoke" --out "$TRACE_ROOT/bench-login-smoke/report.html"
544
+ run_zmr_report "$TRACE_ROOT/bench-login-smoke"
540
545
  fi
541
546
  fi
542
547
 
@@ -546,9 +551,30 @@ cat <<EOF
546
551
 
547
552
  Android pilot complete.
548
553
  Output directory: $TRACE_ROOT
549
- Shareable bundles:
550
- $TRACE_ROOT/auth-redacted.zmrtrace
551
- $TRACE_ROOT/login-smoke-redacted.zmrtrace
554
+ EOF
555
+
556
+ if [[ "$RUNS" -eq 1 ]]; then
557
+ echo "Shareable bundles:"
558
+ if [[ -n "$SCENARIO" ]]; then
559
+ echo " $TRACE_ROOT/scenario-redacted.zmrtrace"
560
+ else
561
+ echo " $TRACE_ROOT/auth-redacted.zmrtrace"
562
+ echo " $TRACE_ROOT/login-smoke-redacted.zmrtrace"
563
+ fi
564
+ else
565
+ echo "Benchmark reports:"
566
+ if [[ -n "$SCENARIO" ]]; then
567
+ echo " $TRACE_ROOT/bench-scenario/report.html"
568
+ echo " $TRACE_ROOT/bench-scenario/junit.xml"
569
+ else
570
+ echo " $TRACE_ROOT/bench-auth/report.html"
571
+ echo " $TRACE_ROOT/bench-auth/junit.xml"
572
+ echo " $TRACE_ROOT/bench-login-smoke/report.html"
573
+ echo " $TRACE_ROOT/bench-login-smoke/junit.xml"
574
+ fi
575
+ fi
576
+
577
+ cat <<EOF
552
578
  Viewer:
553
579
  $ROOT/viewer/index.html
554
580
  EOF