stim 1.19.0 → 1.20.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 (172) hide show
  1. package/README.md +20 -3
  2. package/dist/{activity-BSw0x84R.mjs → activity-BrGh9EAI.mjs} +3 -3
  3. package/dist/{agent-device-usage-output-Bjxu_VmX.mjs → agent-device-usage-output-DVyv_hWj.mjs} +7 -7
  4. package/dist/android-0vv-OruW.mjs +6 -0
  5. package/dist/android-Brx6EKvJ.mjs +1258 -0
  6. package/dist/{android-BdQC6zs1.mjs → android-BsYNR81W.mjs} +103 -45
  7. package/dist/{android-DE5g_5gv.mjs → android-CK3fpEyZ.mjs} +2 -2
  8. package/dist/{android-cas-C87geAg-.mjs → android-cas-DCAuXryb.mjs} +9 -5
  9. package/dist/android-cas-compiler.mjs +1 -1
  10. package/dist/api-run.mjs +159 -8
  11. package/dist/{api-DIf_s86s.mjs → api-zuX3ynYN.mjs} +5 -1
  12. package/dist/api.d.mts +98 -4
  13. package/dist/api.mjs +1 -1
  14. package/dist/{app-install-CFI4ptUL.mjs → app-install-0LgaQRT7.mjs} +9 -8
  15. package/dist/artifact-CnC0mfov.mjs +807 -0
  16. package/dist/artifact-DClXJNWx.mjs +772 -0
  17. package/dist/artifact-lifecycle-DzPpBYvK.mjs +30 -0
  18. package/dist/{browser-web-CDhqQxhv.mjs → browser-web-CzBmhmgs.mjs} +19 -19
  19. package/dist/{budget-Dz-kyXLv.mjs → budget-BONq9vXY.mjs} +3101 -3062
  20. package/dist/build-BixRI6sB.mjs +462 -0
  21. package/dist/{build-facts-B87vo-Im.d.mts → build-facts-DcLv2HSd.d.mts} +1 -32
  22. package/dist/build-plan-CP0tckNU.mjs +553 -0
  23. package/dist/{cache-manifest-DiwKSyv9.mjs → cache-manifest-qA3dT2x2.mjs} +1 -1
  24. package/dist/cache-manifest.mjs +1 -1
  25. package/dist/{manifest-CX0d1IkZ.mjs → ccache-CjHQ5F8g.mjs} +13 -19
  26. package/dist/{chrome-BTUOo8p2.mjs → chrome-Dp2lMLBe.mjs} +1 -1
  27. package/dist/{cli-C9Zvgbdc.mjs → cli-B2R8wH1F.mjs} +20 -20
  28. package/dist/cli.mjs +1 -1
  29. package/dist/{client-CuUo8Nvg.mjs → client-BLy1Pw2V.mjs} +89 -307
  30. package/dist/collector-run.d.mts +4 -0
  31. package/dist/collector-run.mjs +7 -7
  32. package/dist/{command-output-Bt8ygHE7.mjs → command-output-BjPN99ZB.mjs} +2 -2
  33. package/dist/{config-DC5jEtd1.mjs → config-D0LhTY3S.mjs} +8 -12
  34. package/dist/{created-devices-D0s26Hx5.mjs → created-devices-HU_DAb_Q.mjs} +7 -6
  35. package/dist/{dependency-state-DKaS1OM9.mjs → dependency-state-B2m8eqsS.mjs} +2 -2
  36. package/dist/{detached-entry-pB_IPfV4.mjs → detached-entry-BXq4fIl-.mjs} +1 -1
  37. package/dist/{dev-client-CDY_zAmD.mjs → dev-client-ClATaJoH.mjs} +2 -2
  38. package/dist/{device-BHw0WlHl.mjs → device-B7lsC0K1.mjs} +8 -8
  39. package/dist/device-DVCwAFlX.mjs +727 -0
  40. package/dist/{device-capacity-BoN1jQkO.mjs → device-capacity-Ca_ke1LE.mjs} +13 -14
  41. package/dist/device-host-worker.mjs +38 -18
  42. package/dist/device-ios-CRUqPxKw.mjs +413 -0
  43. package/dist/{device-lease-Cu7PtQpZ.mjs → device-lease-DHOBXGgS.mjs} +135 -22
  44. package/dist/{device-lease-run-D38SnZtt.mjs → device-lease-run-CvDeHTkG.mjs} +4 -4
  45. package/dist/{device-pool-CP4rAsHx.mjs → device-pool-L2-5GxMy.mjs} +3 -3
  46. package/dist/{device-remote-B5GiUifk.mjs → device-remote-Bmg42063.mjs} +17 -68
  47. package/dist/doctor-C2MSuMMg.mjs +515 -0
  48. package/dist/{doctor-PRJH94v6.mjs → doctor-DPAqhVCK.mjs} +333 -744
  49. package/dist/{doctor-watchman-wZhyTdp4.mjs → doctor-watchman-CB4NnVZm.mjs} +13 -12
  50. package/dist/{simslim-D9LkJxIO.mjs → eas-build-Dw_ec2Km.mjs} +6 -152
  51. package/dist/{stim-installations-DbmUjsLv.mjs → eas-session-ledger-DZlC-Fq3.mjs} +57 -4
  52. package/dist/{error-diagnostics--v5HGOU0.mjs → error-diagnostics-Du7I_GuY.mjs} +17 -12
  53. package/dist/errors-DzkMYh8Q.mjs +205 -0
  54. package/dist/{exec-i8GDPwq9.mjs → exec-B81Qodjn.mjs} +40 -10
  55. package/dist/{gc-CyHMcKcn.mjs → gc-PWp34zGb.mjs} +21 -20
  56. package/dist/{device-ios-B2ruohE-.mjs → gradle-C2OEKCCI.mjs} +21 -650
  57. package/dist/{guide-dRaQJJk9.mjs → guide-Dai9lphv.mjs} +504 -117
  58. package/dist/{hosted-android-Bs7fytj7.mjs → hosted-android-DQ-Agsd8.mjs} +5 -5
  59. package/dist/{hosted-client-C5G4h_OJ.mjs → hosted-client-Io01s5By.mjs} +9 -3
  60. package/dist/{hosted-ios-Bik035eL.mjs → hosted-ios-DIxFnYp-.mjs} +3 -3
  61. package/dist/{hosted-logs-CI9Az9a4.mjs → hosted-logs-DP-ABU_M.mjs} +8 -8
  62. package/dist/{hosted-macos-DABm3ScO.mjs → hosted-macos-Aziw9T_9.mjs} +7 -6
  63. package/dist/{hosted-native-DbV2aUVw.mjs → hosted-native-BdAQi21M.mjs} +17 -14
  64. package/dist/{idle-shutdown-DYiMBn34.mjs → idle-shutdown-DdMqoSUB.mjs} +7 -7
  65. package/dist/{ios-DYUr9LGc.mjs → ios-BZ9dg2Fr.mjs} +8 -7
  66. package/dist/{ios-BWbHFZVz.mjs → ios-CXLIUxXJ.mjs} +180 -24
  67. package/dist/ios-CZuv6gRv.mjs +1112 -0
  68. package/dist/{ios-DCrkgXEM.mjs → ios-VGvNdsv2.mjs} +1 -1
  69. package/dist/{ios-device-CrW8aMb5.mjs → ios-device-B1B14xND.mjs} +1 -1
  70. package/dist/{ios-device-CJ_ZOGky.mjs → ios-device-BreZOpM0.mjs} +3 -3
  71. package/dist/ios-project-B9drt_aY.mjs +197 -0
  72. package/dist/{ios-state-By0XuLTQ.mjs → ios-state-CjrL2qv-.mjs} +1 -1
  73. package/dist/js-swap-C8icNYIY.mjs +432 -0
  74. package/dist/launch-DxH0THLP.mjs +745 -0
  75. package/dist/{react-native-ios-CSpIxexZ.mjs → launch-YrbqaIFj.mjs} +59 -612
  76. package/dist/{launch-verify-DCveSE6d.mjs → launch-verify-BzRdbuO1.mjs} +26 -7
  77. package/dist/{logs-Bao1HOMy.mjs → logs-DHaRNfg7.mjs} +11 -11
  78. package/dist/macos-C0iljhsN.mjs +2 -0
  79. package/dist/{macos-ciyBdTY5.mjs → macos-V_3Ui7g4.mjs} +42 -50
  80. package/dist/macos-run.mjs +1 -1
  81. package/dist/maintenance-run.mjs +13 -10
  82. package/dist/{metro-DI2AjPF_.mjs → metro-D_j9sN-B.mjs} +18 -5
  83. package/dist/{metro-gateway-BxpJLIwh.mjs → metro-gateway-BBJldOdi.mjs} +2 -2
  84. package/dist/miss-reason-BPAzZS0r.mjs +238 -0
  85. package/dist/{named-ports-B4iWH4E-.mjs → named-ports-C2lvoPsb.mjs} +3 -3
  86. package/dist/native-android-lHzaew7r.mjs +226 -0
  87. package/dist/native-gradle-inputs-BQnIdAlL.mjs +356 -0
  88. package/dist/{native-runtime-BGxsbfny.mjs → native-runtime-BRmWL7ru.mjs} +9 -8
  89. package/dist/native-xcode-inputs-eutS19jU.mjs +307 -0
  90. package/dist/native-xcode-ios-DP3cHLwu.mjs +434 -0
  91. package/dist/{ndjson-LGZAMsqa.mjs → ndjson-GOnbyvso.mjs} +6 -6
  92. package/dist/offload-worker.d.mts +41 -7
  93. package/dist/offload-worker.mjs +409 -74
  94. package/dist/{ownership-EB0ySdGK.mjs → ownership-DXzMMSx6.mjs} +2 -2
  95. package/dist/{placement-log-Dxe34rJm.mjs → placement-log-reFbjqEr.mjs} +1 -1
  96. package/dist/plan-BDk2AeY_.mjs +260 -0
  97. package/dist/plan-placement-D_YYlE6N.mjs +73 -0
  98. package/dist/{ports-ByPc0_97.mjs → ports-jzvAJXXL.mjs} +3 -3
  99. package/dist/{prebuild-BKJ3wJI1.mjs → prebuild-Bw44QxVh.mjs} +4 -4
  100. package/dist/{preview-BbSottmO.mjs → preview-BaBJ72Al.mjs} +50 -32
  101. package/dist/project-B8_duAzW.mjs +1067 -0
  102. package/dist/project-plan-BPwbLwQu.mjs +10 -0
  103. package/dist/{pull-request-IHNdMnG8.mjs → pull-request-vOhLNmIb.mjs} +1 -1
  104. package/dist/pull-requests.mjs +1 -1
  105. package/dist/react-native-android-chm1DMlA.mjs +921 -0
  106. package/dist/react-native-build-oAw6gQeW.mjs +239 -0
  107. package/dist/react-native-doctor-DYXoh-6I.mjs +78 -0
  108. package/dist/react-native-ios-SCTK_CT5.mjs +595 -0
  109. package/dist/{recordings-Bv8McZN4.mjs → recordings-Bk-VivUV.mjs} +1 -1
  110. package/dist/{reload-rAPdqeRR.mjs → reload-D34GkY78.mjs} +17 -13
  111. package/dist/remote-BU9UyhCJ.mjs +192 -0
  112. package/dist/{remote-cache-N3hAEaAL.mjs → remote-cache-Bsts6f6Y.mjs} +5 -5
  113. package/dist/result-BDoRk1k3.mjs +186 -0
  114. package/dist/{run-lAkle1-c.mjs → run-qvff78hv.mjs} +3 -3
  115. package/dist/{server-bare-CkIjnegq.mjs → server-bare-dWjAarki.mjs} +5 -5
  116. package/dist/server-command-7XIKlWtO.mjs +69 -0
  117. package/dist/{server-expo-n3tUi3Em.mjs → server-expo-CsYu2aKL.mjs} +107 -82
  118. package/dist/server-expo-DV43vep4.mjs +2 -0
  119. package/dist/{settings-CxgngUZk.mjs → settings-Bbztm4oV.mjs} +179 -129
  120. package/dist/{settings-2am7TSxp.mjs → settings-CT3E71-i.mjs} +6 -6
  121. package/dist/{state-lKh8shBp.d.mts → settings-vk3mJGSm.d.mts} +4 -2
  122. package/dist/settings.schema.json +84 -0
  123. package/dist/simslim-ChMfe8rg.mjs +149 -0
  124. package/dist/{slot-launch-HV_KtPIw.mjs → slot-launch-B9hn96wd.mjs} +6 -6
  125. package/dist/{stage-BgaZJVZY.mjs → stage-BNKKdDNc.mjs} +6 -3
  126. package/dist/start-CbQCSnXQ.mjs +2 -0
  127. package/dist/{start-DtxthMfI.mjs → start-CdvcTaXr.mjs} +97 -68
  128. package/dist/{state-Tp4uImDS.mjs → state-Boo0IMJq.mjs} +1 -1
  129. package/dist/{state-D2KtnALG.mjs → state-DI84A-uu.mjs} +29 -10
  130. package/dist/{stats-8EJ5ivFT.mjs → stats-BIIDrjA8.mjs} +5 -5
  131. package/dist/{status-BEeL6IKz.mjs → status-BDGHwkCs.mjs} +3 -1
  132. package/dist/{status-BOyp0CUl.mjs → status-DARMkc9q.mjs} +81 -293
  133. package/dist/status-watch-DvmI3SmN.mjs +296 -0
  134. package/dist/{stim-desktop-C65jk_q2.mjs → stim-desktop-8GQX2OV8.mjs} +1 -1
  135. package/dist/{stop-DRipAg-e.mjs → stop-CCZS-C9m.mjs} +2 -2
  136. package/dist/{stop-D0UIpZuh.mjs → stop-WQPQHLuy.mjs} +5 -5
  137. package/dist/{stop-zDlknFoz.mjs → stop-iNemHsJC.mjs} +21 -20
  138. package/dist/supervisor-run.d.mts +7 -2
  139. package/dist/supervisor-run.mjs +32 -20
  140. package/dist/{support-DsNaFg6D.mjs → support-CnE_SIjt.mjs} +17 -7
  141. package/dist/{support-CNqYkV0y.mjs → support-DCco5fut.mjs} +61 -8
  142. package/dist/{swiftpm-macos-aJzPAj1t.mjs → swiftpm-macos-afeNUExW.mjs} +5 -5
  143. package/dist/tailnet-CoJUb2tB.mjs +265 -0
  144. package/dist/toolchain-BIupIro1.mjs +2 -0
  145. package/dist/{toolchain-C3xSoyUl.mjs → toolchain-DqyDXs30.mjs} +206 -53
  146. package/dist/{trigger-CAhmG-4s.mjs → trigger-C0MWBi8O.mjs} +2 -2
  147. package/dist/trigger-SHLOWJ4O.mjs +2 -0
  148. package/dist/{warm-progress-pFLB2e6P.mjs → warm-progress-C3ASD6z5.mjs} +6 -6
  149. package/dist/{watchman-BL1IdOtq.mjs → watchman-CQl51YZu.mjs} +6 -6
  150. package/dist/{web-DbbrPUn0.mjs → web-B3eldHFH.mjs} +9 -13
  151. package/dist/web-BLXpOe6b.mjs +2 -0
  152. package/dist/web-run.mjs +10 -10
  153. package/dist/{workspace-state-CR9X86xJ.mjs → workspace-state-CSIFuuDa.mjs} +33 -33
  154. package/dist/{workspaces-Djb2gfi9.mjs → workspaces-D2xSHBDY.mjs} +1260 -1258
  155. package/dist/{worktree-BL4M1rhB.mjs → worktree-BxDqaTev.mjs} +84 -48
  156. package/dist/worktree-q7_OZNY0.mjs +2 -0
  157. package/package.json +8 -6
  158. package/shim/native-android.gradle +101 -0
  159. package/dist/android-BXc6BmEi.mjs +0 -3732
  160. package/dist/android-BvV1gGdN.mjs +0 -4
  161. package/dist/ios-ITYno9rO.mjs +0 -2241
  162. package/dist/macos-nIOATJmR.mjs +0 -2
  163. package/dist/metro-store-BAHFcJm6.mjs +0 -106
  164. package/dist/miss-reason-CjQCOKqq.mjs +0 -1743
  165. package/dist/plan-placement-BVY59qa8.mjs +0 -290
  166. package/dist/project-B5eQ1USs.mjs +0 -612
  167. package/dist/react-native-ios-BMh2i25I.mjs +0 -2
  168. package/dist/server-expo-DGsdUGX2.mjs +0 -2
  169. package/dist/start-D8NbxNBb.mjs +0 -2
  170. package/dist/trigger-CoalsMTV.mjs +0 -2
  171. package/dist/web-DFpFeZu5.mjs +0 -2
  172. package/dist/worktree-DV-A0LNL.mjs +0 -2
