agent-scenario-loop 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 (170) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +119 -0
  3. package/app/profile-session.ts +812 -0
  4. package/core/config-template.json +41 -0
  5. package/dist/core/agent-summary.d.ts +15 -0
  6. package/dist/core/agent-summary.js +177 -0
  7. package/dist/core/artifact-contract.d.ts +151 -0
  8. package/dist/core/artifact-contract.js +897 -0
  9. package/dist/core/artifact-layout.d.ts +56 -0
  10. package/dist/core/artifact-layout.js +61 -0
  11. package/dist/core/artifact-writer.d.ts +44 -0
  12. package/dist/core/artifact-writer.js +55 -0
  13. package/dist/core/comparison.d.ts +133 -0
  14. package/dist/core/comparison.js +294 -0
  15. package/dist/core/evidence-interpreter.d.ts +28 -0
  16. package/dist/core/evidence-interpreter.js +69 -0
  17. package/dist/core/execution-plan.d.ts +44 -0
  18. package/dist/core/execution-plan.js +95 -0
  19. package/dist/core/planner.d.ts +132 -0
  20. package/dist/core/planner.js +812 -0
  21. package/dist/core/ports.d.ts +198 -0
  22. package/dist/core/ports.js +146 -0
  23. package/dist/core/run-index.d.ts +62 -0
  24. package/dist/core/run-index.js +143 -0
  25. package/dist/core/schema-validator.d.ts +86 -0
  26. package/dist/core/schema-validator.js +407 -0
  27. package/dist/index.d.ts +11 -0
  28. package/dist/index.js +27 -0
  29. package/dist/runner/agent-device-driver.d.ts +126 -0
  30. package/dist/runner/agent-device-driver.js +168 -0
  31. package/dist/runner/agent-device.d.ts +295 -0
  32. package/dist/runner/agent-device.js +1271 -0
  33. package/dist/runner/android-adb-driver.d.ts +175 -0
  34. package/dist/runner/android-adb-driver.js +399 -0
  35. package/dist/runner/android-adb.d.ts +254 -0
  36. package/dist/runner/android-adb.js +1618 -0
  37. package/dist/runner/argent-driver.d.ts +183 -0
  38. package/dist/runner/argent-driver.js +297 -0
  39. package/dist/runner/argent.d.ts +349 -0
  40. package/dist/runner/argent.js +1211 -0
  41. package/dist/runner/check-plan.d.ts +45 -0
  42. package/dist/runner/check-plan.js +210 -0
  43. package/dist/runner/cli.d.ts +20 -0
  44. package/dist/runner/cli.js +23 -0
  45. package/dist/runner/compare-latest.d.ts +99 -0
  46. package/dist/runner/compare-latest.js +233 -0
  47. package/dist/runner/compare.d.ts +58 -0
  48. package/dist/runner/compare.js +157 -0
  49. package/dist/runner/demo-loop.d.ts +45 -0
  50. package/dist/runner/demo-loop.js +170 -0
  51. package/dist/runner/example-android-live.d.ts +137 -0
  52. package/dist/runner/example-android-live.js +454 -0
  53. package/dist/runner/example-ios-live.d.ts +137 -0
  54. package/dist/runner/example-ios-live.js +471 -0
  55. package/dist/runner/host-doctor.d.ts +131 -0
  56. package/dist/runner/host-doctor.js +628 -0
  57. package/dist/runner/init-project.d.ts +88 -0
  58. package/dist/runner/init-project.js +263 -0
  59. package/dist/runner/ios-simctl-driver.d.ts +69 -0
  60. package/dist/runner/ios-simctl-driver.js +97 -0
  61. package/dist/runner/ios-simctl.d.ts +254 -0
  62. package/dist/runner/ios-simctl.js +1415 -0
  63. package/dist/runner/live-android.d.ts +137 -0
  64. package/dist/runner/live-android.js +539 -0
  65. package/dist/runner/live-comparison.d.ts +67 -0
  66. package/dist/runner/live-comparison.js +147 -0
  67. package/dist/runner/live-ios.d.ts +137 -0
  68. package/dist/runner/live-ios.js +460 -0
  69. package/dist/runner/live-proof-summary.d.ts +263 -0
  70. package/dist/runner/live-proof-summary.js +465 -0
  71. package/dist/runner/live-proof.d.ts +467 -0
  72. package/dist/runner/live-proof.js +920 -0
  73. package/dist/runner/local-env.d.ts +64 -0
  74. package/dist/runner/local-env.js +155 -0
  75. package/dist/runner/profile-android.d.ts +82 -0
  76. package/dist/runner/profile-android.js +671 -0
  77. package/dist/runner/profile-ios.d.ts +108 -0
  78. package/dist/runner/profile-ios.js +532 -0
  79. package/dist/runner/profile-mobile.d.ts +254 -0
  80. package/dist/runner/profile-mobile.js +1307 -0
  81. package/dist/runner/validate-project.d.ts +273 -0
  82. package/dist/runner/validate-project.js +1501 -0
  83. package/docs/adapters.md +145 -0
  84. package/docs/api.md +94 -0
  85. package/docs/authoring.md +196 -0
  86. package/docs/concepts.md +136 -0
  87. package/docs/consumer-rehearsal.md +115 -0
  88. package/docs/contracts.md +267 -0
  89. package/docs/live-proofs.md +270 -0
  90. package/docs/principles.md +46 -0
  91. package/examples/event-logs/app-startup-baseline.log +4 -0
  92. package/examples/event-logs/app-startup-current.log +4 -0
  93. package/examples/minimal-app/README.md +70 -0
  94. package/examples/mobile-app/README.md +302 -0
  95. package/examples/mobile-app/app.json +22 -0
  96. package/examples/mobile-app/asl/package-scripts.json +32 -0
  97. package/examples/mobile-app/asl.config.json +37 -0
  98. package/examples/mobile-app/event-logs/android-app-startup.log +4 -0
  99. package/examples/mobile-app/event-logs/android-open-close-cycle.log +12 -0
  100. package/examples/mobile-app/event-logs/android-scroll-settle.log +12 -0
  101. package/examples/mobile-app/event-logs/app-startup.log +4 -0
  102. package/examples/mobile-app/event-logs/open-close-cycle.log +12 -0
  103. package/examples/mobile-app/event-logs/scroll-settle.log +12 -0
  104. package/examples/mobile-app/index.ts +20 -0
  105. package/examples/mobile-app/metro.config.js +20 -0
  106. package/examples/mobile-app/package.json +62 -0
  107. package/examples/mobile-app/patches/expo-modules-jsi@56.0.10.patch +19 -0
  108. package/examples/mobile-app/plugins/with-ios-build-compat.js +271 -0
  109. package/examples/mobile-app/pnpm-lock.yaml +4440 -0
  110. package/examples/mobile-app/runner-manifests/evidence-provider.json +79 -0
  111. package/examples/mobile-app/runner-manifests/primary-runner.json +19 -0
  112. package/examples/mobile-app/scenarios/android/app-startup-video.json +73 -0
  113. package/examples/mobile-app/scenarios/android/app-startup.json +44 -0
  114. package/examples/mobile-app/scenarios/android/open-close-cycle.json +54 -0
  115. package/examples/mobile-app/scenarios/android/scroll-settle.json +49 -0
  116. package/examples/mobile-app/scenarios/ios/app-startup.json +44 -0
  117. package/examples/mobile-app/scenarios/ios/open-close-cycle.json +54 -0
  118. package/examples/mobile-app/scenarios/ios/scroll-settle.json +49 -0
  119. package/examples/mobile-app/scenarios/mobile/app-startup.json +91 -0
  120. package/examples/mobile-app/scenarios/mobile/open-close-cycle.json +160 -0
  121. package/examples/mobile-app/scenarios/mobile/scroll-settle.json +148 -0
  122. package/examples/mobile-app/scripts/asl-capture-accessibility-provider.mjs +112 -0
  123. package/examples/mobile-app/scripts/asl-capture-profiler-provider.mjs +127 -0
  124. package/examples/mobile-app/src/devtools/profile-session.ts +7 -0
  125. package/examples/mobile-app/src/example-screen.tsx +322 -0
  126. package/examples/mobile-app/tsconfig.json +16 -0
  127. package/examples/mobile-app/tsconfig.typecheck.json +13 -0
  128. package/examples/runners/README.md +44 -0
  129. package/examples/runners/adb-android.json +25 -0
  130. package/examples/runners/agent-device-android.json +27 -0
  131. package/examples/runners/agent-device-ios.json +27 -0
  132. package/examples/runners/argent-android.json +32 -0
  133. package/examples/runners/argent-ios.json +32 -0
  134. package/examples/runners/argent-react-profiler-provider.json +15 -0
  135. package/examples/runners/axe-accessibility-provider.json +24 -0
  136. package/examples/runners/manual-log-ingest.json +9 -0
  137. package/examples/runners/rozenite-profiler-provider.json +9 -0
  138. package/examples/runners/script-accessibility-provider.json +24 -0
  139. package/examples/runners/script-memory-provider.json +24 -0
  140. package/examples/runners/script-network-provider.json +24 -0
  141. package/examples/runners/script-profiler-provider.json +30 -0
  142. package/examples/runners/xcodebuildmcp-ios.json +29 -0
  143. package/examples/scenarios/ios/app-startup.json +28 -0
  144. package/examples/scenarios/ios/open-close-cycle.json +35 -0
  145. package/examples/scenarios/mobile/app-startup.json +72 -0
  146. package/examples/scenarios/mobile/media-open-close.json +141 -0
  147. package/examples/scenarios/mobile/open-close-cycle.json +135 -0
  148. package/examples/scenarios/mobile/scroll-settle.json +106 -0
  149. package/package.json +240 -0
  150. package/schemas/budget-verdict.schema.json +115 -0
  151. package/schemas/causal-run.schema.json +279 -0
  152. package/schemas/comparison.schema.json +196 -0
  153. package/schemas/health.schema.json +108 -0
  154. package/schemas/live-proof-set.schema.json +195 -0
  155. package/schemas/live-proof.schema.json +413 -0
  156. package/schemas/manifest.schema.json +204 -0
  157. package/schemas/metrics.schema.json +137 -0
  158. package/schemas/project-validation.schema.json +343 -0
  159. package/schemas/runner-capabilities.schema.json +217 -0
  160. package/schemas/scenario.schema.json +400 -0
  161. package/schemas/verdict.schema.json +88 -0
  162. package/templates/evidence-provider.json +83 -0
  163. package/templates/gitignore-snippet +9 -0
  164. package/templates/integration-readme.md +125 -0
  165. package/templates/mobile-scenario.json +133 -0
  166. package/templates/package-scripts.json +32 -0
  167. package/templates/primary-runner.json +19 -0
  168. package/templates/project.config.json +37 -0
  169. package/templates/scripts/asl-capture-accessibility-provider.mjs +112 -0
  170. package/templates/scripts/asl-capture-profiler-provider.mjs +127 -0
