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,472 @@
1
+ const std = @import("std");
2
+
3
+ pub const app_config_file = ".zmr/config.json";
4
+ pub const app_android_smoke_file = ".zmr/android-smoke.json";
5
+ pub const app_ios_smoke_file = ".zmr/ios-smoke.json";
6
+ pub const app_device_matrix_file = ".zmr/device-matrix.json";
7
+ pub const app_agents_file = ".zmr/AGENTS.md";
8
+
9
+ pub const app_created_files = [_][]const u8{
10
+ app_config_file,
11
+ app_android_smoke_file,
12
+ app_ios_smoke_file,
13
+ app_device_matrix_file,
14
+ app_agents_file,
15
+ };
16
+
17
+ pub const app_script_names = [_][]const u8{
18
+ "doctor",
19
+ "schemas",
20
+ "validate",
21
+ "android",
22
+ "androidReport",
23
+ "androidReliability",
24
+ "ios",
25
+ "iosReport",
26
+ "iosReliability",
27
+ "matrix",
28
+ "pilotGate",
29
+ "readiness",
30
+ "serve",
31
+ "mcp",
32
+ "explain",
33
+ "exportTrace",
34
+ };
35
+
36
+ pub fn writeStarterScenario(
37
+ allocator: std.mem.Allocator,
38
+ path: []const u8,
39
+ app_id: []const u8,
40
+ force: bool,
41
+ ) !void {
42
+ if (!force) {
43
+ std.fs.cwd().access(path, .{}) catch |err| switch (err) {
44
+ error.FileNotFound => {},
45
+ else => return err,
46
+ };
47
+ if (std.fs.cwd().access(path, .{})) |_| return error.PathAlreadyExists else |err| switch (err) {
48
+ error.FileNotFound => {},
49
+ else => return err,
50
+ }
51
+ }
52
+
53
+ if (std.fs.path.dirname(path)) |parent| {
54
+ if (parent.len > 0) try std.fs.cwd().makePath(parent);
55
+ }
56
+
57
+ var file = try std.fs.cwd().createFile(path, .{ .truncate = true });
58
+ defer file.close();
59
+ var buffer: [4096]u8 = undefined;
60
+ var file_writer = file.writer(&buffer);
61
+ const writer = &file_writer.interface;
62
+ try writer.writeAll(
63
+ \\{
64
+ \\ "name": "Starter mobile smoke",
65
+ \\ "appId": "
66
+ );
67
+ try writeJsonStringContent(writer, app_id);
68
+ try writer.writeAll(
69
+ \\",
70
+ \\ "steps": [
71
+ \\ { "action": "launch" },
72
+ \\ { "action": "assertHealthy" },
73
+ \\ { "action": "snapshot" }
74
+ \\ ]
75
+ \\}
76
+ \\
77
+ );
78
+ try writer.flush();
79
+ _ = allocator;
80
+ }
81
+
82
+ pub fn writeAppScaffold(
83
+ allocator: std.mem.Allocator,
84
+ dir: []const u8,
85
+ app_id: []const u8,
86
+ force: bool,
87
+ ) !void {
88
+ const zmr_dir = try std.fs.path.join(allocator, &.{ dir, ".zmr" });
89
+ defer allocator.free(zmr_dir);
90
+ try std.fs.cwd().makePath(zmr_dir);
91
+
92
+ const config_path = try std.fs.path.join(allocator, &.{ zmr_dir, appFileBasename(app_config_file) });
93
+ defer allocator.free(config_path);
94
+ const android_path = try std.fs.path.join(allocator, &.{ zmr_dir, appFileBasename(app_android_smoke_file) });
95
+ defer allocator.free(android_path);
96
+ const ios_path = try std.fs.path.join(allocator, &.{ zmr_dir, appFileBasename(app_ios_smoke_file) });
97
+ defer allocator.free(ios_path);
98
+ const matrix_path = try std.fs.path.join(allocator, &.{ zmr_dir, appFileBasename(app_device_matrix_file) });
99
+ defer allocator.free(matrix_path);
100
+ const agents_path = try std.fs.path.join(allocator, &.{ zmr_dir, appFileBasename(app_agents_file) });
101
+ defer allocator.free(agents_path);
102
+
103
+ try writeAppConfig(config_path, app_id, true);
104
+ try writePlatformSmoke(android_path, "Android smoke", app_id, force);
105
+ try writePlatformSmoke(ios_path, "iOS smoke", app_id, force);
106
+ try writeDeviceMatrix(matrix_path, app_id, true);
107
+ try writeAgentInstructions(agents_path, app_id, true);
108
+ try ensureTraceGitignore(allocator, dir);
109
+ }
110
+
111
+ fn appFileBasename(path: []const u8) []const u8 {
112
+ return std.fs.path.basename(path);
113
+ }
114
+
115
+ fn writeAppConfig(path: []const u8, app_id: []const u8, force: bool) !void {
116
+ var file = try createOutputFile(path, force);
117
+ defer file.close();
118
+ var buffer: [8192]u8 = undefined;
119
+ var file_writer = file.writer(&buffer);
120
+ const writer = &file_writer.interface;
121
+ try writer.writeAll(
122
+ \\{
123
+ \\ "schemaVersion": 1,
124
+ \\ "appId": "
125
+ );
126
+ try writeJsonStringContent(writer, app_id);
127
+ try writer.writeAll(
128
+ \\",
129
+ \\ "android": {
130
+ \\ "enabled": true,
131
+ \\ "defaultDevice": "emulator-5554",
132
+ \\ "smokeScenario": ".zmr/android-smoke.json",
133
+ \\ "traceDir": "traces/zmr-android"
134
+ \\ },
135
+ \\ "ios": {
136
+ \\ "enabled": true,
137
+ \\ "defaultDevice": "booted",
138
+ \\ "smokeScenario": ".zmr/ios-smoke.json",
139
+ \\ "traceDir": "traces/zmr-ios"
140
+ \\ },
141
+ \\ "artifacts": {
142
+ \\ "screenshots": true,
143
+ \\ "hierarchy": true,
144
+ \\ "logs": true,
145
+ \\ "screenRecording": false
146
+ \\ },
147
+ \\ "scripts": {
148
+ \\ "doctor": "zmr doctor --strict --json --config .zmr/config.json",
149
+ \\ "schemas": "zmr schemas --json",
150
+ \\ "validate": "zmr validate --json .zmr/android-smoke.json && zmr validate --json .zmr/ios-smoke.json",
151
+ \\ "android": "zmr run .zmr/android-smoke.json --device emulator-5554 --trace-dir traces/zmr-android",
152
+ \\ "androidReport": "zmr report traces/zmr-android --out traces/zmr-android/report.html",
153
+ \\ "androidReliability": "export ZMR_BIN=\"${ZMR_BIN:-zmr}\"; zmr-benchmark --zmr .zmr/android-smoke.json --device emulator-5554 --app-id
154
+ );
155
+ try writer.writeAll(" ");
156
+ try writeJsonShellArg(writer, app_id);
157
+ try writer.writeAll(
158
+ \\ --runs 20 --trace-root traces/zmr-android-reliability --min-pass-rate 100 --max-failures 0 --max-p95-ms 30000 && \"$ZMR_BIN\" report traces/zmr-android-reliability --out traces/zmr-android-reliability/report.html",
159
+ \\ "ios": "zmr run .zmr/ios-smoke.json --platform ios --device booted --trace-dir traces/zmr-ios",
160
+ \\ "iosReport": "zmr report traces/zmr-ios --out traces/zmr-ios/report.html",
161
+ \\ "iosReliability": "export ZMR_BIN=\"${ZMR_BIN:-zmr}\"; zmr-benchmark --zmr .zmr/ios-smoke.json --platform ios --device booted --app-id
162
+ );
163
+ try writer.writeAll(" ");
164
+ try writeJsonShellArg(writer, app_id);
165
+ try writer.writeAll(
166
+ \\ --xcrun xcrun --runs 20 --trace-root traces/zmr-ios-reliability --min-pass-rate 100 --max-failures 0 --max-p95-ms 45000 && \"$ZMR_BIN\" report traces/zmr-ios-reliability --out traces/zmr-ios-reliability/report.html",
167
+ \\ "matrix": "ZMR_BIN=${ZMR_BIN:-zmr} zmr-device-matrix --matrix .zmr/device-matrix.json --trace-root traces/zmr-matrix --min-pass-rate 100 --max-failures 0",
168
+ \\ "pilotGate": "zmr-pilot-gate --android --ios --android-app-root . --android-app-id
169
+ );
170
+ try writer.writeAll(" ");
171
+ try writeJsonShellArg(writer, app_id);
172
+ try writer.writeAll(
173
+ \\ --android-device emulator-5554 --ios-app-root . --ios-app-path ./build/Debug-iphonesimulator/Sample.app --ios-app-id
174
+ );
175
+ try writer.writeAll(" ");
176
+ try writeJsonShellArg(writer, app_id);
177
+ try writer.writeAll(
178
+ \\ --ios-device booted --runs 20 --min-pass-rate 100 --max-failures 0 --evidence-out traces/zmr-pilots/evidence.jsonl",
179
+ \\ "readiness": "zmr-release-readiness --evidence traces/zmr-pilots/evidence.jsonl --target production --json",
180
+ \\ "serve": "zmr serve --transport stdio --config .zmr/config.json --trace-dir traces/zmr-agent",
181
+ \\ "mcp": "zmr mcp --config .zmr/config.json --trace-dir traces/zmr-agent",
182
+ \\ "explain": "zmr explain traces/zmr-agent --json",
183
+ \\ "exportTrace": "zmr export traces/zmr-agent --out traces/zmr-agent-redacted.zmrtrace --redact"
184
+ \\ }
185
+ \\}
186
+ \\
187
+ );
188
+ try writer.flush();
189
+ }
190
+
191
+ fn writeDeviceMatrix(path: []const u8, app_id: []const u8, force: bool) !void {
192
+ var file = try createOutputFile(path, force);
193
+ defer file.close();
194
+ var buffer: [8192]u8 = undefined;
195
+ var file_writer = file.writer(&buffer);
196
+ const writer = &file_writer.interface;
197
+ try writer.writeAll(
198
+ \\{
199
+ \\ "runs": 1,
200
+ \\ "appId": "
201
+ );
202
+ try writeJsonStringContent(writer, app_id);
203
+ try writer.writeAll(
204
+ \\",
205
+ \\ "devices": [
206
+ \\ {
207
+ \\ "name": "android-emulator",
208
+ \\ "platform": "android",
209
+ \\ "serial": "emulator-5554",
210
+ \\ "scenario": ".zmr/android-smoke.json",
211
+ \\ "adb": "adb"
212
+ \\ },
213
+ \\ {
214
+ \\ "name": "ios-simulator",
215
+ \\ "platform": "ios",
216
+ \\ "iosDeviceType": "simulator",
217
+ \\ "serial": "booted",
218
+ \\ "scenario": ".zmr/ios-smoke.json",
219
+ \\ "xcrun": "xcrun"
220
+ \\ }
221
+ \\ ]
222
+ \\}
223
+ \\
224
+ );
225
+ try writer.flush();
226
+ }
227
+
228
+ fn writePlatformSmoke(path: []const u8, name: []const u8, app_id: []const u8, force: bool) !void {
229
+ if (!force and try pathExists(path)) return;
230
+ var file = try createOutputFile(path, force);
231
+ defer file.close();
232
+ var buffer: [4096]u8 = undefined;
233
+ var file_writer = file.writer(&buffer);
234
+ const writer = &file_writer.interface;
235
+ try writer.writeAll(
236
+ \\{
237
+ \\ "name": "
238
+ );
239
+ try writeJsonStringContent(writer, name);
240
+ try writer.writeAll(
241
+ \\",
242
+ \\ "appId": "
243
+ );
244
+ try writeJsonStringContent(writer, app_id);
245
+ try writer.writeAll(
246
+ \\",
247
+ \\ "steps": [
248
+ \\ { "action": "launch" },
249
+ \\ { "action": "assertHealthy" },
250
+ \\ { "action": "snapshot" }
251
+ \\ ]
252
+ \\}
253
+ \\
254
+ );
255
+ try writer.flush();
256
+ }
257
+
258
+ fn pathExists(path: []const u8) !bool {
259
+ std.fs.cwd().access(path, .{}) catch |err| switch (err) {
260
+ error.FileNotFound => return false,
261
+ else => return err,
262
+ };
263
+ return true;
264
+ }
265
+
266
+ fn writeJsonShellArg(writer: anytype, value: []const u8) !void {
267
+ if (isShellSafe(value)) {
268
+ try writeJsonStringContent(writer, value);
269
+ return;
270
+ }
271
+ try writer.writeAll("'");
272
+ for (value) |ch| {
273
+ if (ch == '\'') try writeJsonStringContent(writer, "'\\''") else try writeJsonStringContent(writer, &[_]u8{ch});
274
+ }
275
+ try writer.writeAll("'");
276
+ }
277
+
278
+ fn writeShellArg(writer: anytype, value: []const u8) !void {
279
+ if (isShellSafe(value)) {
280
+ try writer.writeAll(value);
281
+ return;
282
+ }
283
+ try writer.writeAll("'");
284
+ for (value) |ch| {
285
+ if (ch == '\'') try writer.writeAll("'\\''") else try writer.writeAll(&[_]u8{ch});
286
+ }
287
+ try writer.writeAll("'");
288
+ }
289
+
290
+ fn isShellSafe(value: []const u8) bool {
291
+ if (value.len == 0) return false;
292
+ for (value) |ch| {
293
+ switch (ch) {
294
+ 'A'...'Z', 'a'...'z', '0'...'9', '_', '.', '/', ':', '=', '@', '%', '+', ',', '-' => {},
295
+ else => return false,
296
+ }
297
+ }
298
+ return true;
299
+ }
300
+
301
+ fn writeAgentInstructions(path: []const u8, app_id: []const u8, force: bool) !void {
302
+ var file = try createOutputFile(path, force);
303
+ defer file.close();
304
+ var buffer: [4096]u8 = undefined;
305
+ var file_writer = file.writer(&buffer);
306
+ const writer = &file_writer.interface;
307
+ try writer.writeAll(
308
+ \\# ZMR Agent Instructions
309
+ \\
310
+ \\App id: `
311
+ );
312
+ try writer.writeAll(app_id);
313
+ try writer.writeAll(
314
+ \\`
315
+ \\
316
+ \\Start from the app checkout. Keep generated scenarios and config under `.zmr/`, and write run output under `traces/`.
317
+ \\
318
+ \\## Setup Checks
319
+ \\
320
+ \\```bash
321
+ \\zmr doctor --strict --json --config .zmr/config.json
322
+ \\zmr schemas --json
323
+ \\zmr validate --json .zmr/android-smoke.json && zmr validate --json .zmr/ios-smoke.json
324
+ \\```
325
+ \\
326
+ \\## Interactive Agent Session
327
+ \\
328
+ \\```bash
329
+ \\zmr serve --transport stdio --config .zmr/config.json --trace-dir traces/zmr-agent
330
+ \\zmr mcp --config .zmr/config.json --trace-dir traces/zmr-agent
331
+ \\```
332
+ \\
333
+ \\Use `semantic_snapshot` before choosing tap or type actions. Prefer selectors from accessibility identifiers, resource ids, labels, or exact text before coordinates. Export redacted traces before sharing artifacts.
334
+ \\
335
+ \\## Failure Triage
336
+ \\
337
+ \\```bash
338
+ \\zmr explain traces/zmr-agent --json
339
+ \\```
340
+ \\
341
+ \\Use the JSON explanation before editing selectors. It includes the terminal status, partial visual-capture diagnostics, and the last useful failure context.
342
+ \\
343
+ \\## Trace Sharing
344
+ \\
345
+ \\```bash
346
+ \\zmr export traces/zmr-agent --out traces/zmr-agent-redacted.zmrtrace --redact
347
+ \\```
348
+ \\
349
+ \\Add `--omit-screenshots` when visual artifacts may contain sensitive data.
350
+ \\
351
+ \\## Direct Smoke Runs
352
+ \\
353
+ \\```bash
354
+ \\zmr run .zmr/android-smoke.json --device emulator-5554 --trace-dir traces/zmr-android
355
+ \\zmr report traces/zmr-android --out traces/zmr-android/report.html
356
+ \\export ZMR_BIN="${ZMR_BIN:-zmr}"; zmr-benchmark --zmr .zmr/android-smoke.json --device emulator-5554 --app-id
357
+ );
358
+ try writer.writeAll(" ");
359
+ try writeShellArg(writer, app_id);
360
+ try writer.writeAll(
361
+ \\ --runs 20 --trace-root traces/zmr-android-reliability --min-pass-rate 100 --max-failures 0 --max-p95-ms 30000 && "$ZMR_BIN" report traces/zmr-android-reliability --out traces/zmr-android-reliability/report.html
362
+ \\zmr run .zmr/ios-smoke.json --platform ios --device booted --trace-dir traces/zmr-ios
363
+ \\zmr report traces/zmr-ios --out traces/zmr-ios/report.html
364
+ \\export ZMR_BIN="${ZMR_BIN:-zmr}"; zmr-benchmark --zmr .zmr/ios-smoke.json --platform ios --device booted --app-id
365
+ );
366
+ try writer.writeAll(" ");
367
+ try writeShellArg(writer, app_id);
368
+ try writer.writeAll(
369
+ \\ --xcrun xcrun --runs 20 --trace-root traces/zmr-ios-reliability --min-pass-rate 100 --max-failures 0 --max-p95-ms 45000 && "$ZMR_BIN" report traces/zmr-ios-reliability --out traces/zmr-ios-reliability/report.html
370
+ \\```
371
+ \\
372
+ \\## Release Claims
373
+ \\
374
+ \\```bash
375
+ \\zmr-release-readiness --evidence traces/zmr-pilots/evidence.jsonl --target production --json
376
+ \\```
377
+ \\
378
+ \\Do not claim production readiness from smoke runs alone. Use `satisfied` for proven requirements; do not infer readiness from raw `passed` evidence. Use `recommendedWording` and keep `claimLimitations` intact when summarizing readiness. When readiness is blocked, follow `nextSteps[].commands` in order.
379
+ \\
380
+ \\## App Commands
381
+ \\
382
+ \\```bash
383
+ \\zmr doctor --strict --json --config .zmr/config.json
384
+ \\zmr schemas --json
385
+ \\zmr validate --json .zmr/android-smoke.json && zmr validate --json .zmr/ios-smoke.json
386
+ \\zmr run .zmr/android-smoke.json --device emulator-5554 --trace-dir traces/zmr-android
387
+ \\zmr report traces/zmr-android --out traces/zmr-android/report.html
388
+ \\export ZMR_BIN="${ZMR_BIN:-zmr}"; zmr-benchmark --zmr .zmr/android-smoke.json --device emulator-5554 --app-id
389
+ );
390
+ try writer.writeAll(" ");
391
+ try writeShellArg(writer, app_id);
392
+ try writer.writeAll(
393
+ \\ --runs 20 --trace-root traces/zmr-android-reliability --min-pass-rate 100 --max-failures 0 --max-p95-ms 30000 && "$ZMR_BIN" report traces/zmr-android-reliability --out traces/zmr-android-reliability/report.html
394
+ \\zmr run .zmr/ios-smoke.json --platform ios --device booted --trace-dir traces/zmr-ios
395
+ \\zmr report traces/zmr-ios --out traces/zmr-ios/report.html
396
+ \\export ZMR_BIN="${ZMR_BIN:-zmr}"; zmr-benchmark --zmr .zmr/ios-smoke.json --platform ios --device booted --app-id
397
+ );
398
+ try writer.writeAll(" ");
399
+ try writeShellArg(writer, app_id);
400
+ try writer.writeAll(
401
+ \\ --xcrun xcrun --runs 20 --trace-root traces/zmr-ios-reliability --min-pass-rate 100 --max-failures 0 --max-p95-ms 45000 && "$ZMR_BIN" report traces/zmr-ios-reliability --out traces/zmr-ios-reliability/report.html
402
+ \\ZMR_BIN=${ZMR_BIN:-zmr} zmr-device-matrix --matrix .zmr/device-matrix.json --trace-root traces/zmr-matrix --min-pass-rate 100 --max-failures 0
403
+ );
404
+ try writer.writeAll("zmr-pilot-gate --android --ios --android-app-root . --android-app-id ");
405
+ try writeShellArg(writer, app_id);
406
+ try writer.writeAll(" --android-device emulator-5554 --ios-app-root . --ios-app-path ./build/Debug-iphonesimulator/Sample.app --ios-app-id ");
407
+ try writeShellArg(writer, app_id);
408
+ try writer.writeAll(
409
+ \\ --ios-device booted --runs 20 --min-pass-rate 100 --max-failures 0 --evidence-out traces/zmr-pilots/evidence.jsonl
410
+ \\zmr-release-readiness --evidence traces/zmr-pilots/evidence.jsonl --target production --json
411
+ \\zmr serve --transport stdio --config .zmr/config.json --trace-dir traces/zmr-agent
412
+ \\zmr mcp --config .zmr/config.json --trace-dir traces/zmr-agent
413
+ \\zmr explain traces/zmr-agent --json
414
+ \\zmr export traces/zmr-agent --out traces/zmr-agent-redacted.zmrtrace --redact
415
+ \\```
416
+ \\
417
+ );
418
+ try writer.flush();
419
+ }
420
+
421
+ fn createOutputFile(path: []const u8, force: bool) !std.fs.File {
422
+ if (std.fs.path.dirname(path)) |parent| {
423
+ if (parent.len > 0) try std.fs.cwd().makePath(parent);
424
+ }
425
+ if (!force) return try std.fs.cwd().createFile(path, .{ .exclusive = true });
426
+ return try std.fs.cwd().createFile(path, .{ .truncate = true });
427
+ }
428
+
429
+ fn ensureTraceGitignore(allocator: std.mem.Allocator, dir: []const u8) !void {
430
+ const path = try std.fs.path.join(allocator, &.{ dir, ".gitignore" });
431
+ defer allocator.free(path);
432
+
433
+ const existing = std.fs.cwd().readFileAlloc(allocator, path, 1024 * 1024) catch |err| switch (err) {
434
+ error.FileNotFound => "",
435
+ else => return err,
436
+ };
437
+ const had_existing_file = existing.ptr != "".ptr;
438
+ defer if (had_existing_file) allocator.free(existing);
439
+
440
+ if (std.mem.indexOf(u8, existing, "traces/") != null) return;
441
+
442
+ var file = try std.fs.cwd().createFile(path, .{ .truncate = true });
443
+ defer file.close();
444
+ var buffer: [4096]u8 = undefined;
445
+ var file_writer = file.writer(&buffer);
446
+ const writer = &file_writer.interface;
447
+ if (existing.len > 0) {
448
+ try writer.writeAll(existing);
449
+ if (!std.mem.endsWith(u8, existing, "\n")) try writer.writeAll("\n");
450
+ try writer.writeAll("\n");
451
+ }
452
+ try writer.writeAll(
453
+ \\# ZMR local run artifacts
454
+ \\traces/
455
+ \\
456
+ );
457
+ try writer.flush();
458
+ }
459
+
460
+ fn writeJsonStringContent(writer: anytype, value: []const u8) !void {
461
+ for (value) |ch| {
462
+ switch (ch) {
463
+ '"' => try writer.writeAll("\\\""),
464
+ '\\' => try writer.writeAll("\\\\"),
465
+ '\n' => try writer.writeAll("\\n"),
466
+ '\r' => try writer.writeAll("\\r"),
467
+ '\t' => try writer.writeAll("\\t"),
468
+ 0...7, 11, 12, 14...31 => try writer.print("\\u{x:0>4}", .{ch}),
469
+ else => try writer.writeAll(&.{ch}),
470
+ }
471
+ }
472
+ }