@@ -1,7 +1,8 @@
1
- import { r as findProjectRoot } from "./project-B5eQ1USs.mjs";
2
- import { t as ANDROID_AVD_CONFIG_HELP } from "./settings-CxgngUZk.mjs";
3
- import { s as RECENT_LAUNCH_MS } from "./watchman-BL1IdOtq.mjs";
4
- import { o as guideStatus, t as WATCHMAN_NESTED_WORKTREES } from "./doctor-watchman-wZhyTdp4.mjs";
1
+ import { t as ANDROID_AVD_CONFIG_HELP } from "./settings-Bbztm4oV.mjs";
2
+ import { r as findProjectRoot } from "./project-B8_duAzW.mjs";
3
+ import { s as RECENT_LAUNCH_MS } from "./watchman-CQl51YZu.mjs";
4
+ import { o as guideStatus, t as WATCHMAN_NESTED_WORKTREES } from "./doctor-watchman-CB4NnVZm.mjs";
5
+ import "./status-watch-DvmI3SmN.mjs";
5
6
  import { SETTINGS, SETTINGS_SCHEMA_URL, TUTORIAL_VERSION } from "@stim-cli/core/state";
6
7
  import chalk from "chalk";
7
8
  //#region src/guide/agent.ts
@@ -14,6 +15,9 @@ device with another workspace. Prefer plain output: it streams each phase and
14
15
  ends with the facts the next step needs. Use --json only when a script must
15
16
  parse a stable payload.
16
17
 
18
+ For a native Xcode or Gradle app without React Native or Expo, read guide
19
+ lifecycle native-ios or guide lifecycle native-android before running it.
20
+
17
21
  TWO WORKFLOWS
18
22
 
19
23
  WORKTREE is the default. Take SINGLE CHECKOUT only when the user asks to work in
@@ -91,7 +95,10 @@ the printed remedy and keep concurrent runs on the same STIM_HOME.
91
95
  Before native work, run doctor for the platform in scope when the STATUS block
92
96
  at the top of this topic says it is due. No block means doctor is current and
93
97
  Stim is up to date. In the worktree workflow, run it from the linked worktree
94
- so it also checks the source checkout. Follow each finding's printed remedy,
98
+ so it also checks the source checkout. Doctor never loads fingerprint.config.js
99
+ or runs an eas from the project tree; its fingerprint checks can still run
100
+ installed Expo packages, including hoisted ones, as a build does. warm,
101
+ start, ios and android run project code. Follow each finding's printed remedy,
95
102
  and read the routed topic below before acting on one you do not understand. If
96
103
  the stim resolved from PATH is older than another installation, fix PATH or the
97
104
  installation before continuing so commands and guidance match. Under host
@@ -405,7 +412,7 @@ FULL TOPIC LIST
405
412
  //#endregion
406
413
  //#region src/guide/api.ts