@@ -0,0 +1,125 @@
1
+ # Agent Scenario Loop Integration
2
+
3
+ This folder was generated by `asl-init` for the `{{SCENARIO_ID}}` scenario.
4
+
5
+ ## Files
6
+
7
+ - `asl.config.json`: app identifiers, artifact roots, and runner defaults
8
+ - `scenarios/mobile/{{SCENARIO_ID}}.json`: first durable mobile scenario
9
+ - `runner-manifests/primary-runner.json`: primary runner capability manifest
10
+ - `runner-manifests/evidence-provider.json`: optional evidence-provider manifest
11
+ - `scripts/asl-capture-accessibility-provider.mjs`: runnable starter accessibility provider command
12
+ - `scripts/asl-capture-profiler-provider.mjs`: runnable starter profiler, memory, and network provider command
13
+ - `src/devtools/profile-session.ts`: React Native app-side truth-event helper
14
+ - `asl/package-scripts.json`: package-script snippets to merge into your app `package.json`
15
+ - `asl/gitignore-snippet`: artifact ignore rules to merge into your app `.gitignore`
16
+
17
+ `asl.config.json` declares the supported fixture, adb, simctl, agent-device, and Argent lanes that the generated scripts expose. Keep that list aligned with the scripts your project intends agents to run; `asl-validate-project` reports invalid required config as errors and omitted optional package drivers as warnings.
18
+
19
+ For iOS apps with multiple installed variants on the same simulator, add sibling bundle ids to `app.iosConflictingBundleIds`. ASL treats those as a preflight guard: if a declared sibling variant is installed beside the target bundle, iOS live proof fails before launch so deep-link and profile-session evidence cannot come from the wrong app.
20
+
21
+ ## App Wiring
22
+
23
+ 1. Install the peer app dependencies used by `src/devtools/profile-session.ts` if your app does not already have them:
24
+
25
+ ```bash
26
+ pnpm add @react-native-async-storage/async-storage expo-linking
27
+ ```
28
+
29
+ 2. Mount `useProfileSessionBootstrap()` once near the app root.
30
+ 3. Emit app-owned truth events with `emitProfileEvent()` at scenario milestones.
31
+ 4. Register command targets with `registerProfileCommandTargetHandler()` when a runner should activate app behavior directly.
32
+ 5. Append `asl/gitignore-snippet` to your app `.gitignore`; commit scenarios, runner manifests, and app truth-event wiring. Keep `paths.artifactRoot`, `paths.iosArtifactsRoot`, and `paths.androidArtifactsRoot` under ignored project-local artifact paths so agent sandboxes and git state agree on where runtime proof evidence belongs.
33
+
34
+ ## First Proof Loop
35
+
36
+ Start with planner validation before live device proof:
37
+
38
+ ```bash
39
+ asl-validate-project --root . --platform all --out artifacts/asl/project-validation
40
+ asl-check-plan --scenario scenarios/mobile/{{SCENARIO_ID}}.json --runner runner-manifests/primary-runner.json --provider runner-manifests/evidence-provider.json --platform ios --out artifacts/asl/plan/{{SCENARIO_ID}}-ios
41
+ asl-check-plan --scenario scenarios/mobile/{{SCENARIO_ID}}.json --runner runner-manifests/primary-runner.json --provider runner-manifests/evidence-provider.json --platform android --out artifacts/asl/plan/{{SCENARIO_ID}}-android
42
+ ```
43
+
44
+ Keep deterministic validation and live device proof as separate lanes. Planner checks and fixture profile runs should work in ordinary build or agent sandboxes. Live runs that touch adb, simctl, agent-device, Argent, emulators, simulators, physical devices, Metro, or native build tools need host/device access from the first attempt. If a live command cannot reach those host services, classify it as runner environment health before treating it as a scenario regression.
45
+
46
+ Use sandboxed execution for `asl-validate-project`, `asl-check-plan`, fixture profile runs that read `--events`, and `asl-live-proof` inspection. Use host/device execution for `asl-profile-android --adb-capture`, `asl-profile-ios --simctl-capture`, `asl-live-android`, `asl-live-ios`, `asl-agent-device`, `asl-argent`, and any aggregate live script with `--agent-device-proof` or `--argent-proof`. Preserve the failed health artifact and rerun the same command with the right device access instead of changing the scenario to work around environment health.
47
+
48
+ When the app emits profile events, run platform profile commands against captured evidence or a live runner:
49
+
50
+ ```bash
51
+ ASL_PROFILE_IOS_EVENTS=event-logs/{{SCENARIO_ID}}-ios.log pnpm asl:profile:ios
52
+ ASL_PROFILE_ANDROID_EVENTS=event-logs/{{SCENARIO_ID}}-android.log pnpm asl:profile:android
53
+ ASL_PROFILE_IOS_EVENTS=event-logs/{{SCENARIO_ID}}-ios.log pnpm asl:profile:ios:provider
54
+ ASL_PROFILE_ANDROID_EVENTS=event-logs/{{SCENARIO_ID}}-android.log pnpm asl:profile:android:provider
55
+ asl-profile-ios --config asl.config.json --scenario scenarios/mobile/{{SCENARIO_ID}}.json --simctl-capture --profile-session --profile-session-storage --launch --comparison-lane {{SCENARIO_ID}}-ios-live --out artifacts/asl/ios --run-id {{SCENARIO_ID}}-ios-live
56
+ asl-profile-android --config asl.config.json --scenario scenarios/mobile/{{SCENARIO_ID}}.json --adb-capture --profile-session --clear-logcat --launch --comparison-lane {{SCENARIO_ID}}-android-live --out artifacts/asl/android --run-id {{SCENARIO_ID}}-android-live
57
+ ```
58
+
59
+ Those commands write `health.json`, `verdict.json`, `agent-summary.md`, `metrics.json`, `causal-run.json`, and raw evidence under the printed run directory. The `*:provider` scripts also execute the starter provider commands through `runner-manifests/evidence-provider.json`, then inventory the generated accessibility, profiler, memory, and network evidence in `manifest.artifacts.evidenceAttachments`. Compare a trusted current run with an explicit artifact path:
60
+
61
+ When `--wait-ms` is omitted from profile-session live capture, ASL derives the final adb or simctl evidence window from the scenario execution steps and cycle count. Pass `--wait-ms` only when a target app needs an explicit override.
62
+
63
+ ```bash
64
+ ASL_COMPARE_IOS_CURRENT=artifacts/asl/ios/{{SCENARIO_ID}}/{{SCENARIO_ID}}-ios-live pnpm asl:compare:ios
65
+ ASL_COMPARE_ANDROID_CURRENT=artifacts/asl/android/{{SCENARIO_ID}}/{{SCENARIO_ID}}-android-live pnpm asl:compare:android
66
+ ```
67
+
68
+ For aggregate live proof, use the generated live scripts:
69
+
70
+ ```bash
71
+ ASL_IOS_UDID=<simulator-udid> ASL_IOS_APP_ID=<bundle-id> pnpm asl:ios:live
72
+ ASL_ANDROID_SERIAL=<emulator-or-device-serial> ASL_ANDROID_APP_ID=<package-name> pnpm asl:android:live
73
+ ```
74
+
75
+ Those scripts pass `--compare-latest --fail-on-regression`. A missing trusted baseline is recorded as skipped comparison evidence; a real regression exits nonzero after artifacts are written.
76
+
77
+ For repeated local runs, put machine-specific runner values in an ignored `.asl.local.env` file at your app root instead of repeating inline environment variables. ASL CLIs load the nearest `.asl.local.env` without overriding already-exported values:
78
+
79
+ ```bash
80
+ ASL_HOST_DOCTOR_REQUIRE=android,ios,agent-device,argent
81
+ ASL_AGENT_DEVICE_REQUIRED_PLATFORMS=ios,android
82
+ ASL_ANDROID_SERIAL=<emulator-or-device-serial>
83
+ ASL_IOS_UDID=<simulator-udid>
84
+ ASL_ANDROID_APP_ID=<package-name>
85
+ ASL_IOS_APP_ID=<bundle-id>
86
+ ASL_IOS_DEV_CLIENT_URL=<dev-client-url>
87
+ ASL_ARGENT_BIN=pnpm
88
+ ASL_ARGENT_BASE_ARGS="dlx @swmansion/argent run"
89
+ ```
90
+
91
+ Set `ASL_IOS_DEV_CLIENT_URL` only when an iOS dev-client shell must open a specific Metro URL before profile-session evidence is collected. Keep the actual URL in ignored local env state because it depends on the local app id, host, and port.
92
+
93
+ ## Portable Interaction Proof
94
+
95
+ Use the generated interaction scripts when an external driver should prove launch, visible UI, gestures, screenshots, or another scenario-declared driver action without changing the scenario file:
96
+
97
+ ```bash
98
+ ASL_IOS_UDID=<simulator-udid> ASL_IOS_APP_ID=<bundle-id> pnpm asl:agent-device:ios
99
+ ASL_ANDROID_SERIAL=<emulator-or-device-serial> ASL_ANDROID_APP_ID=<package-name> pnpm asl:agent-device:android
100
+ ASL_ARGENT_BIN=/path/to/argent pnpm asl:argent:check
101
+ ASL_ARGENT_BIN=/path/to/argent ASL_IOS_APP_ID=<bundle-id> pnpm asl:argent:ios
102
+ ASL_ARGENT_BIN=/path/to/argent ASL_ANDROID_SERIAL=<emulator-or-device-serial> ASL_ANDROID_APP_ID=<package-name> pnpm asl:argent:android
103
+ ```
104
+
105
+ If agent-device already owns the device through a named session, set `ASL_IOS_AGENT_DEVICE_SESSION` or `ASL_ANDROID_AGENT_DEVICE_SESSION` instead of relying only on the direct UDID or serial; the generated direct agent-device scripts and aggregate live sidecar scripts both honor those variables. The default session mode is `reuse`, which lets that existing session own target selection. Set `ASL_IOS_AGENT_DEVICE_SESSION_MODE=bind` or `ASL_ANDROID_AGENT_DEVICE_SESSION_MODE=bind` when ASL should name the sidecar session while still forwarding the configured UDID or serial. Run `pnpm asl:agent-device:check` and `pnpm asl:argent:check` before device-bound sidecars when you need to verify installed tool surfaces; those checks write availability artifacts under `artifacts/asl/agent-device-check` and `artifacts/asl/argent-check`. The agent-device check also records active sessions in `raw/agent-device-availability.json` and summarizes them in `agent-summary.md`, so agents can choose a compatible session without rerunning discovery manually. Prefer a real `argent` executable on PATH, or set `ASL_ARGENT_BIN=/path/to/argent` when the package manager installed it somewhere else. `ASL_ARGENT_BIN=npx` with `ASL_ARGENT_BASE_ARGS="--yes @swmansion/argent run"` is supported as a wrapper shape, but verify it with `pnpm asl:argent:check` before relying on it. Argent uses `--udid` for both iOS simulators and Android emulators; ASL resolves the iOS `booted` shorthand to the concrete simulator UDID before invoking Argent. Set `ASL_ARGENT_COMMAND_TIMEOUT_MS` when package-manager or simulator startup is slower than the default command window. Set `ASL_ARGENT_IOS_SIMCTL_SCREENSHOT_FALLBACK=1` when iOS Argent launch and accessibility proof should keep running even if Argent's screenshot backend is unhealthy and simctl can still capture the screen.
106
+
107
+ If your project adds an aggregate batch runner that writes `live-proof.json`, inspect it with:
108
+
109
+ ```bash
110
+ ASL_LIVE_PROOF=artifacts/asl/<platform>/_live-proof/<run-id>/live-proof.json pnpm asl:live-proof
111
+ ```
112
+
113
+ After Android and iOS live proofs have both written aggregate artifacts, run the generated platform-set gate:
114
+
115
+ ```bash
116
+ pnpm asl:live-proof:both
117
+ ```
118
+
119
+ That script requires both platform proofs, writes `artifacts/asl/live-proof-set/live-proof-set.json` plus `agent-summary.md`, and exits nonzero when either platform is missing, failed, or regressed.
120
+
121
+ Set `ASL_REQUIRE_LIVE_PROOF_ARTIFACTS=1` before this gate when the local artifact tree itself is part of the proof, not just the aggregate `live-proof.json` files.
122
+
123
+ Run `pnpm asl:host:doctor` before host/device live proof when adb, CoreSimulator, agent-device, or Argent availability is uncertain. Set `ASL_HOST_DOCTOR_REQUIRE=android,ios,agent-device,argent` in `.asl.local.env` or the shell when sidecar proof availability is part of the gate.
124
+
125
+ The package-script snippets in `asl/package-scripts.json` include fixture, host doctor, portable agent-device and Argent interaction proof, live profile, compare, and proof-inspection commands. Merge the snippets you use into your app `package.json` so future agents can run the loop without rediscovering command arguments.
@@ -0,0 +1,133 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "id": "first-journey",
4
+ "flowId": "first-journey",
5
+ "description": "Prove one durable mobile app journey with app-owned truth events.",
6
+ "journey": {
7
+ "name": "First journey",
8
+ "intent": "Run one important user journey and prove the app reached the expected state.",
9
+ "actor": "returning user",
10
+ "startState": "app ready for the journey",
11
+ "endState": "journey completed"
12
+ },
13
+ "platforms": ["ios", "android"],
14
+ "requiredCapabilities": ["launch", "sessionControl", "command", "logCapture", "artifactWrite"],
15
+ "optionalCapabilities": ["screenshot", "uiTree", "profiler"],
16
+ "truthEvents": {
17
+ "journeyStarted": {
18
+ "event": "first_journey_started",
19
+ "required": true,
20
+ "timeoutMs": 3000,
21
+ "phase": "intent"
22
+ },
23
+ "journeyCompleted": {
24
+ "event": "first_journey_completed",
25
+ "required": true,
26
+ "timeoutMs": 8000,
27
+ "phase": "completion"
28
+ }
29
+ },
30
+ "milestones": [
31
+ {
32
+ "id": "journeyStarted",
33
+ "event": "first_journey_started",
34
+ "description": "The app accepted the journey intent.",
35
+ "required": true,
36
+ "timeoutMs": 3000,
37
+ "phase": "intent"
38
+ },
39
+ {
40
+ "id": "journeyCompleted",
41
+ "event": "first_journey_completed",
42
+ "description": "The app emitted the completion truth event.",
43
+ "required": true,
44
+ "timeoutMs": 8000,
45
+ "phase": "completion"
46
+ }
47
+ ],
48
+ "expectedEvents": ["first_journey_started", "first_journey_completed"],
49
+ "cycles": {
50
+ "iterations": 3,
51
+ "warmupIterations": 0,
52
+ "stopOnFailure": true
53
+ },
54
+ "budgets": [
55
+ {
56
+ "name": "journey p95",
57
+ "source": "milestone",
58
+ "metric": "p95",
59
+ "unit": "ms",
60
+ "limit": 8000,
61
+ "fromMilestone": "journeyStarted",
62
+ "toMilestone": "journeyCompleted"
63
+ },
64
+ {
65
+ "name": "failures",
66
+ "source": "milestone",
67
+ "metric": "failures",
68
+ "unit": "count",
69
+ "limit": 0
70
+ }
71
+ ],
72
+ "steps": [
73
+ {
74
+ "id": "launch",
75
+ "kind": "launch"
76
+ },
77
+ {
78
+ "id": "start-journey",
79
+ "kind": "command",
80
+ "command": "activate-target:first-journey",
81
+ "driverAction": "tap",
82
+ "selector": {
83
+ "kind": "testId",
84
+ "value": "first-journey-start"
85
+ },
86
+ "adapterOptions": {
87
+ "argent": {
88
+ "x": 0.5,
89
+ "y": 0.6
90
+ }
91
+ },
92
+ "timeoutMs": 3000
93
+ },
94
+ {
95
+ "id": "wait-for-completion",
96
+ "kind": "waitForMilestone",
97
+ "milestone": "journeyCompleted",
98
+ "timeoutMs": 8000
99
+ },
100
+ {
101
+ "id": "capture-final-screen",
102
+ "kind": "captureEvidence",
103
+ "artifact": "screenshot",
104
+ "driverAction": "screenshot",
105
+ "required": false
106
+ }
107
+ ],
108
+ "artifacts": {
109
+ "required": ["logs", "signals"],
110
+ "optional": ["screenshot", "uiTree", "profiler"]
111
+ },
112
+ "adapterOptions": {
113
+ "androidAdb": {
114
+ "commands": [
115
+ {
116
+ "command": "activate-target:first-journey",
117
+ "label": "start first journey",
118
+ "waitMs": 300
119
+ }
120
+ ]
121
+ },
122
+ "iosSimctl": {
123
+ "commands": [
124
+ {
125
+ "command": "activate-target:first-journey",
126
+ "label": "start first journey",
127
+ "waitMs": 300
128
+ }
129
+ ],
130
+ "repeat": 3
131
+ }
132
+ }
133
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "asl:check:ios": "asl-check-plan --scenario scenarios/mobile/{{SCENARIO_ID}}.json --runner runner-manifests/primary-runner.json --provider runner-manifests/evidence-provider.json --platform ios --out artifacts/asl/plan/{{SCENARIO_ID}}-ios",
3
+ "asl:check:android": "asl-check-plan --scenario scenarios/mobile/{{SCENARIO_ID}}.json --runner runner-manifests/primary-runner.json --provider runner-manifests/evidence-provider.json --platform android --out artifacts/asl/plan/{{SCENARIO_ID}}-android",
4
+ "asl:validate": "asl-validate-project --root . --platform all --out artifacts/asl/project-validation",
5
+ "asl:host:doctor": "asl-host-doctor --out artifacts/asl/host-doctor",
6
+ "asl:profile:ios": "asl-profile-ios --config asl.config.json --scenario scenarios/mobile/{{SCENARIO_ID}}.json ${ASL_PROFILE_IOS_EVENTS:+--events $ASL_PROFILE_IOS_EVENTS} --comparison-lane {{SCENARIO_ID}}-ios-fixture --out artifacts/asl/ios --run-id {{SCENARIO_ID}}-ios",
7
+ "asl:profile:android": "asl-profile-android --config asl.config.json --scenario scenarios/mobile/{{SCENARIO_ID}}.json ${ASL_PROFILE_ANDROID_EVENTS:+--events $ASL_PROFILE_ANDROID_EVENTS} --comparison-lane {{SCENARIO_ID}}-android-fixture --out artifacts/asl/android --run-id {{SCENARIO_ID}}-android",
8
+ "asl:profile:ios:provider": "asl-profile-ios --config asl.config.json --scenario scenarios/mobile/{{SCENARIO_ID}}.json ${ASL_PROFILE_IOS_EVENTS:+--events $ASL_PROFILE_IOS_EVENTS} --provider runner-manifests/evidence-provider.json --comparison-lane {{SCENARIO_ID}}-ios-provider --out artifacts/asl/ios --run-id {{SCENARIO_ID}}-ios-provider",
9
+ "asl:profile:android:provider": "asl-profile-android --config asl.config.json --scenario scenarios/mobile/{{SCENARIO_ID}}.json ${ASL_PROFILE_ANDROID_EVENTS:+--events $ASL_PROFILE_ANDROID_EVENTS} --provider runner-manifests/evidence-provider.json --comparison-lane {{SCENARIO_ID}}-android-provider --out artifacts/asl/android --run-id {{SCENARIO_ID}}-android-provider",
10
+ "asl:agent-device:check": "asl-agent-device --check --out artifacts/asl/agent-device-check",
11
+ "asl:agent-device:ios": "asl-agent-device --platform ios ${ASL_IOS_AGENT_DEVICE_SESSION:+--session $ASL_IOS_AGENT_DEVICE_SESSION} ${ASL_IOS_AGENT_DEVICE_SESSION_MODE:+--session-mode $ASL_IOS_AGENT_DEVICE_SESSION_MODE} ${ASL_IOS_UDID:+--udid $ASL_IOS_UDID} ${ASL_AGENT_DEVICE_COMMAND_TIMEOUT_MS:+--command-timeout-ms $ASL_AGENT_DEVICE_COMMAND_TIMEOUT_MS} --scenario scenarios/mobile/{{SCENARIO_ID}}.json --app ${ASL_IOS_APP_ID:-com.example.app} --open --wait-ms 1000 --out artifacts/asl/agent-device-ios --run-id {{SCENARIO_ID}}-ios-agent-device",
12
+ "asl:agent-device:android": "asl-agent-device --platform android ${ASL_ANDROID_AGENT_DEVICE_SESSION:+--session $ASL_ANDROID_AGENT_DEVICE_SESSION} ${ASL_ANDROID_AGENT_DEVICE_SESSION_MODE:+--session-mode $ASL_ANDROID_AGENT_DEVICE_SESSION_MODE} ${ASL_ANDROID_SERIAL:+--serial $ASL_ANDROID_SERIAL} ${ASL_AGENT_DEVICE_COMMAND_TIMEOUT_MS:+--command-timeout-ms $ASL_AGENT_DEVICE_COMMAND_TIMEOUT_MS} --scenario scenarios/mobile/{{SCENARIO_ID}}.json --app ${ASL_ANDROID_APP_ID:-com.example.app} --open --wait-ms 1000 --out artifacts/asl/agent-device-android --run-id {{SCENARIO_ID}}-android-agent-device",
13
+ "asl:argent:check": "asl-argent --check --out artifacts/asl/argent-check",
14
+ "asl:argent:ios": "asl-argent --platform ios ${ASL_ARGENT_BIN:+--argent $ASL_ARGENT_BIN} ${ASL_ARGENT_BASE_ARGS:+--base-args \"$ASL_ARGENT_BASE_ARGS\"} ${ASL_ARGENT_DEVICE_FLAG:+--device-flag $ASL_ARGENT_DEVICE_FLAG} ${ASL_ARGENT_APP_FLAG:+--app-flag $ASL_ARGENT_APP_FLAG} ${ASL_ARGENT_COMMAND_TIMEOUT_MS:+--command-timeout-ms $ASL_ARGENT_COMMAND_TIMEOUT_MS} ${ASL_ARGENT_IOS_SIMCTL_SCREENSHOT_FALLBACK:+--ios-simctl-screenshot-fallback} ${ASL_XCRUN_PATH:+--xcrun $ASL_XCRUN_PATH} --scenario scenarios/mobile/{{SCENARIO_ID}}.json --app ${ASL_IOS_APP_ID:-com.example.app} --device ${ASL_IOS_UDID:-booted} --out artifacts/asl/argent-ios --run-id {{SCENARIO_ID}}-ios-argent",
15
+ "asl:argent:android": "asl-argent --platform android ${ASL_ARGENT_BIN:+--argent $ASL_ARGENT_BIN} ${ASL_ARGENT_BASE_ARGS:+--base-args \"$ASL_ARGENT_BASE_ARGS\"} ${ASL_ARGENT_DEVICE_FLAG:+--device-flag $ASL_ARGENT_DEVICE_FLAG} ${ASL_ARGENT_APP_FLAG:+--app-flag $ASL_ARGENT_APP_FLAG} ${ASL_ARGENT_COMMAND_TIMEOUT_MS:+--command-timeout-ms $ASL_ARGENT_COMMAND_TIMEOUT_MS} --scenario scenarios/mobile/{{SCENARIO_ID}}.json --app ${ASL_ANDROID_APP_ID:-com.example.app} --device ${ASL_ANDROID_SERIAL:-emulator-5554} --out artifacts/asl/argent-android --run-id {{SCENARIO_ID}}-android-argent",
16
+ "asl:android:live": "asl-live-android --config asl.config.json --scenario scenarios/mobile/{{SCENARIO_ID}}.json ${ASL_ANDROID_SERIAL:+--serial $ASL_ANDROID_SERIAL} ${ASL_ANDROID_APP_ID:+--package $ASL_ANDROID_APP_ID} ${ASL_ANDROID_REACT_NATIVE_DEBUG_HOST:+--react-native-debug-host $ASL_ANDROID_REACT_NATIVE_DEBUG_HOST} --out artifacts/asl/android-live --compare-latest --fail-on-regression",
17
+ "asl:android:live:agent-device": "asl-live-android --config asl.config.json --scenario scenarios/mobile/{{SCENARIO_ID}}.json ${ASL_ANDROID_SERIAL:+--serial $ASL_ANDROID_SERIAL} ${ASL_ANDROID_APP_ID:+--package $ASL_ANDROID_APP_ID} ${ASL_ANDROID_REACT_NATIVE_DEBUG_HOST:+--react-native-debug-host $ASL_ANDROID_REACT_NATIVE_DEBUG_HOST} ${ASL_ANDROID_AGENT_DEVICE_SESSION:+--agent-device-session $ASL_ANDROID_AGENT_DEVICE_SESSION} ${ASL_ANDROID_AGENT_DEVICE_SESSION_MODE:+--agent-device-session-mode $ASL_ANDROID_AGENT_DEVICE_SESSION_MODE} --out artifacts/asl/android-live --run-suffix agent-device --agent-device-proof --compare-latest --fail-on-regression",
18
+ "asl:android:live:argent": "asl-live-android --config asl.config.json --scenario scenarios/mobile/{{SCENARIO_ID}}.json ${ASL_ANDROID_SERIAL:+--serial $ASL_ANDROID_SERIAL} ${ASL_ANDROID_APP_ID:+--package $ASL_ANDROID_APP_ID} ${ASL_ANDROID_REACT_NATIVE_DEBUG_HOST:+--react-native-debug-host $ASL_ANDROID_REACT_NATIVE_DEBUG_HOST} --out artifacts/asl/android-live --run-suffix argent --argent-proof --compare-latest --fail-on-regression",
19
+ "asl:android:live:runners": "asl-live-android --config asl.config.json --scenario scenarios/mobile/{{SCENARIO_ID}}.json ${ASL_ANDROID_SERIAL:+--serial $ASL_ANDROID_SERIAL} ${ASL_ANDROID_APP_ID:+--package $ASL_ANDROID_APP_ID} ${ASL_ANDROID_REACT_NATIVE_DEBUG_HOST:+--react-native-debug-host $ASL_ANDROID_REACT_NATIVE_DEBUG_HOST} ${ASL_ANDROID_AGENT_DEVICE_SESSION:+--agent-device-session $ASL_ANDROID_AGENT_DEVICE_SESSION} ${ASL_ANDROID_AGENT_DEVICE_SESSION_MODE:+--agent-device-session-mode $ASL_ANDROID_AGENT_DEVICE_SESSION_MODE} --out artifacts/asl/android-live --run-suffix runners --agent-device-proof --argent-proof --compare-latest --fail-on-regression",
20
+ "asl:ios:live": "asl-live-ios --config asl.config.json --scenario scenarios/mobile/{{SCENARIO_ID}}.json ${ASL_IOS_UDID:+--device $ASL_IOS_UDID} ${ASL_IOS_APP_ID:+--bundle $ASL_IOS_APP_ID} --out artifacts/asl/ios-live --compare-latest --fail-on-regression",
21
+ "asl:ios:live:agent-device": "asl-live-ios --config asl.config.json --scenario scenarios/mobile/{{SCENARIO_ID}}.json ${ASL_IOS_UDID:+--device $ASL_IOS_UDID} ${ASL_IOS_APP_ID:+--bundle $ASL_IOS_APP_ID} ${ASL_IOS_AGENT_DEVICE_SESSION:+--agent-device-session $ASL_IOS_AGENT_DEVICE_SESSION} ${ASL_IOS_AGENT_DEVICE_SESSION_MODE:+--agent-device-session-mode $ASL_IOS_AGENT_DEVICE_SESSION_MODE} --out artifacts/asl/ios-live --run-suffix agent-device --agent-device-proof --compare-latest --fail-on-regression",
22
+ "asl:ios:live:argent": "asl-live-ios --config asl.config.json --scenario scenarios/mobile/{{SCENARIO_ID}}.json ${ASL_IOS_UDID:+--device $ASL_IOS_UDID} ${ASL_IOS_APP_ID:+--bundle $ASL_IOS_APP_ID} --out artifacts/asl/ios-live --run-suffix argent --argent-proof --compare-latest --fail-on-regression",
23
+ "asl:ios:live:runners": "asl-live-ios --config asl.config.json --scenario scenarios/mobile/{{SCENARIO_ID}}.json ${ASL_IOS_UDID:+--device $ASL_IOS_UDID} ${ASL_IOS_APP_ID:+--bundle $ASL_IOS_APP_ID} ${ASL_IOS_AGENT_DEVICE_SESSION:+--agent-device-session $ASL_IOS_AGENT_DEVICE_SESSION} ${ASL_IOS_AGENT_DEVICE_SESSION_MODE:+--agent-device-session-mode $ASL_IOS_AGENT_DEVICE_SESSION_MODE} --out artifacts/asl/ios-live --run-suffix runners --agent-device-proof --argent-proof --compare-latest --fail-on-regression",
24
+ "asl:profile:ios:live": "asl-profile-ios --config asl.config.json --scenario scenarios/mobile/{{SCENARIO_ID}}.json --simctl-capture --profile-session --profile-session-storage --launch --comparison-lane {{SCENARIO_ID}}-ios-live --out artifacts/asl/ios --run-id {{SCENARIO_ID}}-ios-live",
25
+ "asl:profile:android:live": "asl-profile-android --config asl.config.json --scenario scenarios/mobile/{{SCENARIO_ID}}.json --adb-capture --profile-session --clear-logcat --launch --comparison-lane {{SCENARIO_ID}}-android-live --out artifacts/asl/android --run-id {{SCENARIO_ID}}-android-live",
26
+ "asl:compare:ios": "asl-compare-latest --root artifacts/asl/ios --scenario {{SCENARIO_ID}} --current ${ASL_COMPARE_IOS_CURRENT:?set_ASL_COMPARE_IOS_CURRENT} --out artifacts/asl/ios/comparisons/{{SCENARIO_ID}} --fail-on-regression",
27
+ "asl:compare:android": "asl-compare-latest --root artifacts/asl/android --scenario {{SCENARIO_ID}} --current ${ASL_COMPARE_ANDROID_CURRENT:?set_ASL_COMPARE_ANDROID_CURRENT} --out artifacts/asl/android/comparisons/{{SCENARIO_ID}} --fail-on-regression",
28
+ "asl:live-proof:android": "asl-live-proof --file ${ASL_ANDROID_LIVE_PROOF:-artifacts/asl/android-live/_live-proof/android-live-proof/live-proof.json} --fail-on-regression",
29
+ "asl:live-proof:ios": "asl-live-proof --file ${ASL_IOS_LIVE_PROOF:-artifacts/asl/ios-live/_live-proof/ios-live-proof/live-proof.json} --fail-on-regression",
30
+ "asl:live-proof:both": "asl-live-proof --file ${ASL_ANDROID_LIVE_PROOF:-artifacts/asl/android-live/_live-proof/android-live-proof/live-proof.json} --file ${ASL_IOS_LIVE_PROOF:-artifacts/asl/ios-live/_live-proof/ios-live-proof/live-proof.json} --require-platforms android,ios --out artifacts/asl/live-proof-set --fail-on-regression ${ASL_REQUIRE_LIVE_PROOF_ARTIFACTS:+--require-artifacts}",
31
+ "asl:live-proof": "asl-live-proof --file ${ASL_LIVE_PROOF:?set_ASL_LIVE_PROOF} --fail-on-regression"
32
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "runnerId": "example-mobile-runner",
4
+ "kind": "primary",
5
+ "platforms": ["ios", "android"],
6
+ "capabilities": ["launch", "sessionControl", "command", "logCapture", "artifactWrite"],
7
+ "driverActions": ["tap", "readLogs"],
8
+ "artifactOutputs": ["logs", "signals"],
9
+ "lifecycle": [
10
+ "prepare",
11
+ "launch",
12
+ "startSession",
13
+ "executeStep",
14
+ "waitForTruthEvent",
15
+ "captureEvidence",
16
+ "stopSession",
17
+ "finalize"
18
+ ]
19
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "projectName": "replace-me",
3
+ "app": {
4
+ "displayName": "Example App",
5
+ "scheme": "example-app",
6
+ "profileSessionScheme": "example-app",
7
+ "iosBundleId": "com.example.app",
8
+ "iosConflictingBundleIds": [],
9
+ "androidPackage": "com.example.app",
10
+ "ios": {
11
+ "xcodeScheme": "Example App",
12
+ "launchConfiguration": "Debug",
13
+ "profileConfiguration": "Release"
14
+ }
15
+ },
16
+ "paths": {
17
+ "artifactRoot": "artifacts/asl",
18
+ "iosArtifactsRoot": "artifacts/asl/ios",
19
+ "androidArtifactsRoot": "artifacts/asl/android",
20
+ "scenarioRoot": "scenarios/mobile"
21
+ },
22
+ "drivers": {
23
+ "default": "fixture-log-ingest",
24
+ "supported": ["fixture-log-ingest", "adb", "ios-simctl", "agent-device", "argent", "xcodebuildmcp"]
25
+ },
26
+ "versionControl": {
27
+ "commit": [
28
+ "scenarios/**",
29
+ "runner-manifests/**",
30
+ "docs/**"
31
+ ],
32
+ "ignore": [
33
+ "artifacts/asl/**",
34
+ "raw simulator recordings, screenshots, and ephemeral profiler exports"
35
+ ]
36
+ }
37
+ }
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+
6
+ /**
7
+ * Parses simple `--key value` CLI arguments.
8
+ *
9
+ * @param {string[]} argv
10
+ * @returns {Record<string, string | boolean>}
11
+ */
12
+ function parseArgs(argv) {
13
+ const args = {};
14
+ for (let index = 0; index < argv.length; index += 1) {
15
+ const token = argv[index];
16
+ if (!token?.startsWith('--')) {
17
+ continue;
18
+ }
19
+
20
+ const key = token.slice(2);
21
+ const value = argv[index + 1];
22
+ if (value && !value.startsWith('--')) {
23
+ args[key] = value;
24
+ index += 1;
25
+ } else {
26
+ args[key] = true;
27
+ }
28
+ }
29
+ return args;
30
+ }
31
+
32
+ /**
33
+ * Reads a required string CLI argument.
34
+ *
35
+ * @param {Record<string, string | boolean>} args
36
+ * @param {string} key
37
+ * @returns {string}
38
+ */
39
+ function requireStringArg(args, key) {
40
+ const value = args[key];
41
+ if (typeof value !== 'string' || value.length === 0) {
42
+ throw new Error(`Missing required --${key} value.`);
43
+ }
44
+ return value;
45
+ }
46
+
47
+ /**
48
+ * Writes a formatted JSON artifact.
49
+ *
50
+ * @param {string} outPath
51
+ * @param {Record<string, unknown>} payload
52
+ * @returns {void}
53
+ */
54
+ function writeJsonArtifact(outPath, payload) {
55
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
56
+ fs.writeFileSync(outPath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
57
+ }
58
+
59
+ /**
60
+ * Writes deterministic accessibility evidence for a scaffolded provider command.
61
+ *
62
+ * @param {{outPath: string, platform: string, runId: string, scenarioId: string}} options
63
+ * @returns {void}
64
+ */
65
+ function writeAccessibilityEvidence({
66
+ outPath,
67
+ platform,
68
+ runId,
69
+ scenarioId,
70
+ }) {
71
+ writeJsonArtifact(outPath, {
72
+ checks: [
73
+ {
74
+ id: 'scenario-entrypoint-visible',
75
+ selector: {
76
+ kind: 'testId',
77
+ value: 'asl-example-title',
78
+ },
79
+ status: 'passed',
80
+ },
81
+ ],
82
+ platform,
83
+ providerId: 'example-evidence-provider',
84
+ runId,
85
+ scenarioId,
86
+ schemaVersion: '1.0.0',
87
+ summary: 'Replace this scaffold accessibility evidence with project-local axe, inspector, or driver output.',
88
+ violations: [],
89
+ });
90
+ }
91
+
92
+ /**
93
+ * Runs the provider script.
94
+ *
95
+ * @returns {void}
96
+ */
97
+ function main() {
98
+ const args = parseArgs(process.argv.slice(2));
99
+ writeAccessibilityEvidence({
100
+ outPath: requireStringArg(args, 'out'),
101
+ platform: requireStringArg(args, 'platform'),
102
+ runId: requireStringArg(args, 'run-id'),
103
+ scenarioId: requireStringArg(args, 'scenario'),
104
+ });
105
+ }
106
+
107
+ try {
108
+ main();
109
+ } catch (error) {
110
+ console.error(error instanceof Error ? error.message : String(error));
111
+ process.exitCode = 1;
112
+ }
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+
6
+ /**
7
+ * Parses simple `--key value` CLI arguments.
8
+ *
9
+ * @param {string[]} argv
10
+ * @returns {Record<string, string | boolean>}
11
+ */
12
+ function parseArgs(argv) {
13
+ const args = {};
14
+ for (let index = 0; index < argv.length; index += 1) {
15
+ const token = argv[index];
16
+ if (!token?.startsWith('--')) {
17
+ continue;
18
+ }
19
+
20
+ const key = token.slice(2);
21
+ const value = argv[index + 1];
22
+ if (value && !value.startsWith('--')) {
23
+ args[key] = value;
24
+ index += 1;
25
+ } else {
26
+ args[key] = true;
27
+ }
28
+ }
29
+ return args;
30
+ }
31
+
32
+ /**
33
+ * Reads a required string CLI argument.
34
+ *
35
+ * @param {Record<string, string | boolean>} args
36
+ * @param {string} key
37
+ * @returns {string}
38
+ */
39
+ function requireStringArg(args, key) {
40
+ const value = args[key];
41
+ if (typeof value !== 'string' || value.length === 0) {
42
+ throw new Error(`Missing required --${key} value.`);
43
+ }
44
+ return value;
45
+ }
46
+
47
+ /**
48
+ * Writes a formatted JSON artifact.
49
+ *
50
+ * @param {string} outPath
51
+ * @param {Record<string, unknown>} payload
52
+ * @returns {void}
53
+ */
54
+ function writeJsonArtifact(outPath, payload) {
55
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
56
+ fs.writeFileSync(outPath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
57
+ }
58
+
59
+ /**
60
+ * Writes deterministic evidence for a scaffolded provider command.
61
+ *
62
+ * @param {{memoryOutPath: string, networkOutPath: string, outPath: string, platform: string, runId: string, scenarioId: string}} options
63
+ * @returns {void}
64
+ */
65
+ function writeProviderEvidence({
66
+ memoryOutPath,
67
+ networkOutPath,
68
+ outPath,
69
+ platform,
70
+ runId,
71
+ scenarioId,
72
+ }) {
73
+ const shared = {
74
+ platform,
75
+ providerId: 'example-evidence-provider',
76
+ runId,
77
+ scenarioId,
78
+ schemaVersion: '1.0.0',
79
+ };
80
+
81
+ writeJsonArtifact(outPath, {
82
+ ...shared,
83
+ samples: [],
84
+ summary: 'Replace this scaffold provider with a project-local profiler command when real profiler data is available.',
85
+ });
86
+ writeJsonArtifact(memoryOutPath, {
87
+ ...shared,
88
+ jsHeapBytes: null,
89
+ nativeHeapBytes: null,
90
+ summary: 'Replace this scaffold memory signal with device or runtime memory evidence.',
91
+ });
92
+ writeJsonArtifact(networkOutPath, {
93
+ log: {
94
+ creator: {
95
+ name: 'agent-scenario-loop scaffold provider',
96
+ version: '1.0.0',
97
+ },
98
+ entries: [],
99
+ version: '1.2',
100
+ },
101
+ ...shared,
102
+ });
103
+ }
104
+
105
+ /**
106
+ * Runs the provider script.
107
+ *
108
+ * @returns {void}
109
+ */
110
+ function main() {
111
+ const args = parseArgs(process.argv.slice(2));
112
+ writeProviderEvidence({
113
+ memoryOutPath: requireStringArg(args, 'memory-out'),
114
+ networkOutPath: requireStringArg(args, 'network-out'),
115
+ outPath: requireStringArg(args, 'out'),
116
+ platform: requireStringArg(args, 'platform'),
117
+ runId: requireStringArg(args, 'run-id'),
118
+ scenarioId: requireStringArg(args, 'scenario'),
119
+ });
120
+ }
121
+
122
+ try {
123
+ main();
124
+ } catch (error) {
125
+ console.error(error instanceof Error ? error.message : String(error));
126
+ process.exitCode = 1;
127
+ }