407
414
  var api_default = {
408
- summary: "Typed lifecycle API: createStim, run, stop, diagnostics, and cancellation",
415
+ summary: "Typed lifecycle API: createStim, build, run, stop, diagnostics, and cancellation",
409
416
  body: () => `PROGRAMMATIC API
410
417
 
411
418
  Install stim as a project dependency: npm install --save-dev stim.
@@ -435,8 +442,27 @@ run takes platform plus its options:
435
442
  macos: remoteBuild
436
443
  web: headed (default false)
437
444
  iOS and Android also accept slot, metroCheck, buildCache, and remoteBuild.
438
- All methods accept signal. run builds, installs and launches; build-only is
439
- not available. Web requires a running server, just like stim web.
445
+ All methods accept signal. run builds, installs and launches.
446
+ Web requires a running server, just like stim web.
447
+
448
+ build produces an artifact without acquiring a device, starting a runtime, or
449
+ stopping an existing session. Its result is inferred from platform:
450
+ const result = await stim.build({ platform: 'ios' });
451
+ console.log(result.facts.appPath);
452
+
453
+ Build options:
454
+ ios: scheme, configuration, arch (arm64, x86_64, all), buildCache, remoteBuild
455
+ android: variant, abi (arm64-v8a, armeabi-v7a, x86, x86_64, all), buildCache, remoteBuild
456
+ macos: remoteBuild
457
+ An iOS build targets the simulator. A Debug build defaults to the host
458
+ architecture (Android only while optimizations.android.targetAbiOnly is on, the
459
+ default); Release defaults to all. macOS builds the configured Debug SwiftPM app.
460
+ Build results retain an owned appPath (iOS), apkPath (Android) or bundle (macOS)
461
+ inside this workspace until worktree removal. Later builds keep earlier copies.
462
+ A build leaves a running app's last build record and build log untouched; it
463
+ writes its own build-artifact-<platform>.ndjson. No distribution archive or web build
464
+ pipeline is inferred. Use @stim-cli/ci's buildCI to export a portable artifact and
465
+ diagnostics for your CI provider.
440
466
 
441
467
  run returns { platform, facts }. iOS facts include udid; Android includes
442
468
  serial; both include bundleId, appPath, metroPort, cacheHit and launched.
@@ -518,7 +544,9 @@ worktree, carries git:
518
544
  null when git fails or does not answer within 3 s, or the
519
545
  worktree has no environment and sits in ~/Desktop, ~/Documents,
520
546
  ~/Downloads, iCloud Drive, ~/Library/CloudStorage or /Volumes,
521
- which status does not open on macOS
547
+ which status does not open on macOS; one-shot status also
548
+ leaves it null for a worktree with no live environment and
549
+ for every unprovisioned worktree
522
550
  changed tracked paths with staged or unstaged changes, conflicts included
523
551
  untracked untracked entries as git status lists them; a new directory
524
552
  counts once
@@ -528,16 +556,17 @@ worktree, carries git:
528
556
  mergedInto "origin/<default>" when gc would call the branch merged, judged
529
557
  from local refs without fetching, else null
530
558
 
531
- status runs \`git status --porcelain=v2 --branch\` in every worktree in
532
- parallel, and caches the merge verdict by HEAD and default-branch commit
559
+ status runs \`git status --porcelain=v2 --branch\` in the worktrees it reads,
560
+ six git calls at a time, and caches the merge verdict by HEAD and default-branch commit
533
561
  under $STIM_HOME/git-merge. One call starts no new merge check 250 ms after
534
562
  its first; later calls judge the rest, and until then a verdict for the same
535
563
  HEAD at an older default-branch commit stands in; a check that timed out
536
564
  is retried after 5 minutes. \`status --watch\` reuses a worktree's git read
537
565
  until its index, HEAD, reflog or the branch, upstream or default-branch refs
538
566
  change, and for at most 60 s, so a file edit, creation or deletion that is
539
- not staged can take up to a minute to show. Plain status prints "git: 2 changed, 1 untracked, ahead 3" under
540
- each environment, and the same after each worktree with no environment.
567
+ not staged can take up to a minute to show. One-shot status reads git only
568
+ for live environments; \`status --watch\` reads every worktree. Plain status prints "git: 2 changed, 1 untracked, ahead 3" under
569
+ each environment it read, and the same after each worktree with no environment under \`--watch\`.
541
570
 
542
571
  The paired phone's Work sheet can open changed and untracked files when the
543
572
  server advertises workspace-diff. File lists and selected patches load on
@@ -632,7 +661,8 @@ leased until <time>" for each one.`,
632
661
  port the Metro port RESERVED for this workspace
633
662
  supervisorPid the detached supervisor's pid, or NULL when a dev server was
634
663
  already answering that Stim did not start
635
- mode "bare-inproc" | "expo-child" | null (see \`guide metro\`)
664
+ mode "bare-inproc" | "expo-child" | "command-child" | null
665
+ (see \`guide metro\`)
636
666
  logsDir where the NDJSON timeline is written
637
667
  agentDevice { stateDir }: absolute workspace agent-device state path;
638
668
  set AGENT_DEVICE_STATE_DIR to it (see guide logs)
@@ -696,8 +726,9 @@ leased until <time>" for each one.`,
696
726
  configuration the Xcode configuration that was built ("Release" from
697
727
  --configuration or the ios.configuration setting); null for
698
728
  the default Debug
699
- scheme the explicit shared Xcode scheme selected by --scheme;
700
- absent for automatic selection; not the app URL scheme
729
+ scheme the explicit shared Xcode scheme selected by --scheme or
730
+ the ios.scheme setting; absent for automatic selection;
731
+ not the app URL scheme
701
732
  cacheKey the shared-build-cache key derived from it (the
702
733
  configuration is part of it: -release-sim vs
703
734
  -debug-sim-arm64). A single-architecture simulator build
@@ -757,8 +788,11 @@ leased until <time>" for each one.`,
757
788
  the bundle would cost more than installing it
758
789
  launched true, "bundling", or "unverified". THE THREE ARE DIFFERENT
759
790
  FACTS and only the last one is a problem.
760
- true Metro finished the bundle response, then the app stayed
761
- alive through a three-second stability window.
791
+ true Metro finished the bundle response, or an app
792
+ without Metro (a release build, or a native
793
+ Xcode or Gradle app) has a live process; then
794
+ the app stayed alive through a three-second
795
+ stability window.
762
796
  The command checks process liveness when the
763
797
  platform exposes it. Errors from that window
764
798
  are printed even when the app stays alive,
@@ -1013,6 +1047,9 @@ leased until <time>" for each one.`,
1013
1047
  optional fields
1014
1048
  findings the diagnostic findings; a lower resolved Stim is a
1015
1049
  costs-time finding with a PATH or installation remedy
1050
+ memory-culprit is a costs-time finding naming a process
1051
+ with an abnormally large footprint and, when it is safe to
1052
+ restart, the command
1016
1053
  offload-candidate is a note after 3+ successful local cold builds in 7 days average over 3 min, with no remote.machines and an online tailnet Mac; open Stim Desktop Settings > Remote Macs > Add
1017
1054
 
1018
1055
  ON FAILURE
@@ -1464,6 +1501,9 @@ RULES
1464
1501
  of the Stim tutorial app (stim guide tutorial). Status
1465
1502
  reports whatever version it finds; Desktop decides which
1466
1503
  versions it supports. Static app.json only.
1504
+ doctorRuns { ios?: { at }, android?: { at } }: when stim doctor last
1505
+ ran in the workspace, per platform, as ISO timestamps;
1506
+ absent until it has run.
1467
1507
  recording { enabled }: whether stim-server may record the workspace's
1468
1508
  device screens for replay, from recording.enabled
1469
1509
 
@@ -1733,8 +1773,8 @@ RULES
1733
1773
  build { platform, slot, state, phase, startedAt, phaseStartedAt,
1734
1774
  outcome, outcomeKnown, cacheLookupOutcome?, expectedMs, expectedPhaseMs,
1735
1775
  completedPhaseMs?, basis,
1736
- plannedPhases, missReason?, missProvisional?, detail?, placement,
1737
- waitingOn?, waitingFor? }
1776
+ plannedPhases, missReason?, missProvisional?, detail?, activity?,
1777
+ placement, waitingOn?, waitingFor? }
1738
1778
 
1739
1779
  state "running" while the run's own native-run claim is live;
1740
1780
  "stale" when that claim was released or its process is
@@ -1826,6 +1866,16 @@ RULES
1826
1866
  to file names, at most 160 characters
1827
1867
  updatedAt when the build last wrote it; the run writes it at most
1828
1868
  every 2 seconds
1869
+ activity during device or launch, once Stim observed what the run
1870
+ waits on: { name, startedAt, percent? }. name is
1871
+ "booting" while device waits for the owned simulator or
1872
+ emulator the run is booting; during launch, "bundling"
1873
+ from the app's bundle request to this workspace's Metro
1874
+ until Metro delivered it, or "waiting-ready" while the
1875
+ app reports its readiness pending; absent in between.
1876
+ startedAt is the evidence's time. percent is Metro's own
1877
+ progress for that request, present only once Metro
1878
+ reported one. Activity never changes launched.
1829
1879
  placement where the build runs: "local", or while it is offloaded
1830
1880
  { host, phase, startedAt, phaseStartedAt }. host is the
1831
1881
  remote.machines entry; phase is the step there: sync,
@@ -1846,6 +1896,7 @@ RULES
1846
1896
 
1847
1897
  build: ios compile, 1m10s elapsed -- about 3 min left (median of 4 cold runs)
1848
1898
  build: ios compile on janics-mac-mini (build, 2m10s), 3m05s elapsed
1899
+ build: ios launch (bundling JS 45%, 12s), 58s elapsed
1849
1900
 
1850
1901
  An environment with a recorded run also carries lastBuilds, each
1851
1902
  platform's most recent ios or android run, finished or failed:
@@ -2025,7 +2076,9 @@ RULES
2025
2076
  builds included, when machine.memorySource is footprint. Otherwise it is
2026
2077
  the estimate, with memorySource estimate.
2027
2078
 
2028
- capacity.committedMb sums memoryMb. The memory budget plans before a boot
2079
+ capacity.committedMb sums memoryMb; in a \`status --watch --json\` line each
2080
+ figure, including an environment's memoryMb, is held on its own (see
2081
+ \`guide lifecycle\`), so the sums can differ from what they sum. The memory budget plans before a boot
2029
2082
  and always uses the estimate. What is using CPU and memory now is the
2030
2083
  top-level machine section:
2031
2084
 
@@ -2441,8 +2494,9 @@ WHAT THE SUPERVISOR IS
2441
2494
  to install, and no cross-project state. It hosts the dev server, writes its
2442
2495
  output as NDJSON into the global workspace logs directory
2443
2496
  ($STIM_HOME/workspaces/<project>--<digest>/logs; see \`guide logs\`), and
2444
- records itself in that workspace's state.json before it starts serving. Two modes,
2445
- chosen by ecosystem detection:
2497
+ records itself in that workspace's state.json before it starts serving. Three
2498
+ modes: metro.command selects command-child, otherwise ecosystem detection
2499
+ picks one of the first two:
2446
2500
 
2447
2501
  bare-inproc bare React Native: Metro is hosted INSIDE the supervisor,
2448
2502
  from the project's own node_modules, with Stim's reporter
@@ -2451,6 +2505,15 @@ WHAT THE SUPERVISOR IS
2451
2505
  expo-child Expo: the project's own \`expo start --port <port>\` runs as
2452
2506
  a child and its stdout is parsed into records. Levels are
2453
2507
  INFERRED from each line, so those records carry raw: true.
2508
+ command-child metro.command (\`guide settings\`): that argv runs as a
2509
+ child in its own process group, with {port} replaced by the
2510
+ reserved port, and its output is parsed like expo-child's
2511
+ under command_stdout and command_stderr events. Stopping the
2512
+ dev server signals the whole group, so a wrapper such as
2513
+ yarn takes its Metro with it. Stim adds no reporter, warmup
2514
+ observer or shared transform store, so ios and android
2515
+ launches stay UNVERIFIED: read \`stim logs --source metro\`
2516
+ for the bundle lines.
2454
2517
 
2455
2518
  In expo-child mode, remote intent plus metro.tunnel "expo" makes \`start\` pass
2456
2519
  \`--tunnel\` and EXPO_UNSTABLE_TUNNEL_V2=1 (the legacy ws-tunnel path is
@@ -2469,7 +2532,8 @@ WHAT THE SUPERVISOR IS
2469
2532
 
2470
2533
  IDLE STOP: the supervisor stops its dev server after metro.idleStopMinutes
2471
2534
  (default 60; 0 never stops) with no bundle request, no client log record
2472
- (in-app console logs; for Expo, any stdout line of the Expo CLI) and no Stim
2535
+ (in-app console logs; for Expo or metro.command, any stdout line of the
2536
+ dev server) and no Stim
2473
2537
  command in the workspace (start, ios, android, reload, worktree warm). It
2474
2538
  checks once a minute. It keeps running while a bundle response is in
2475
2539
  flight, while a build in the workspace is in progress, while a stim ios,
@@ -2650,11 +2714,12 @@ ports release [label] releases without signalling a process.
2650
2714
  Omitting the label selects every named port, never Metro. stim stop leaves
2651
2715
  named ports alone.
2652
2716
 
2653
- worktree remove stops named listeners and releases their allocations.
2654
- gc reports allocations whose workspace no longer exists; gc --delete stops
2655
- and releases them. Unmounted or unresolved workspace paths are retained.
2656
- Failed stops keep the registry entry. Use the same current Stim version for
2657
- cleanup: versions without ports do not know about named allocations.
2717
+ worktree remove, gc --delete and automatic maintenance release named
2718
+ allocations without signalling their listeners; run ports stop first to stop
2719
+ a server this workspace started. gc reports allocations whose workspace no
2720
+ longer exists; gc --delete releases them. Unmounted or unresolved workspace
2721
+ paths are retained. Use the same current Stim version for cleanup: versions
2722
+ without ports do not know about named allocations.
2658
2723
 
2659
2724
  A shared API does not get a shared reservation. Pass its port by environment
2660
2725
  instead of allocating a separate label in every worktree.`
@@ -2879,7 +2944,8 @@ THE RECORD
2879
2944
  clockOffsetMs the offset added to deviceTs; absent if the query failed.
2880
2945
  A collector_clock warning then says timestamps retain device time.
2881
2946
  raw true when the level was inferred from a line of text rather than
2882
- reported by the producer (every expo-child record)
2947
+ reported by the producer (every expo-child and command-child
2948
+ record)
2883
2949
  context --errors --json only: the code frame and stack lines Expo
2884
2950
  printed after this error, as an array of strings. Absent when
2885
2951
  there are none. Stim adds it at query time; it is not in the
@@ -2950,11 +3016,11 @@ they stay in the local logs. These records carry no tokens.
2950
3016
 
2951
3017
  WHAT WRITES WHAT
2952
3018
  maintenance.ndjson workspace maintenance actions, failures and explaining skips
2953
- metro.ndjson the bundler, in both supervisor modes
3019
+ metro.ndjson the bundler, in every supervisor mode
2954
3020
  client.ndjson in-app console logs and redboxes -- BARE PROJECTS ONLY.
2955
- In expo-child mode everything Expo prints lands in
2956
- metro.ndjson with raw: true, so \`--source client\`
2957
- returns nothing there.
3021
+ In expo-child and command-child mode everything the
3022
+ dev server prints lands in metro.ndjson with raw: true,
3023
+ so \`--source client\` returns nothing there.
2958
3024
  device.ndjson the device-log collector uses \`simctl log stream\`
2959
3025
  predicated on the app, or \`adb logcat\` filtered to
2960
3026
  the app's pid. Local iOS simulator capture starts
@@ -3117,7 +3183,9 @@ WHAT WRITES WHAT
3117
3183
  build-android.ndjson extracted diagnostics at level error, and the launch as
3118
3184
  a marker record. One RUN's worth: each build starts the
3119
3185
  file over, so the first error in it always belongs to
3120
- the run that pointed you at it.
3186
+ the run that pointed you at it. A build from the API's
3187
+ build() writes build-artifact-<platform>.ndjson
3188
+ instead, so it never replaces a run's transcript.
3121
3189
 
3122
3190
  Only a dev server Stim hosted is captured. If you started the bundler
3123
3191
  yourself, the metro and client sources stay empty -- which is not a sign of a
@@ -3579,7 +3647,8 @@ Rerun with --remote-build auto for normal placement and local fallback, or
3579
3647
  body: () => `STIM_NO_SCHEME
3580
3648
  Stim could not list or select an app scheme in ios/. Share the intended app
3581
3649
  scheme so xcodebuild can see it. Select an available exact name with
3582
- \`stim ios --scheme <name>\`. An unknown explicit name prints available choices.
3650
+ \`stim ios --scheme <name>\`, or set it once with the ios.scheme setting. An
3651
+ unknown explicit name prints available choices.
3583
3652
  Without an explicit selector, a workspace
3584
3653
  name match wins; otherwise Stim accepts a sole non-test scheme, or a listed
3585
3654
  scheme matching app.json. Unmatched ambiguous schemes are refused.`
@@ -4255,10 +4324,10 @@ captured" (in metro.ndjson, bare RN)
4255
4324
  \`--device-profile\` on an eas/proxy run (\`--remote\` or android.remote), a
4256
4325
  \`android --remote auto\`, a changed hosted placement, or hosted Debug
4257
4326
  with \`--no-metro-check\` (see lifecycle hosted-android), a working directory
4258
- with no package.json above it, or one whose nearest package.json does not
4259
- parse or depends on neither react-native nor expo, so the directory is not
4260
- an app (the refusal names that package.json and says which of the two it
4261
- is; \`doctor\` reports the same directory as a finding), a \`logs\` query in
4327
+ with no supported app project, or a project whose integration refuses this
4328
+ operation. React Native and Expo require a readable package.json that
4329
+ declares react-native or expo; the refusal names the invalid file.
4330
+ \`doctor\` reports project admission problems as findings. A \`logs\` query in
4262
4331
  a workspace that has never produced a log timeline (the refusal names the
4263
4332
  nearest registered descendant app with logs when one exists), an
4264
4333
  android/app/build.gradle that declares product flavors with
@@ -4285,7 +4354,7 @@ captured" (in metro.ndjson, bare RN)
4285
4354
  \`gc --json --cache <name>\` refuses with STIM_BAD_ARG when no shared cache
4286
4355
  carries the name; the remedy names the caches on this machine. \`gc\`
4287
4356
  refuses --cache together with --worktrees the same way.
4288
- A working directory with no package.json above it gets the same
4357
+ A working directory with no supported project above it gets the same
4289
4358
  STIM_NO_PROJECT refusal from \`start\`, \`ios\`, \`android\`, \`stop\`,
4290
4359
  \`reload\`, \`logs\`, \`doctor\` and \`device lock|unlock\`. With \`--json\` the { code, message, remedy } object
4291
4360
  is on stdout, except \`logs --json\`, whose stdout stays empty NDJSON; the
@@ -4746,6 +4815,10 @@ const lifecycle = {
4746
4815
  summary: "The full worktree -> start -> ios/android -> logs -> teardown flow, with sections for builds, devices and flags",
4747
4816
  preamble: () => `ENVIRONMENT LIFECYCLE
4748
4817
 
4818
+ For a native Xcode or Gradle app without React Native or Expo, read
4819
+ \`stim guide lifecycle native-ios\` or \`stim guide lifecycle native-android\`.
4820
+ The Metro and JavaScript steps below apply to React Native and Expo.
4821
+
4749
4822
  Two workflows share steps 2 through 6.
4750
4823
 
4751
4824
  SINGLE CHECKOUT: work in place, on a branch, in one directory. There is no
@@ -4821,7 +4894,7 @@ a seed belongs to this workflow.
4821
4894
  # unwarmed worktrees with no Stim registry entry. Git-created branches stay.
4822
4895
  stim worktree remove
4823
4896
 
4824
- A Debug \`ios\` or \`android\` run checks the reserved port before the device
4897
+ A React Native or Expo Debug \`ios\` or \`android\` run checks the reserved port before the device
4825
4898
  or the build. When no healthy dev server of this workspace answers there, it
4826
4899
  runs the same start as step 2: a dev server started outside Stim is reused, a
4827
4900
  reservation held by a foreign process moves to a free port, and the budgets
@@ -4985,7 +5058,10 @@ WAITING FOR A CHANGE
4985
5058
  To wait for a device, a build or a dev server instead of polling, run
4986
5059
  \`stim status --watch --json\`. It keeps running and prints one complete
4987
5060
  status payload per line: one at once, then one each time the payload
4988
- changes, never two identical ones in a row. It reacts to Stim state files,
5061
+ changes, never two identical ones in a row. In a watch line, a usage
5062
+ figure repeats its value in the previous line until it moves by a step: an owner's cpuPercent 5 points, an
5063
+ owner's or environment's memoryMb and capacity.committedMb 16 MB. An owner's row
5064
+ changes as a whole, so its residentMb and processes update with it. It reacts to Stim state files,
4989
5065
  the EAS session ledger, adb device arrivals and departures, and simulator
4990
5066
  state, and recomputes every 30 seconds as a fallback. A log append updates
4991
5067
  only the log error count and device activity, no sooner than 15 seconds
@@ -4996,6 +5072,121 @@ WAITING FOR A CHANGE
4996
5072
  still running, which it notices at the next change.
4997
5073
  Without --json it reprints the human view on change.`,
4998
5074
  sections: {
5075
+ "native-ios": {
5076
+ summary: "native Xcode project selection, process launch, cache behavior and current limits",
5077
+ body: () => `NATIVE XCODE APPS
5078
+ Run from the directory containing the .xcodeproj or .xcworkspace. A Node
5079
+ package.json is not required. React Native and Expo apps keep their existing
5080
+ integration.
5081
+
5082
+ stim doctor --platform ios
5083
+ stim ios --scheme MyApp --configuration Debug --json
5084
+ stim ios --scheme MyApp --configuration Release --json
5085
+ stim logs --errors
5086
+ stim stop
5087
+
5088
+ Use the project's actual scheme and configuration names. Shared Run schemes
5089
+ select application targets; a unique application target can also use its
5090
+ automatic scheme. A workspace owns its referenced projects. --scheme selects
5091
+ an exact name; duplicate container/scheme matches refuse instead of guessing.
5092
+ --configuration overrides ios.configuration in .stim.json, then defaults to
5093
+ Debug. Custom configurations such as Staging use the same path.
5094
+
5095
+ Native Debug, Release and custom configurations do not start or require Metro,
5096
+ embed JavaScript, or swap a JavaScript bundle. The app is launched as a native
5097
+ process and reports metroPort: null. launched: true establishes a live process;
5098
+ verify the expected screen and interaction on the reported device separately.
5099
+ After a native source edit, rerun stim ios. stim reload is for JavaScript.
5100
+ The existing owned simulator, device lease, signing, log and teardown services
5101
+ still apply. Stim does not change signing accounts or provisioning profiles.
5102
+
5103
+ A reusable artifact key includes the selected build configuration, source and
5104
+ known dependency inputs, toolchain and build options. In a git checkout,
5105
+ ignored files the project does not reference are not part of the key. Native
5106
+ Release cache reuse does not depend on releaseBundleSwap. Xcode compilation
5107
+ uses the shared compilation cache when the toolchain and settings support it.
5108
+ --plan predicts this same local artifact without preparing dependencies or
5109
+ choosing a device.
5110
+
5111
+ Inputs whose complete build closure cannot be established build locally with
5112
+ artifact caching skipped. This includes shell build phases, custom build rules,
5113
+ C-family header graphs, Swift package graphs and unresolved external input
5114
+ paths or compiler overrides. The miss reason explains the exclusion. --plan
5115
+ refuses such a prediction; omit --plan to prepare and build the app.
5116
+
5117
+ A compatible approved build Mac can compile a native Xcode app with
5118
+ --remote-build auto or --remote-build <name>. Both peers must support
5119
+ native-xcode-build. This first worker path requires a verified artifact identity
5120
+ and all inputs contained in the repository and visible to git. Ignored or
5121
+ external inputs keep the build here, and placement names the first one; their
5122
+ bytes are never silently omitted. The common case is a Finder .DS_Store that a
5123
+ global gitignore hides: delete it, or stop ignoring it, to offload. The worker
5124
+ compares its own toolchain and compiler environment identity before compiling
5125
+ and refuses, naming the differing parameter, so the build falls back here.
5126
+ Worker source mirrors remove stale inputs, while Xcode compilation caches remain
5127
+ in the worker's private Stim home. Physical builds stay local.
5128
+
5129
+ Native Xcode apps can install on an already approved hosting Mac with
5130
+ process-mode support:
5131
+ stim ios --scheme MyApp --configuration Debug --remote janics-mac-mini --json
5132
+
5133
+ The host must advertise hosted-ios-process; older hosts refuse with an update
5134
+ remedy before reservation or upload. --remote auto skips incompatible hosts,
5135
+ but an existing session stays on its recorded host until stim stop. Debug,
5136
+ Release and custom configurations use the offered simulator architecture,
5137
+ report metroPort: null, and close any previous session Metro bridge. Logs,
5138
+ view/control, agent access and scoped stop use the existing hosted-ios services.
5139
+ Verify UI and interaction separately from the host's live process evidence.
5140
+
5141
+ Unbounded native build offload, hosted --plan, eas/proxy devices and EAS artifact
5142
+ profiles remain unsupported for native Xcode apps. These limits do not change React Native or
5143
+ Expo support.
5144
+
5145
+ Copyable agent request:
5146
+ Run this native Xcode app with stim ios --scheme MyApp --configuration Debug
5147
+ --json. Verify its expected screen and interaction on the reported device,
5148
+ inspect stim logs --errors, then stop only this workspace with stim stop.`
5149
+ },
5150
+ "native-android": {
5151
+ summary: "native Gradle app selection, hosted emulators, build-worker inputs and current limits",
5152
+ body: () => `NATIVE ANDROID GRADLE APPS
5153
+ Native Android Gradle apps run directly with \`stim android [--variant freeDebug]\`:
5154
+ no Metro, npm install or React Native dependency is required. The directory must
5155
+ contain a Gradle wrapper and settings.gradle or settings.gradle.kts. AGP resolves
5156
+ one application module and the exact variant (debug by default). Stim installs
5157
+ one signed universal or matching-ABI APK on an owned emulator or leased phone.
5158
+ Use \`--remote <approved-mac>\` or \`--remote auto\` for a hosted emulator; the host
5159
+ must support native Android process mode. Multiple application modules,
5160
+ density/split APK sets and EAS/proxy targets are unsupported. Set
5161
+ org.gradle.configureondemand=false; configuration cache remains supported.
5162
+ Stim verifies the existing APK signature; it does not sign it.
5163
+
5164
+ Stim artifact caching is unavailable because arbitrary Gradle inputs are not fully
5165
+ tracked. Build workers require a project-scoped \`android.offloadInputs\` declaration:
5166
+ \`{"complete":true,"ignored":["app/src/main/assets/generated.json"],"outputs":["build","app/build"]}\`.
5167
+ Use \`stim android --remote-build <approved-mac>\` after reviewing this declaration.
5168
+ Paths are exact repository-relative paths. complete affirms that Git-visible
5169
+ source plus the listed ignored files suffice, including optional files; Gradle
5170
+ DSL can read undeclared input without failing. Never list secrets or user Gradle
5171
+ homes. External inputs, directory links, submodules and custom local.properties
5172
+ refuse; an SDK-only local.properties stays local. Only declared directories
5173
+ reported by AGP and previously produced by this worker remain warm, along with
5174
+ worker project .gradle/.kotlin state. Undeclared bytes are removed. Gradle owns
5175
+ incremental, task-cache and configuration-cache reuse; transfer digests never
5176
+ become reusable APK cache keys. Older hosts refuse before source upload.
5177
+
5178
+ outputs must list every build directory of this build's projects that the build
5179
+ creates: the root build directory and each module's, library modules included.
5180
+ A remote build that creates an undeclared one fails with its name, and Stim
5181
+ builds locally; add it to outputs. A buildSrc directory refuses before upload.
5182
+ Included builds (includeBuild, such as a build-logic plugin build) and
5183
+ externalNativeBuild staging (.cxx) are not reported by AGP and cannot be
5184
+ declared, so a remote build that creates them fails and Stim builds locally.
5185
+
5186
+ Native Android \`--plan\` refuses without executing Gradle; \`doctor\` reports native
5187
+ prerequisites. \`reload\` refuses because the app has no Metro runtime. Re-run
5188
+ \`stim android\` after an edit and use \`stim stop\` for scoped cleanup.`
5189
+ },
4999
5190
  ci: {
5000
5191
  summary: "Run an app and tests through @stim-cli/ci with diagnostics and scoped cleanup",
5001
5192
  body: () => `CONTINUOUS INTEGRATION
@@ -5059,7 +5250,7 @@ policy. No flag or setting runs on this Mac.
5059
5250
  A named Mac is strict: refusal, unreachable, declined or failed preparation
5060
5251
  returns STIM_HOSTING_REFUSED without fallback. --device, a different recorded
5061
5252
  machine, a running local owned emulator in the slot, and --no-metro-check for
5062
- hosted Debug refuse STIM_BAD_ARG. Run stim stop before switching placement.
5253
+ hosted Metro development apps refuse STIM_BAD_ARG. Run stim stop before switching placement.
5063
5254
  Unreadable android.host blocks only its slot; restore its recorded machine and
5064
5255
  session from the host before stopping that slot.
5065
5256
 
@@ -5069,7 +5260,7 @@ compatible build worker. Stim builds before reserving, records the session as
5069
5260
  soon as it exists, then delivers one App.apk. The emulator boots headless;
5070
5261
  androidEmulatorApp is ignored with a note.
5071
5262
 
5072
- Debug requires the local Metro supervisor. metro.publicUrl and metro.tunnel
5263
+ React Native Debug requires the local Metro supervisor. metro.publicUrl and metro.tunnel
5073
5264
  are ignored with a note. A private tailnet gateway reaches the host loopback
5074
5265
  bridge; the host reverses the client's Metro port into that bridge on its exact
5075
5266
  ledger-owned serial and sets debug_http_host to localhost:<clientMetroPort>.
@@ -5079,6 +5270,14 @@ adb against the host serial on this Mac. Release variants skip Metro. Launch
5079
5270
  is true only with client bundle evidence or a live host release process;
5080
5271
  bundling requires a bundle request and unverified has no launch evidence.
5081
5272
 
5273
+ Native Gradle apps use process mode for every variant. The host must advertise
5274
+ hosted-android-process; older hosts refuse before admission or APK upload.
5275
+ Native runs close any previous Metro bridge, launch the signed APK and require
5276
+ a live app process for launched=true. --no-metro-check is allowed, and reload
5277
+ refuses because there is no Metro runtime. Re-run stim android after an edit.
5278
+ Native EAS/proxy targets remain unsupported. Build offload requires the complete
5279
+ android.offloadInputs declaration; see stim guide lifecycle native-android.
5280
+
5082
5281
  Status adds android.host { machine, session, selected, agent, device: { name,
5083
5282
  systemImage, api }, state } per slot. The public name is the profile and API
5084
5283
  level, never the host serial or AVD name. ready, stopped and unverified are
@@ -5275,7 +5474,7 @@ sessions.
5275
5474
 
5276
5475
 
5277
5476
  Stim builds or fetches before reserving, records the session immediately, then
5278
- uploads the app on every run. Debug keeps Metro here; its supervisor owns a
5477
+ uploads the app on every run. React Native and Expo Debug keep Metro here; the supervisor owns a
5279
5478
  private gateway bound to this Mac's Tailscale address, pinned to the host peer
5280
5479
  and protected by a per-session secret. The host exposes only a loopback bridge.
5281
5480
  No start --remote is needed; metro.tunnel and metro.publicUrl are ignored.
@@ -5283,7 +5482,8 @@ Debug requires a running supervisor with private gateway support before reservin
5283
5482
  a simulator. --no-metro-check refuses with STIM_BAD_ARG for hosted Debug runs.
5284
5483
  If its supervisor is missing or older, run stim stop; stim start, then retry.
5285
5484
  Non-Debug runs skip Metro. Launch is unverified until this workspace's Metro
5286
- provides bundle evidence, or the host proves a live release process.
5485
+ provides bundle evidence, or the host proves a live release process. Native Xcode
5486
+ apps use process mode in every configuration and require no Metro; see native-ios.
5287
5487
 
5288
5488
  A rerun on the same named Mac reattaches and delivers a new app attempt. A
5289
5489
  stopped or missing session is replaced; an unreachable or unknown owner refuses
@@ -5956,6 +6156,11 @@ PREDICTING THE NEXT BUILD (--plan)
5956
6156
  installing, or starting Metro. They take no workspace lock and write no
5957
6157
  Stim state, cache entry or statistic, so they run beside a build.
5958
6158
 
6159
+ The selected project integration supplies the read-only plan and uses its
6160
+ build recipe's identity and cache policy. If it has no planner, --plan
6161
+ refuses with STIM_BAD_ARG; run the command without --plan. The refusal
6162
+ reports no cache hit. React Native and Expo planning behaves as follows.
6163
+
5959
6164
  stim ios --plan
5960
6165
  plan ios 1b625d.. -> local cache hit
5961
6166
  expect ~2.7s (median of 1 hit run)
@@ -6006,13 +6211,16 @@ IOS SCHEME SELECTION
6006
6211
  Xcode scheme, not the app's URL scheme. Combine it with --configuration
6007
6212
  when choosing both an app scheme and a build configuration.
6008
6213
 
6009
- Without --scheme, automatic selection is unchanged:
6214
+ The ios.scheme setting names the scheme once for the app (guide settings);
6215
+ --scheme overrides it per run, and both give the same keys below.
6216
+
6217
+ Without --scheme or ios.scheme, automatic selection is unchanged:
6010
6218
  Stim keeps a scheme matching the workspace/project name, or the sole non-test
6011
6219
  scheme. When neither identifies one, it also checks the static top-level name
6012
6220
  in the app directory's app.json against Xcode's listed schemes. It does not
6013
6221
  execute app config or choose an arbitrary scheme from an ambiguous list.
6014
- If selection fails, pass --scheme with an available name, or share the app
6015
- scheme in Xcode first. See guide errors STIM_NO_SCHEME.
6222
+ If selection fails, pass --scheme with an available name, set ios.scheme, or
6223
+ share the app scheme in Xcode first. See guide errors STIM_NO_SCHEME.
6016
6224
 
6017
6225
  Explicit schemes have separate artifact keys, shared-build locks, and Xcode
6018
6226
  build directories. Stim identifies the resulting application from Xcode's
@@ -6021,7 +6229,8 @@ IOS SCHEME SELECTION
6021
6229
  configured Stim cache providers use the scheme-specific key. The older Expo
6022
6230
  buildCacheProvider tier is skipped for explicit schemes because a provider
6023
6231
  may key only on the fingerprint and return another scheme's app. Omitting
6024
- --scheme retains the existing cache keys and provider behavior.
6232
+ both --scheme and ios.scheme retains the existing cache keys and provider
6233
+ behavior.
6025
6234
 
6026
6235
  AN ARTIFACT THE DEVICE ALREADY HOLDS IS NOT INSTALLED AGAIN
6027
6236
  Both platforms store the artifact verbatim, so its hash is its identity.
@@ -6063,7 +6272,9 @@ A RUNNING APP IS RESTARTED, LIKE XCODE'S RUN
6063
6272
  launch com.example.app restarted running app (was pid 4242) (0.9s)
6064
6273
 
6065
6274
  A stop that fails refuses the launch rather than reusing the old process.
6066
- When the process list cannot be read, Stim launches without stopping
6275
+ When a simulator's process list cannot be read, Stim launches with
6276
+ \`simctl launch --terminate-running-process\`, which replaces any running
6277
+ copy. When an emulator's cannot be read, Stim launches without stopping
6067
6278
  anything and verification reports what it observes. An iPhone run
6068
6279
  (\`ios --device\`) needs no extra step: its collector launches with
6069
6280
  devicectl's \`--terminate-existing\`. Remote targets launch through
@@ -6982,7 +7193,7 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
6982
7193
  MUTATE: the cache entry stays the pristine, shareable artifact, and the
6983
7194
  per-run address lives only in the copy that is installed and then deleted.
6984
7195
 
6985
- A RELEASE device run builds fresh every time for now. A cached Release app
7196
+ A React Native or Expo RELEASE device run builds fresh every time for now. A cached Release app
6986
7197
  carries its BUILDER's JS, and the device JS swap (which has to re-seal what
6987
7198
  it injects) lands with phase 6 of appandflow/stim#178, so the cache hit is
6988
7199
  refused rather than installed with someone else's JavaScript.
@@ -7197,8 +7408,8 @@ THE POOL: WHICH DEVICE AN ID-LESS \`--device\` PICKS
7197
7408
  distribution stay out of scope.
7198
7409
 
7199
7410
  \`ios --configuration <name>\` selects the Xcode configuration --
7200
- \`--configuration Release\` builds a SIMULATOR Release app with the JS
7201
- bundle embedded. It overrides the ios.configuration setting (the app-level
7411
+ for React Native and Expo, \`--configuration Release\` builds a SIMULATOR
7412
+ Release app with the JS bundle embedded. It overrides the ios.configuration setting (the app-level
7202
7413
  default); unset, the Debug flow is unchanged. A non-Debug configuration
7203
7414
  skips Metro ENTIRELY: no gate, no port wiring, no dev-client deep link (a
7204
7415
  plain \`simctl launch\`), and the payload says \`metroPort: null\` --
@@ -7287,6 +7498,19 @@ HOST MEMORY PRESSURE AND STALLED SIMULATORS
7287
7498
  a failed query remains unknown. Existing swap or low free RAM alone is not
7288
7499
  enough to diagnose pressure.
7289
7500
 
7501
+ Doctor, and boot messages under warning or critical pressure, also name any
7502
+ process whose physical footprint is abnormal, read from macOS top (it counts
7503
+ compressed and swapped memory, which ps RSS leaves out, and reads root-owned
7504
+ daemons). Abnormal is at least a quarter of physical memory and 8 GiB, or
7505
+ 2 GiB for fseventsd and Watchman, which are normally far smaller. A known
7506
+ safe-to-restart process comes with its command; Stim never runs it:
7507
+ fseventsd sudo killall fseventsd (launchd restarts it;
7508
+ file watchers rescan once)
7509
+ Watchman stim gc --delete --cache watchman
7510
+ Gradle or Kotlin daemon stim gc --delete --cache gradle-daemons
7511
+ Any other process is named with its size only; ask before quitting it.
7512
+ Doctor skips this check when STIM_HOME is set: processes are machine-global.
7513
+
7290
7514
  If pressure is elevated, free host memory before retrying. Use \`stim stop\`
7291
7515
  only in workspaces you own and have finished using; ask before closing other
7292
7516
  agents' simulators or heavy apps. Never quit Device Hub or Simulator.app to
@@ -7335,7 +7559,9 @@ Pressure checks read free disk on the Stim home, projects and worker root
7335
7559
  volumes. On macOS the memory signal is the sysctl pressure level, with no
7336
7560
  signal if sysctl fails. Other platforms use os.freemem(); macOS never falls
7337
7561
  back to it. Memory pressure is recorded only; stopping devices, dev servers
7338
- and helpers is not automatic.
7562
+ and helpers is not automatic. While the level is warning or critical, the
7563
+ check also names abnormally large processes (guide lifecycle simslim) in
7564
+ status's maintenance block and the maintenance log.
7339
7565
 
7340
7566
  In on mode a pass runs, cheapest to rebuild first:
7341
7567
  1. orphaned workspace directories whose project is gone, and registered
@@ -7716,11 +7942,11 @@ STALE STATUS CACHE ENTRIES
7716
7942
  if it comes back.
7717
7943
 
7718
7944
  NAMED SERVER PORTS
7719
- worktree remove stops TCP listeners on each named allocation and releases
7720
- the ports. gc reports named allocations for missing workspaces; gc --delete
7721
- stops their listeners and releases them. Unmounted or unresolved paths stay
7722
- registered. Failed stops retain their allocations for a later retry.
7723
- stim stop does not touch named ports. See guide ports.
7945
+ worktree remove, gc --delete and automatic maintenance release named
7946
+ allocations without signalling their listeners. gc reports named
7947
+ allocations for missing workspaces; gc --delete releases them. Unmounted or
7948
+ unresolved paths stay registered. Only ports stop stops a named listener,
7949
+ and stim stop does not touch named ports. See guide ports.
7724
7950
 
7725
7951
  ON THE SOURCE CHECKOUT
7726
7952
  git cannot remove a repository's main working tree, and deleting the source
@@ -8232,6 +8458,15 @@ KEYS STIM READS
8232
8458
  The \`--runtime\` flag overrides this per invocation,
8233
8459
  and an uninstalled version refuses the same way,
8234
8460
  naming the layer the value came from
8461
+ ios.scheme e.g. "RNTester" -- the shared Xcode scheme to build
8462
+ when the workspace lists several and none matches
8463
+ its name, which otherwise refuses with
8464
+ STIM_NO_SCHEME. The \`--scheme\` flag overrides it
8465
+ per invocation; an \`--eas-profile\` build reads only
8466
+ the flag. Part of the cache key, like the flag, so
8467
+ it also skips the older Expo buildCacheProvider tier,
8468
+ which may key only on the fingerprint. Workspace or
8469
+ committed scope.
8235
8470
  ios.configuration e.g. "Release" -- the Xcode configuration to build
8236
8471
  (simulator only). Committing
8237
8472
  { "ios": { "configuration": "Release" } } makes every
@@ -8244,6 +8479,19 @@ KEYS STIM READS
8244
8479
  "auto" places on an approved Mac when this Mac is full or
8245
8480
  busy. Unset runs here; "local" runs here even when a
8246
8481
  lower layer says otherwise. See lifecycle hosted-ios.
8482
+ ios.projectPath directory under the app holding a bare React Native
8483
+ app's Xcode project and Podfile; default "ios". "."
8484
+ is the app directory itself (RNTester's layout).
8485
+ Detection, pod install, xcodebuild, the bundle id,
8486
+ doctor, worktree warm and remove, and remote builds
8487
+ all use it. A subdirectory is fingerprinted the way
8488
+ ios/ is; "." hashes every top-level entry except
8489
+ node_modules, Pods, build, android, .stim.json and
8490
+ anything git ignores, so a JS edit misses the build
8491
+ cache instead of risking a stale build. Workspace or committed scope. ios refuses
8492
+ with STIM_BAD_ARG a path that escapes the app or
8493
+ holds no .xcworkspace or .xcodeproj, and any value
8494
+ on an Expo app, whose prebuild writes ios/.
8247
8495
  ios.simslimProfile a SimSlim JSON profile under the app directory,
8248
8496
  at most 64 KiB. Install the
8249
8497
  external tool once with
@@ -8354,6 +8602,26 @@ ${ANDROID_AVD_CONFIG_HELP.map((line) => ` ${line}`).joi
8354
8602
  and hw.audioOutput for that headless launch. With
8355
8603
  androidEmulatorApp "stim-desktop" on macOS, -gpu host
8356
8604
  overrides hw.gpu.mode the same way.
8605
+ android.gradleRoot directory holding gradlew and settings.gradle(.kts),
8606
+ relative to the app; default "android". It may sit
8607
+ outside the app but not outside the repository:
8608
+ RNTester in the React Native monorepo uses "../..".
8609
+ android.module the app's Gradle project path; default ":app", e.g.
8610
+ ":packages:rn-tester:android:app". Its directory is
8611
+ Gradle's default mapping from the root and must hold
8612
+ a build.gradle(.kts). With either set, Gradle runs
8613
+ from the root with qualified tasks such as
8614
+ <module>:assembleDebug; both values join the cache
8615
+ key and the remote build job, and the root's
8616
+ settings, build script, gradle.properties and
8617
+ gradle/ are fingerprinted. Native sources Gradle
8618
+ builds from elsewhere in the repository are not:
8619
+ list them in fingerprint.config.js extraSources.
8620
+ android refuses with STIM_BAD_ARG a root outside the
8621
+ repository or without settings.gradle, a module
8622
+ without a build script, and either value on an Expo
8623
+ app, whose prebuild writes android/. Workspace or
8624
+ committed scope.
8357
8625
  android.variant e.g. "productionDebug" -- the gradle variant to
8358
8626
  assemble and install on a project with product
8359
8627
  flavors. A repo like tlon-mobile with
@@ -8369,20 +8637,36 @@ ${ANDROID_AVD_CONFIG_HELP.map((line) => ` ${line}`).joi
8369
8637
  build: embedded JS, no Metro, cache keyed on the
8370
8638
  variant, and an APK re-pack on cache hits. See
8371
8639
  \`guide lifecycle release\`.
8640
+ android.offloadInputs a native Gradle build-worker declaration:
8641
+ {"complete":true,"ignored":[],"outputs":["build","app/build"]}.
8642
+ complete confirms Git-visible source plus the exact
8643
+ repository-relative ignored files suffice for this
8644
+ build. outputs names generated directories reported
8645
+ by AGP; recorded outputs can survive worker sync.
8646
+ Omitted optional inputs can change build results.
8647
+ Stim artifact caching remains unavailable. See
8648
+ \`stim guide lifecycle native-android\` for input,
8649
+ privacy and worker requirements.
8372
8650
  android.keystore the keystore a RE-PACKED release APK is signed with,
8373
8651
  absolute or relative to the project root. Unset means
8374
8652
  android/app/debug.keystore, which every RN and Expo
8375
8653
  android project carries -- the right default, because
8376
8654
  what this signs is a local emulator install and never
8377
8655
  anything distributed. Set it only when the release
8378
- variant must be signed with the repo's own key.
8656
+ variant must be signed with the repo's own key. A
8657
+ value in the committed .stim.json must resolve
8658
+ inside the repository (symlinks included); an
8659
+ absolute path outside it needs --scope workspace
8660
+ or repo.
8379
8661
  android.keystorePassword
8380
8662
  the password for it. apksigner's SCHEMED form is
8381
8663
  passed through unchanged (\`env:MY_KS_PASS\`,
8382
8664
  \`file:/keys/pw.txt\`, \`stdin\`), which is how a
8383
8665
  committed .stim.json avoids carrying a secret; a
8384
- bare string is used as the literal password. Unset
8385
- means the debug keystore's fixed "android".
8666
+ bare string (or \`pass:<password>\`) is the literal
8667
+ password, handed to apksigner through its
8668
+ environment, never its command line. Unset means
8669
+ the debug keystore's fixed "android".
8386
8670
  android.remote "proxy", "eas", or a named approved Mac in
8387
8671
  remote.machines; "auto" places on an approved Mac when
8388
8672
  this Mac is full or busy. Unset runs here; "local"
@@ -8416,6 +8700,21 @@ ${ANDROID_AVD_CONFIG_HELP.map((line) => ` ${line}`).joi
8416
8700
  foreground serve process, never Funnel; "auto" never
8417
8701
  selects it. The device must share the tailnet. See
8418
8702
  \`guide metro\`. Any other value is refused as invalid.
8703
+ metro.command argv array that starts this app's dev server, for an
8704
+ app whose Metro only runs through its own command,
8705
+ e.g. ["node", "../react-native/cli.js", "start",
8706
+ "--port", "{port}"]. No shell; it runs from the app
8707
+ directory, and {port} becomes the reserved Metro port,
8708
+ which the command must pass. It replaces bare-inproc
8709
+ and expo-child with command-child (see \`guide
8710
+ metro\`). Workspace or committed scope.
8711
+ Metro must keep running from inside the app, which
8712
+ is how Stim proves it is this app's. Without Stim's
8713
+ Metro reporter, launches stay UNVERIFIED. start
8714
+ refuses with STIM_BAD_ARG a value that is not such an
8715
+ array or has no {port}, --reset-cache (put the
8716
+ command's own reset flag in metro.command instead),
8717
+ and any use on Windows.
8419
8718
  metro.ngrokUrl the stable managed ngrok URL. It requires metro.tunnel
8420
8719
  "ngrok" and passes --url to ngrok http. Stim owns
8421
8720
  this process.
@@ -8766,7 +9065,7 @@ simulator sessions on, by MagicDNS name with an optional serve port (default
8766
9065
  stim doctor --fix
8767
9066
 
8768
9067
  Automatic membership is separate from approval. In Desktop Settings > Remote Macs,
8769
- use Automatic builds and Automatic simulators for this Mac or a configured remote.
9068
+ use Builds enabled and Simulators enabled for this Mac or a configured remote.
8770
9069
  The equivalent machine settings list the excluded members; both default to []:
8771
9070
 
8772
9071
  stim settings set remote.buildPoolDisabled '["local"]'
@@ -8949,7 +9248,7 @@ iPhone simulator on the target runtime. Its project-selected CocoaPods must matc
8949
9248
  the app's Gemfile.lock pins CocoaPods: both Macs then run that version through
8950
9249
  bundler, so the machine needs only Bundler on its stim-server PATH and
8951
9250
  installs the pinned gems itself on the first build. The comparison selects
8952
- the app's .ruby-version when installed, with pod install's UTF-8 locale defaults.
9251
+ the app's .ruby-version when installed, and otherwise the GEM_HOME, GEM_PATH and Ruby-related PATH entries of the Mac's login shell (read once with $SHELL -lic), unless the caller already sets GEM_HOME, with pod install's UTF-8 locale defaults. pod install, including the Bundler run, uses the same environment.
8953
9252
  For Android its JDK major
8954
9253
  version must match, and its Android SDK must hold the NDK, build-tools and
8955
9254
  compile platform that the project's React Native version names in
@@ -9381,7 +9680,7 @@ the same as for any web project: start the dev server, then run stim web.
9381
9680
  stim logs --errors
9382
9681
  stim reload web
9383
9682
  stim stop # closes Chrome; stim ports stop web stops Vite
9384
- stim worktree remove # in a linked worktree: Chrome, Vite and profile
9683
+ stim worktree remove # in a linked worktree: Chrome and profile, not Vite
9385
9684
 
9386
9685
  The repo layer is shared by every worktree of the repository, so a new
9387
9686
  worktree skips the two settings lines; the certificate and scheme remedies
@@ -9955,15 +10254,16 @@ const TUTORIAL_REPO = "appandflow/stim-tutorial";
9955
10254
  const TUTORIAL_BUNDLE_ID = "dev.stim.tutorial";
9956
10255
  const TUTORIAL_RESTART_PROMPT = "Restart the Stim tutorial.";
9957
10256
  /**
9958
- * The requests Stim Desktop offers to copy, written the way a developer would ask. {base}, {tour}, {worktrees} and
9959
- * {machine} are filled in by Desktop. Only the first and last name a guide section, which holds the folder and cleanup safety rules.
10257
+ * The requests Stim Desktop offers to copy, written the way a developer would ask. {base}, {tour} and {worktrees}
10258
+ * are filled in by Desktop. A step with a section names the guide section that holds its safety rules.
9960
10259
  */
9961
10260
  const TUTORIAL_ASKS = {
9962
- begin: `Clone ${TUTORIAL_REPO} into {base} and install its dependencies, then run stim doctor for iOS there so Stim registers it. Use a fresh folder: if {base} already exists or is inside another git repository, stop and ask me for another folder, and never git add in my own repo. Follow stim guide tutorial run.`,
10261
+ begin: `Clone ${TUTORIAL_REPO} into {base} and follow stim guide tutorial run.`,
10262
+ build: "Make the title purple in the Stim tutorial app and check it on the simulator.",
9963
10263
  agent: "Open the app on the iOS simulator, take a screenshot and confirm the title color.",
9964
- machine: "Build the app for iOS on {machine} with stim instead of on this Mac. Do not approve or pair anything.",
9965
10264
  finish: "I'm done with these experiments in {base} and don't need the changes. Stop the apps and remove the worktrees {worktrees}, and keep the clone. Follow stim guide tutorial finish.",
9966
- share: `Open a pull request to ${TUTORIAL_REPO} with my title color change, and include a screenshot of it running in the simulator. See stim guide tutorial share.`,
10265
+ delete: "Remove the Stim tutorial: remove its worktrees and the clone at {base} with stim worktree remove, then delete {base}. Follow stim guide tutorial delete.",
10266
+ share: `Open a pull request to ${TUTORIAL_REPO} with my title color change, with before and after screenshots from the simulator. See stim guide tutorial share.`,
9967
10267
  retry: "The first iOS build of the tutorial app in {tour} failed. Find out why and run it on iOS again."
9968
10268
  };
9969
10269
  const TUTORIAL_STEPS = [
@@ -9981,7 +10281,7 @@ const TUTORIAL_STEPS = [
9981
10281
  title: "Make a Change",
9982
10282
  who: "you",
9983
10283
  optional: false,
9984
- ask: null,
10284
+ ask: TUTORIAL_ASKS.build,
9985
10285
  section: null,
9986
10286
  commands: []
9987
10287
  },
@@ -10037,15 +10337,6 @@ const TUTORIAL_STEPS = [
10037
10337
  section: null,
10038
10338
  commands: []
10039
10339
  },
10040
- {
10041
- id: "machine",
10042
- title: "Build on Another Mac",
10043
- who: "both",
10044
- optional: true,
10045
- ask: TUTORIAL_ASKS.machine,
10046
- section: null,
10047
- commands: ["cd \"{tour}\"", "stim ios --remote local --remote-build \"{machine}\" --no-build-cache"]
10048
- },
10049
10340
  {
10050
10341
  id: "share",
10051
10342
  title: "Share Your Finish",
@@ -10055,11 +10346,16 @@ const TUTORIAL_STEPS = [
10055
10346
  section: "share",
10056
10347
  commands: [
10057
10348
  "cd \"{tour}\"",
10058
- "git commit -am \"Tutorial change\" -m \"Build <time>, second build cache <hit or miss>\"",
10059
- "xcrun simctl io {udid} screenshot finish.png",
10349
+ "git commit -am \"<one-line summary>\"",
10350
+ "mkdir -p .expo/screenshots",
10351
+ "git checkout origin/main -- theme.js",
10352
+ "xcrun simctl io {udid} screenshot .expo/screenshots/before.png",
10353
+ "git checkout HEAD -- theme.js",
10354
+ "xcrun simctl io {udid} screenshot .expo/screenshots/after.png",
10355
+ "cp .github/pull_request_template.md .expo/screenshots/body.md",
10060
10356
  `gh repo fork ${TUTORIAL_REPO} --remote --remote-name fork`,
10061
10357
  "git push -u fork HEAD",
10062
- `gh pr create --repo ${TUTORIAL_REPO} --fill --attach "finish.png#The change running in the simulator"`
10358
+ `gh pr create --repo ${TUTORIAL_REPO} --title "<one-line summary>" --body-file .expo/screenshots/body.md --attach ".expo/screenshots/before.png#Before" --attach ".expo/screenshots/after.png#After"`
10063
10359
  ]
10064
10360
  },
10065
10361
  {
@@ -10078,6 +10374,23 @@ const TUTORIAL_STEPS = [
10078
10374
  "stim worktree remove \"{tour}\"",
10079
10375
  "stim worktree remove \"{second}\""
10080
10376
  ]
10377
+ },
10378
+ {
10379
+ id: "delete",
10380
+ title: "Delete the Test App",
10381
+ who: "agent",
10382
+ optional: true,
10383
+ ask: TUTORIAL_ASKS.delete,
10384
+ section: "delete",
10385
+ commands: [
10386
+ "cd \"{base}\"",
10387
+ "grep stimTutorial app.json",
10388
+ "git worktree list",
10389
+ "stim stop",
10390
+ "stim worktree remove \"{base}\"",
10391
+ "cd ..",
10392
+ "rm -rf \"{base}\""
10393
+ ]
10081
10394
  }
10082
10395
  ];
10083
10396
  //#endregion
@@ -10090,6 +10403,23 @@ from a worktree of {base}, use stim ios --remote local --remote-build local,
10090
10403
  even when the user's settings would place the device or the build on another
10091
10404
  Mac. Only an explicit request to build on another Mac changes the build, with
10092
10405
  stim ios --remote local --remote-build "<machine>"; the device stays here.`;
10406
+ const screenshots = `Before and after screenshots, for a pull request: in each change's worktree,
10407
+ run the app on iOS before you edit anything, open it with agent-device and save
10408
+ the screen, then make the change and, once Fast Refresh applies it, save the
10409
+ same screen on the same device:
10410
+
10411
+ mkdir -p .expo/screenshots
10412
+ export AGENT_DEVICE_STATE_DIR="<agentDevice.stateDir from stim status --json>"
10413
+ agent-device open ${TUTORIAL_BUNDLE_ID} --platform ios --udid <ios.udid>
10414
+ agent-device screenshot .expo/screenshots/before.png
10415
+ (make the change)
10416
+ agent-device screenshot .expo/screenshots/after.png
10417
+
10418
+ Without agent-device, use xcrun simctl io <ios.udid> screenshot
10419
+ .expo/screenshots/before.png, using that worktree's udid from stim status --json,
10420
+ never booted. Take both with the same tool so they match in size. .expo/ is
10421
+ ignored and outside the native fingerprint, so the files are never committed,
10422
+ never change the build, and never block stim worktree remove. Keep each PNG under 1 MB.`;
10093
10423
  function commands(id) {
10094
10424
  return TUTORIAL_STEPS.find((step) => step.id === id).commands.join("\n");
10095
10425
  }
@@ -10114,7 +10444,7 @@ const TOPICS = {
10114
10444
  preamble: () => `STIM TUTORIAL
10115
10445
 
10116
10446
  The tutorial clones ${TUTORIAL_REPO}, a tiny Expo app, into {base}; the clone is
10117
- never run or removed. The user then asks for a visual change, and for another
10447
+ never run, and only the optional delete step removes it. The user then asks for a visual change, and for another
10118
10448
  change while the first builds. Each change runs in its own linked worktree of
10119
10449
  {base}, so each has its own simulator and Metro port, and the second worktree's
10120
10450
  first iOS build reuses the first one's native build. Only the clone, the
@@ -10126,6 +10456,7 @@ ${local}
10126
10456
 
10127
10457
  "Follow stim guide tutorial run" means stim guide tutorial run.
10128
10458
  "Follow stim guide tutorial finish" means stim guide tutorial finish.
10459
+ "Follow stim guide tutorial delete" means stim guide tutorial delete.
10129
10460
  "${TUTORIAL_RESTART_PROMPT}" means stim guide tutorial restart.
10130
10461
 
10131
10462
  ${paths}
@@ -10139,8 +10470,14 @@ For commands to type yourself, read stim guide tutorial manual.`,
10139
10470
 
10140
10471
  ${paths}
10141
10472
 
10142
- Folder safety: use a fresh folder. If {base} exists, stop and ask the user for
10143
- another folder; never overwrite or delete it. First check the parent folder:
10473
+ Folder safety: if {base} does not exist, clone into it. If it exists, reuse it
10474
+ only when all of these hold: app.json has expo.extra.stimTutorial equal to
10475
+ ${TUTORIAL_VERSION}, git remote get-url origin names ${TUTORIAL_REPO}, git
10476
+ rev-parse --show-toplevel equals {base}, and git status --porcelain prints
10477
+ nothing. Then skip the clone, install its dependencies (npm ci), run stim doctor
10478
+ --platform ios there, and continue at PAUSE. For any other existing folder (another
10479
+ repository, local changes, no tutorial marker), stop and ask the user for
10480
+ another folder; never overwrite or delete it. Before cloning, check the parent folder:
10144
10481
  create it if absent, then run git -C <parent> rev-parse --is-inside-work-tree.
10145
10482
  If that succeeds, the folder is inside another repository: stop and ask the
10146
10483
  user for another folder. Never git add in the user's repo.
@@ -10154,13 +10491,16 @@ ${TUTORIAL_VERSION}. If it does not, report the mismatch and stop; the user need
10154
10491
  Enter the clone and install its dependencies (npm ci) so the worktrees made for
10155
10492
  the changes inherit them, then run stim doctor --platform ios there: it registers
10156
10493
  the clone with Stim so Stim Desktop sees it, and builds or boots nothing. Do not
10157
- run the app: the clone is only the base for the user's changes and is never removed.
10494
+ run the app: the clone is only the base for the user's changes; only the optional
10495
+ delete step removes it.
10158
10496
  Report the findings of stim doctor and do not act on them: no SimSlim install, no
10159
10497
  --fix, nothing that changes the machine during the tutorial. On npm or network failure, report stderr
10160
10498
  and stop.
10161
10499
 
10162
10500
  ${local}
10163
10501
 
10502
+ ${screenshots}
10503
+
10164
10504
  PAUSE: end the turn. Tell the user to ask for a visual change next, such as
10165
10505
  making the title purple, in their own words. Each change runs in a new linked
10166
10506
  worktree of {base} (stim guide agent), and its first iOS build takes a few
@@ -10185,18 +10525,47 @@ changes, which is the consent stim guide agent asks for before worktree remove
10185
10525
  --force, for exactly those two paths: if the plain remove refuses one of them
10186
10526
  only because of uncommitted changes or commits found nowhere else, remove that
10187
10527
  worktree with --force. Never use --force on the clone or on any other
10188
- worktree, and on any other refusal report it and stop. Keep the clone. Print
10189
- these optional cleanup commands for the user; do not run them:
10190
-
10191
- rm -rf "{base}"
10528
+ worktree, and on any other refusal report it and stop. Keep the clone: the
10529
+ optional delete step removes it only when the user asks (stim guide tutorial
10530
+ delete).
10192
10531
 
10193
10532
  If archive is enabled, tell the user the worktrees appear under Archived in
10194
10533
  Stim Desktop. With archive disabled, report removal without promising an
10195
10534
  archive. Without Desktop, inspect stim status --json for the removed
10196
10535
  environments. End the turn.`
10536
+ },
10537
+ delete: {
10538
+ summary: "Remove the tutorial worktrees and the clone through Stim, then delete the folder",
10539
+ body: () => `DELETE THE TEST APP (OPTIONAL)
10540
+
10541
+ ${paths}
10542
+
10543
+ Only on the user's explicit request, which the delete prompt is. It covers
10544
+ {base} and its linked worktrees, nothing else. Remove them through Stim so their
10545
+ simulators, Metro ports and Stim records are torn down before the files go.
10546
+
10547
+ First check that {base}/app.json has expo.extra.stimTutorial. If it does not,
10548
+ {base} is not the tutorial clone: report it and stop.
10549
+
10550
+ List the worktrees with git -C "{base}" worktree list. For each linked worktree
10551
+ (every entry except {base} itself), run stim stop from its path, then
10552
+ stim worktree remove "<path>" from {base}. Never use --force here: the delete
10553
+ request does not say which changes the user can lose. If a remove refuses, report
10554
+ it and stop without deleting anything, so the user can decide about that
10555
+ worktree.
10556
+
10557
+ Then, from {base}, run stim stop and stim worktree remove "{base}". On the
10558
+ source checkout it reclaims the Stim environment and leaves the files. If it
10559
+ refuses, report it and stop. Only after it succeeds, from the parent folder,
10560
+ delete the clone:
10561
+
10562
+ rm -rf "{base}"
10563
+
10564
+ Never use --force in this step, and never delete any path other than {base}. Report
10565
+ what you removed and end the turn.`
10197
10566
  },
10198
10567
  share: {
10199
- summary: "Optionally open a public pull request with a screenshot of the change",
10568
+ summary: "Optionally open a public pull request with before and after screenshots of the change",
10200
10569
  body: () => `SHARE YOUR FINISH (OPTIONAL)
10201
10570
 
10202
10571
  Only on the user's explicit request, which the share prompt is, and before the
@@ -10204,31 +10573,50 @@ finish step removes the worktrees. The pull request is public: the user's GitHub
10204
10573
  name and change appear on ${TUTORIAL_REPO}, and a bot replies and closes it. It
10205
10574
  needs gh signed in. Never open it unprompted or from any other step.
10206
10575
 
10207
- Work from the worktree holding the user's change, {tour}. Commit the change
10208
- there, fork the repository and push the branch to the fork, since the user has
10209
- no write access:
10576
+ Work from the worktree holding the user's change, {tour}. Commit only the
10577
+ change's files there, never .expo/screenshots/. Fork the repository and push the
10578
+ branch to the fork, since the user has no write access:
10210
10579
 
10211
10580
  gh repo fork ${TUTORIAL_REPO} --remote --remote-name fork
10212
10581
  git push -u fork HEAD
10213
10582
 
10214
- Screenshot: take a PNG under 1 MB of the app showing the change, with
10215
- agent-device screenshot when it is installed, otherwise
10216
- xcrun simctl io <ios.udid> screenshot finish.png, using the udid of that
10217
- worktree from stim status --json, never booted. If the app is no longer
10218
- running, run it again from the branch first.
10219
-
10220
- Open the pull request with gh pr create --repo ${TUTORIAL_REPO} --fill. Put one
10221
- short line with the build time and whether the second worktree's first iOS build
10222
- was a cache hit, when you know them from stim status --json or stim stats, in the
10223
- commit message body (git commit -m "<title>" -m "<that line>"), so --fill carries it
10224
- into the pull request body.
10225
-
10226
- Attach the screenshot with gh: gh pr create --attach "finish.png#The change
10227
- running in the simulator" uploads the image and appends it to the body. gh
10228
- 2.99.0 (2026-09-01) has --attach; confirm with gh pr create --help rather than
10229
- guessing a version. When gh has no --attach, commit the PNG on the PR branch as
10230
- finish/<github-login>.png before pushing and embed it in the body with a
10231
- relative link: ![the change](finish/<github-login>.png).`
10583
+ Screenshots: use .expo/screenshots/before.png and .expo/screenshots/after.png from that
10584
+ worktree (stim guide tutorial run). If either is missing, take it now on the same
10585
+ device, that worktree's ios.udid from stim status --json, never booted, with the
10586
+ app running from the branch (run it again first if needed). For
10587
+ a missing before, show the original screen with
10588
+ git checkout origin/main -- <changed files>, capture it once Fast Refresh applies,
10589
+ then restore the change with git checkout HEAD -- <changed files> and capture the
10590
+ after again with the same tool, so both match in size.
10591
+
10592
+ Body: write .expo/screenshots/body.md from the clone's
10593
+ .github/pull_request_template.md, keeping its headings, table and Stim link and
10594
+ dropping its <!-- --> comments. An older clone without the template gets the
10595
+ same parts, in this order:
10596
+ - the first line: one sentence saying what changed and why;
10597
+ - the table, with ![Before](./.expo/screenshots/before.png) and
10598
+ ![After](./.expo/screenshots/after.png) in its cells;
10599
+ - a How it was verified section, with only what you observed: Device is ios.name from
10600
+ stim status --json; Readiness is the "app reported ready" time stim ios
10601
+ printed; Build is lastBuilds.ios.durationMs and whether lastBuilds.ios.cacheHit
10602
+ was local or remote (a cache hit) or false (a full build), plus, when you know
10603
+ it, whether the second worktree's first iOS build was a cache hit; Logs is the
10604
+ result of stim logs --errors. Drop a line you did not observe rather than
10605
+ guess it;
10606
+ - the closing line: Built and verified with [Stim](https://github.com/appandflow/stim).
10607
+
10608
+ Open the pull request from {tour}:
10609
+
10610
+ gh pr create --repo ${TUTORIAL_REPO} --title "<the one-line summary>" \\
10611
+ --body-file .expo/screenshots/body.md \\
10612
+ --attach ".expo/screenshots/before.png#Before" --attach ".expo/screenshots/after.png#After"
10613
+
10614
+ --attach uploads each image and rewrites the matching ./.expo/screenshots/ reference in
10615
+ the body to the uploaded file, so the table shows both images. gh 2.99.0
10616
+ (2026-09-01) has --attach; confirm with gh pr create --help rather than guessing
10617
+ a version. When gh has no --attach, open the pull request with --body-file alone
10618
+ and tell the user to drag the two images into the table on GitHub. Give the user
10619
+ the pull request URL.`
10232
10620
  },
10233
10621
  restart: {
10234
10622
  summary: "Start the tutorial again without removing anything",
@@ -10253,8 +10641,7 @@ ${paths}
10253
10641
  Replace {base}, {tour} and {second} with absolute paths: {base} is the clone,
10254
10642
  {tour} the first worktree and {second} the second. For Agent Actions, replace
10255
10643
  {stateDir} with agentDevice.stateDir from stim ios or stim status --json and
10256
- {udid} with the workspace's ios.udid. For the optional machine step, replace
10257
- {machine} with a machine you have already approved, or skip it. Clone with git
10644
+ {udid} with the workspace's ios.udid. Clone with git
10258
10645
  clone https://github.com/${TUTORIAL_REPO}.git into a fresh folder outside any
10259
10646
  repository. Each change runs in its own worktree of that clone (stim guide
10260
10647
  agent); the clone itself is never run. Run each worktree on iOS with