stim 1.20.2 → 1.22.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 (231) hide show
  1. package/README.md +141 -8
  2. package/dist/acquire-artifact-Co2k4f7O.mjs +171 -0
  3. package/dist/act-DsuqEkiM.mjs +2801 -0
  4. package/dist/activity-CMLmzp7o.mjs +1397 -0
  5. package/dist/{agent-device-usage-output-Dzgv6lgR.mjs → agent-device-usage-output-DUFaTMdd.mjs} +23 -67
  6. package/dist/{android-DCKZrNvV.mjs → android-ByRMUoiJ.mjs} +157 -93
  7. package/dist/{android-Mz_Inn3w.mjs → android-C7sVmyI9.mjs} +299 -116
  8. package/dist/{android-Dlzxshgp.mjs → android-DGkNqa_c.mjs} +5 -5
  9. package/dist/{android-HqP1_vB_.mjs → android-DR5lZ2Az.mjs} +2 -2
  10. package/dist/android-cas-compiler.mjs +2 -2
  11. package/dist/{android-cas-Bl-cLJBk.mjs → android-cas-ngferVq2.mjs} +7 -6
  12. package/dist/android-project-BCAAmMwz.mjs +12 -0
  13. package/dist/android-project-DVWyCbWU.d.mts +391 -0
  14. package/dist/api.d.mts +810 -64
  15. package/dist/api.mjs +782 -1
  16. package/dist/{artifact-HSwkIWEQ.mjs → artifact-8EY8SAPw.mjs} +240 -213
  17. package/dist/{artifact-D3czlUvT.mjs → artifact-DJPy518Y.mjs} +203 -164
  18. package/dist/artifact-platform-CXcmX_j3.mjs +27 -0
  19. package/dist/{browser-web-CA-F1GQo.mjs → browser-web-Bcv99PBM.mjs} +194 -64
  20. package/dist/budget-Cvwn54qV.mjs +9285 -0
  21. package/dist/{build-DYDbfkk5.mjs → build-CabC3G23.mjs} +153 -98
  22. package/dist/build-cache-Dv7RN200.mjs +602 -0
  23. package/dist/build-plan-DUC8Ka9y.mjs +248 -0
  24. package/dist/{cache-manifest-XH-fvDUj.mjs → cache-manifest-DSeQjt-Q.mjs} +2 -2
  25. package/dist/cache-manifest.mjs +1 -1
  26. package/dist/{ccache-zdKiYQ3E.mjs → ccache-Btwc36-I.mjs} +10 -9
  27. package/dist/{cdp-Dq_ypAlJ.mjs → cdp-3fwICjzi.mjs} +18 -5
  28. package/dist/{chrome-BnzZMqsR.mjs → chrome-Bbw689-6.mjs} +7 -4
  29. package/dist/{cli-CLP3diBm.mjs → cli-CHZa9Lvk.mjs} +20 -20
  30. package/dist/cli.mjs +1 -1
  31. package/dist/client-OyRoEuSr.mjs +1577 -0
  32. package/dist/collector-run.d.mts +4 -40
  33. package/dist/collector-run.mjs +9 -8
  34. package/dist/{command-output-BTy8hZ0F.mjs → command-output-gwpm28fu.mjs} +1 -1
  35. package/dist/config-DlrsduwG.mjs +955 -0
  36. package/dist/{created-devices-Dzd5C2LF.mjs → created-devices-x9rN6bhs.mjs} +14 -4
  37. package/dist/{dependency-state-DG0p31-y.mjs → dependency-state-1I9KZT1Y.mjs} +20 -5
  38. package/dist/{detached-entry-B9C_zeQC.mjs → detached-entry-BwNEtIrP.mjs} +1 -1
  39. package/dist/{dev-client-DAWVIGBu.mjs → dev-client-BSDR5h3F.mjs} +2 -2
  40. package/dist/{device-BECOiufl.mjs → device-B-L3Fbxy.mjs} +91 -34
  41. package/dist/device-DklWZLqb.mjs +1068 -0
  42. package/dist/{device-capacity-CBUAcRyQ.mjs → device-capacity-DK5_EwRS.mjs} +42 -13
  43. package/dist/device-host-worker.mjs +76 -79
  44. package/dist/{device-ios-COSi6oS1.mjs → device-ios-DoB6KFCO.mjs} +77 -51
  45. package/dist/{device-lease-run-Cw2M4z51.mjs → device-lease-run-xJPQ_QQG.mjs} +14 -26
  46. package/dist/{device-pool-Bf-pOhSw.mjs → device-pool-BMkdsefq.mjs} +5 -5
  47. package/dist/{doctor-kfMBnUw6.mjs → doctor-Bo7BXLux.mjs} +271 -320
  48. package/dist/{doctor-CB8oZKCk.mjs → doctor-D5_EzwfA.mjs} +186 -91
  49. package/dist/doctor-fingerprint-DZLoYFVb.mjs +16 -0
  50. package/dist/doctor-fingerprint.mjs +19 -0
  51. package/dist/doctor-thjWAV0z.mjs +2 -0
  52. package/dist/{doctor-watchman-BRyV2adA.mjs → doctor-watchman-N2wDotwQ.mjs} +40 -21
  53. package/dist/{eas-build-JyfqyHfC.mjs → eas-build-CwknaQYj.mjs} +53 -20
  54. package/dist/{build-plan-B7SZsc2N.mjs → eas-fallback-CSgAulr5.mjs} +41 -214
  55. package/dist/{watchman-CPQFZLXY.mjs → eas-session-ledger-DlY_serQ.mjs} +230 -11
  56. package/dist/{error-diagnostics-BR677IDM.mjs → error-diagnostics-DBWO2rxE.mjs} +172 -82
  57. package/dist/exec-DEoAhcze.mjs +858 -0
  58. package/dist/exec-DnYc4HGH.d.mts +78 -0
  59. package/dist/{gc-qWOMdnER.mjs → gc-CXxN-4SE.mjs} +1091 -1029
  60. package/dist/gc-DnNDQyaP.mjs +3 -0
  61. package/dist/{guide-G9cAbVAc.mjs → guide-Br0LEfKy.mjs} +1165 -297
  62. package/dist/{hosted-android-C3MHGld1.mjs → hosted-android-B-9f-jkd.mjs} +10 -8
  63. package/dist/{hosted-client-ZEKpS9vw.mjs → hosted-client-DZAPrcro.mjs} +34 -19
  64. package/dist/{hosted-ios-l-o3iM9J.mjs → hosted-ios-CsCS21hn.mjs} +1 -1
  65. package/dist/{hosted-logs-EzQM7-3Z.mjs → hosted-logs-FIRx9X2f.mjs} +77 -43
  66. package/dist/{hosted-macos-BACVAVqE.mjs → hosted-macos-_awYSpfv.mjs} +35 -22
  67. package/dist/{hosted-native-A4s1QPXe.mjs → hosted-native-BdzE668-.mjs} +49 -27
  68. package/dist/idle-C4cZ757K.d.mts +24 -0
  69. package/dist/{idle-shutdown-DtxhPTKe.mjs → idle-shutdown-DDg3SdvY.mjs} +11 -10
  70. package/dist/ios-CDvlmUs7.d.mts +24 -0
  71. package/dist/{ios-BwVCF7oA.mjs → ios-Ds1clbIt.mjs} +8 -8
  72. package/dist/{ios-device-D7pi2Sca.mjs → ios-device--h_-8ENa.mjs} +51 -26
  73. package/dist/{ios-device-B5veSDDQ.mjs → ios-device-BXZ0c7Kb.mjs} +1 -1
  74. package/dist/{ios-RkW4pnQf.mjs → ios-hcBpnEGk.mjs} +200 -93
  75. package/dist/{ios-Ce7bc7br.mjs → ios-pm4nL6By.mjs} +160 -108
  76. package/dist/{ios-project-K9hY_Jab.mjs → ios-project-CZhKODF3.mjs} +13 -13
  77. package/dist/ios-signing-BtAffm0A.mjs +489 -0
  78. package/dist/{ios-state-CBLe1xYw.mjs → ios-state-Dtw5K2aI.mjs} +1 -1
  79. package/dist/{ios-nOLJGVdy.mjs → ios-xtlRHaeB.mjs} +1 -1
  80. package/dist/{js-swap-Bb3nsS07.mjs → js-swap-Bwhh4S7V.mjs} +30 -25
  81. package/dist/{launch-C3qJ6Agb.mjs → launch-BjxemNpK.mjs} +85 -543
  82. package/dist/{launch-F4sOqAhB.mjs → launch-CCzXlc8r.mjs} +91 -90
  83. package/dist/logs-BfwwadsZ.mjs +479 -0
  84. package/dist/logs-jTS_lHSo.mjs +248 -0
  85. package/dist/{macos-D7uDvO-z.mjs → macos-DVlSSyUg.mjs} +1 -1
  86. package/dist/macos-NfV2D7dO.mjs +694 -0
  87. package/dist/macos-run.mjs +1 -1
  88. package/dist/maintenance-run.mjs +123 -248
  89. package/dist/{metro-DnCEjCxs.mjs → metro-DiXtA3Ty.mjs} +107 -30
  90. package/dist/metro-gateway-CKZqKqx7.mjs +209 -0
  91. package/dist/{named-ports-4WopRPg-.mjs → named-ports-DkEzOfzO.mjs} +42 -90
  92. package/dist/native-android-CqgnOgsT.mjs +645 -0
  93. package/dist/native-gradle-artifact-inputs-DHFnmrVf.mjs +270 -0
  94. package/dist/native-gradle-inputs-Dnh84Zbd.mjs +211 -0
  95. package/dist/native-inputs-TRTTTRxt.mjs +91 -0
  96. package/dist/native-run-NwUuxbkg.mjs +276 -0
  97. package/dist/{native-runtime-k8dTIrbK.mjs → native-runtime-B7KIafpM.mjs} +11 -9
  98. package/dist/native-source-Y0yLhFyQ.mjs +133 -0
  99. package/dist/{native-xcode-inputs-CykopJdM.mjs → native-xcode-inputs-D_VOfFL7.mjs} +26 -18
  100. package/dist/{native-xcode-ios-Bbf30Gm0.mjs → native-xcode-ios-BYDOHWLN.mjs} +104 -60
  101. package/dist/{ndjson-GOnbyvso.mjs → ndjson-CgE_o3Dl.mjs} +3 -3
  102. package/dist/offload-worker.d.mts +59 -41
  103. package/dist/offload-worker.mjs +478 -115
  104. package/dist/operation-exec-BW0em61G.mjs +171 -0
  105. package/dist/{ownership-DCtw_2IA.mjs → ownership-C5fo5Edb.mjs} +3 -3
  106. package/dist/{page-DRHy329N.mjs → page-DBL2PiUU.mjs} +12 -5
  107. package/dist/paths-BsJMoPP2.mjs +12 -0
  108. package/dist/paths-DQtTgsBf.mjs +42 -0
  109. package/dist/{placement-log-MOfPVgaM.mjs → placement-log-BnGgJRRZ.mjs} +5 -5
  110. package/dist/{plan-placement-V0TdzYXp.mjs → plan-placement-DIsMtN-d.mjs} +15 -11
  111. package/dist/{ports-DdSUicVo.mjs → ports-Cy1RkmuQ.mjs} +3 -3
  112. package/dist/prebuild-D4JoEAEs.mjs +484 -0
  113. package/dist/{project-_phNIJfs.mjs → project-wM_XFTvE.mjs} +65 -37
  114. package/dist/{pull-request-CQ8sPbZa.mjs → pull-request-W1IuLXDt.mjs} +102 -20
  115. package/dist/pull-request-eODyIqZF.d.mts +124 -0
  116. package/dist/pull-requests.d.mts +2 -120
  117. package/dist/pull-requests.mjs +2 -2
  118. package/dist/{react-native-android-CM1XRDwv.mjs → react-native-android-6OZ7MSM7.mjs} +121 -72
  119. package/dist/{gradle-DSrXb6lG.mjs → react-native-build-Dvd8hC21.mjs} +292 -15
  120. package/dist/react-native-doctor-DsoSF1Y9.mjs +234 -0
  121. package/dist/{react-native-ios-Dhq_eiYQ.mjs → react-native-ios-4u1Rn-GR.mjs} +71 -39
  122. package/dist/{recordings-D0d5aQt0.mjs → recordings-CQmfzAh7.mjs} +12 -9
  123. package/dist/registration-BcRgF6Ox.mjs +51 -0
  124. package/dist/{reload-C8JhxjSh.mjs → reload-BzkYabB-.mjs} +94 -47
  125. package/dist/reload-D7AVlb8N.mjs +2 -0
  126. package/dist/{remote-BiR6aYjy.mjs → remote-DjGDmlTZ.mjs} +26 -18
  127. package/dist/result-D2Ykz_iD.mjs +435 -0
  128. package/dist/{run-BtPgpKks.mjs → run-DidHghD1.mjs} +5 -5
  129. package/dist/selection-DSMbWEX2.mjs +620 -0
  130. package/dist/{server-bare-R2m_gsH8.mjs → server-bare-CafzuyFB.mjs} +3 -4
  131. package/dist/{server-command-CTtCkmcQ.mjs → server-command-C6QjeupS.mjs} +14 -7
  132. package/dist/{server-expo-DsguMGUF.mjs → server-expo-CrjB29pq.mjs} +54 -11
  133. package/dist/server-expo-D58c9L0M.mjs +2 -0
  134. package/dist/{settings-B717odTD.mjs → settings-C7O8A6ZI.mjs} +309 -119
  135. package/dist/settings-DpR-1Wnn.mjs +114 -0
  136. package/dist/settings-write-CzD60rOD.mjs +360 -0
  137. package/dist/settings.schema.json +176 -16
  138. package/dist/{simslim-CCr3rE-A.mjs → simslim-BNckWpXi.mjs} +14 -12
  139. package/dist/{slot-launch-DPt0m9Nd.mjs → slot-launch-CbfmNgM6.mjs} +15 -20
  140. package/dist/{spawn-entry-Dcb7sDUz.mjs → spawn-entry-jw19m8Nl.mjs} +0 -1
  141. package/dist/stage-Bps5oius.mjs +200 -0
  142. package/dist/{start-TXeMmmO2.mjs → start-Br-oxqMD.mjs} +1 -1
  143. package/dist/start-EojN4WFz.mjs +1364 -0
  144. package/dist/{state-D1Icyjlu.mjs → state--smjFSTL.mjs} +55 -13
  145. package/dist/{state-CG_OFoaA.mjs → state-08I3hw3_.mjs} +3 -2
  146. package/dist/{stats-BoY5sEPU.mjs → stats-D3GerlFW.mjs} +7 -13
  147. package/dist/stats-report-BzaPrRm6.mjs +16 -0
  148. package/dist/{status-BTA7kKiQ.mjs → status-BRROykC8.mjs} +7 -5
  149. package/dist/status-EvrK72L2.mjs +2 -0
  150. package/dist/{status-WgFbkRzj.mjs → status-WBDajew2.mjs} +554 -452
  151. package/dist/{status-watch-C23ILtf9.mjs → status-watch-Bg_u-pwR.mjs} +222 -25
  152. package/dist/status-worker.d.mts +1 -0
  153. package/dist/status-worker.mjs +19 -0
  154. package/dist/stim-desktop-D5OGpYZM.mjs +112 -0
  155. package/dist/{stop-DR6VoDIk.mjs → stop-DTZ9uTw3.mjs} +2 -2
  156. package/dist/{stop-DPEhunlw.mjs → stop-Dky10AEJ.mjs} +33 -23
  157. package/dist/{stop-BNmBMi1F.mjs → stop-Mv6W9FAk.mjs} +137 -98
  158. package/dist/supervisor-run.d.mts +8 -17
  159. package/dist/supervisor-run.mjs +56 -28
  160. package/dist/{support-CwjbFs9H.mjs → support-CNioewsd.mjs} +14 -14
  161. package/dist/{support-DMTqj3Hm.mjs → support-yUiOjnVY.mjs} +20 -13
  162. package/dist/swiftpm-artifact-inputs-DFcGsXDP.mjs +153 -0
  163. package/dist/swiftpm-macos-BIak-Rxv.mjs +532 -0
  164. package/dist/toolchain-BY8X8HXx.mjs +2 -0
  165. package/dist/toolchain-DpJMHq0n.mjs +876 -0
  166. package/dist/{trigger-CigAYjeL.mjs → trigger-Cbyc41d_.mjs} +21 -6
  167. package/dist/trigger-yb_nFzPF.mjs +2 -0
  168. package/dist/{warm-progress-BuY-Fvfn.mjs → warm-progress-CA3TgT3-.mjs} +263 -156
  169. package/dist/web-CZ07gd77.mjs +2 -0
  170. package/dist/{web-BDBMxDpx.mjs → web-DKx6yoFA.mjs} +20 -14
  171. package/dist/web-run.mjs +13 -12
  172. package/dist/windows-report.d.mts +1 -0
  173. package/dist/windows-report.mjs +18 -0
  174. package/dist/windows-report.ps1 +43 -0
  175. package/dist/{workspace-state-DJt_MMvf.mjs → workspace-state-U08dEzhG.mjs} +62 -58
  176. package/dist/{workspaces-C36s2jb9.mjs → workspaces-CpANmBlq.mjs} +1723 -1286
  177. package/dist/{worktree-CgvJYJ-Q.mjs → worktree-CHecZ4Oq.mjs} +1097 -547
  178. package/dist/worktree-ZqZVjD9K.mjs +2 -0
  179. package/dist/xcode-BXn9-xYx.mjs +896 -0
  180. package/package.json +7 -5
  181. package/shim/android-audio/Main.java +24 -0
  182. package/shim/android-audio/README.md +17 -0
  183. package/shim/android-audio.jar +0 -0
  184. package/shim/android-optimizations.gradle +7 -5
  185. package/dist/activity-BSV745Kz.mjs +0 -629
  186. package/dist/api-qvOzWyfO.mjs +0 -154
  187. package/dist/api-run.mjs +0 -294
  188. package/dist/app-install-BIKtMfPq.mjs +0 -896
  189. package/dist/artifact-lifecycle-DzPpBYvK.mjs +0 -30
  190. package/dist/budget-f7516Zk0.mjs +0 -4106
  191. package/dist/build-facts-DcLv2HSd.d.mts +0 -111
  192. package/dist/cancellation-C_22VQTH.mjs +0 -24
  193. package/dist/client-qQAaXSUs.mjs +0 -1771
  194. package/dist/config-BYDxlMY3.mjs +0 -562
  195. package/dist/device-DuiPJQSn.mjs +0 -290
  196. package/dist/device-lease-BX1sJQ2E.mjs +0 -1224
  197. package/dist/device-remote-xRT0Vmo9.mjs +0 -1272
  198. package/dist/eas-session-ledger-Cm0Gwq2F.mjs +0 -207
  199. package/dist/exec-DqKo9GKJ.mjs +0 -268
  200. package/dist/git-DDfFzkMm.mjs +0 -45
  201. package/dist/launch-verify-B6Q8hZP-.mjs +0 -428
  202. package/dist/logs-BGrEUaEs.mjs +0 -383
  203. package/dist/macos-Ch5VJmcS.mjs +0 -584
  204. package/dist/metro-gateway-CX9Xlkq2.mjs +0 -103
  205. package/dist/miss-reason-mKzZ4VvN.mjs +0 -238
  206. package/dist/native-android-D5qc3-_v.mjs +0 -226
  207. package/dist/native-gradle-inputs-BsLRrl7u.mjs +0 -349
  208. package/dist/ndjson-Du3j0AQ9.d.mts +0 -17
  209. package/dist/plan-C4TjdPsR.mjs +0 -260
  210. package/dist/prebuild-DYbucPXy.mjs +0 -212
  211. package/dist/preview-78a9kR_-.mjs +0 -1011
  212. package/dist/react-native-build-CtCccJtb.mjs +0 -239
  213. package/dist/react-native-doctor-CfRnFfjs.mjs +0 -78
  214. package/dist/remote-cache-OyOyVIS3.mjs +0 -526
  215. package/dist/result-CaJg5Iat.mjs +0 -186
  216. package/dist/server-expo-Bigriskl.mjs +0 -2
  217. package/dist/settings-BORNaaCO.mjs +0 -402
  218. package/dist/settings-vk3mJGSm.d.mts +0 -13
  219. package/dist/spawn-claims-DVS5RkMS.mjs +0 -72
  220. package/dist/stage-KNTQopqv.mjs +0 -157
  221. package/dist/start-BWqzODpb.mjs +0 -1116
  222. package/dist/stim-desktop-BzBN2Em2.mjs +0 -63
  223. package/dist/swiftpm-macos-DRNVJWlb.mjs +0 -119
  224. package/dist/tailnet-Dh-qiYzG.mjs +0 -265
  225. package/dist/toolchain-BFkAcstd.mjs +0 -2
  226. package/dist/toolchain-DxSsNssu.mjs +0 -1657
  227. package/dist/trigger-9_USgmxz.mjs +0 -2
  228. package/dist/web-Dq-i3wzJ.mjs +0 -2
  229. package/dist/worktree-DpLdCHga.mjs +0 -2
  230. package/dist/{api-run.d.mts → doctor-fingerprint.d.mts} +0 -0
  231. package/dist/{errors-DzkMYh8Q.mjs → errors-ccaPc4ox.mjs} +2 -2
@@ -1,8 +1,8 @@
1
- import { t as ANDROID_AVD_CONFIG_HELP } from "./settings-B717odTD.mjs";
2
- import { r as findProjectRoot } from "./project-_phNIJfs.mjs";
3
- import { s as RECENT_LAUNCH_MS } from "./watchman-CPQFZLXY.mjs";
4
- import { o as guideStatus, t as WATCHMAN_NESTED_WORKTREES } from "./doctor-watchman-BRyV2adA.mjs";
5
- import "./status-watch-C23ILtf9.mjs";
1
+ import { t as ANDROID_AVD_CONFIG_HELP } from "./settings-C7O8A6ZI.mjs";
2
+ import { i as findProjectRoot$1 } from "./project-wM_XFTvE.mjs";
3
+ import { h as RECENT_LAUNCH_MS } from "./eas-session-ledger-DlY_serQ.mjs";
4
+ import { o as guideStatus, t as WATCHMAN_NESTED_WORKTREES } from "./doctor-watchman-N2wDotwQ.mjs";
5
+ import "./status-watch-Bg_u-pwR.mjs";
6
6
  import { SETTINGS, SETTINGS_SCHEMA_URL, TUTORIAL_VERSION } from "@stim-cli/core/state";
7
7
  import chalk from "chalk";
8
8
  //#region src/guide/agent.ts
@@ -117,7 +117,7 @@ other apps or other workspaces' devices.
117
117
  stim ios # or: stim android, or: stim web
118
118
 
119
119
  When Stim Desktop is installed, worktree warm, start, ios, android and web
120
- print "Open in Stim Desktop: <link>" on stderr (links.desktop in JSON). The
120
+ print "Open in Stim: <link>" on stderr (links.desktop in JSON). The
121
121
  first time one prints it in a workspace, tell the user once: "You can watch
122
122
  this workspace in Stim Desktop: <link>". Do not repeat it after later
123
123
  commands. When no command prints it, do not mention Stim Desktop.
@@ -146,9 +146,9 @@ lifecycle builds.
146
146
  Agents never set STIM_ACCESS_TICKET, run stim-server setup, edit remote.*,
147
147
  server.* or hosting.* for the user, or approve requests. Read stim guide settings for
148
148
  Desktop access tickets and person-owned approval.
149
- Read stim guide settings to select a remote Mac. Keep a requested named
149
+ Read stim guide settings to select a remote machine. Keep a requested named
150
150
  placement when recovering STIM_OFFLOAD_REFUSED: report its reason and remedy.
151
- When the user mentions slow builds, a busy Mac or wanting updates away from the desk, tell them about Stim Desktop > Settings > Remote Macs > Add or Phones > Pair a Phone; see stim guide settings.
151
+ When the user mentions slow builds, a busy machine or wanting updates away from the desk, tell them about Stim Desktop > Settings > Remote Machines > Add or Phones > Pair a Phone; see stim guide settings.
152
152
 
153
153
  Read stim guide lifecycle concurrency when a build waits on another workspace
154
154
  or a build call times out. A native build can outlive a shell timeout; if the
@@ -260,6 +260,12 @@ holds it across runs; stim device unlock gives it back. Never delete another
260
260
  workspace's lease file under ~/.stim/device-locks; gc --delete removes expired
261
261
  ones.
262
262
 
263
+ stim device provision ios [udid] registers a phone with the project's Apple
264
+ Developer team and creates or refreshes its Development profile. It changes
265
+ the team's developer account, so never run it, and never pass it --yes,
266
+ without the user's explicit go-ahead for that step. When ios --device fails
267
+ on signing, report the remedy and ask the user.
268
+
263
269
  stop and worktree remove release this workspace's leases. On a physical
264
270
  iPhone, stop also closes the app by ending its log collector; it does not
265
271
  shut down the phone or uninstall the app.
@@ -412,7 +418,7 @@ FULL TOPIC LIST
412
418
  //#endregion
413
419
  //#region src/guide/api.ts
414
420
  var api_default = {
415
- summary: "Typed lifecycle API: createStim, build, run, stop, diagnostics, and cancellation",
421
+ summary: "Typed lifecycle API: createStim, build, run, reload, stop, warmWorktree, removeWorktree, diagnostics, doctor, GC, settings, stats, logs, and cancellation",
416
422
  body: () => `PROGRAMMATIC API
417
423
 
418
424
  Install stim as a project dependency: npm install --save-dev stim.
@@ -430,18 +436,156 @@ Import { createStim, StimError } from 'stim'. Node 22.12 or later is required.
430
436
  }
431
437
  }
432
438
 
433
- createStim takes projectRoot, optional absolute home and buildCache paths,
434
- and onProgress({ stream, message }). message is an output chunk. Without
435
- onProgress, operations write nothing to the caller's stdout or stderr. Each
436
- operation uses a bundled worker that calls the same lifecycle operations as
437
- the CLI; it never parses CLI arguments or changes the caller's cwd/environment.
439
+ createStim takes projectRoot, optional absolute home and buildCache paths, an
440
+ env object (default process.env) that Stim reads its settings overrides from
441
+ and passes to its child processes, and onProgress({ stream, message }). message is an
442
+ output chunk. Without onProgress, operations write nothing to the caller's
443
+ stdout or stderr. The home is fixed when the client is created: home, else
444
+ STIM_HOME from env, else the default home. All operations run in the calling
445
+ process against the client's home and project and call the same lifecycle
446
+ operations as the CLI. None parses CLI arguments or changes the caller's
447
+ cwd/environment.
448
+ Custom Expo buildCacheProvider modules are not supported. Use EAS or a Stim
449
+ cache provider.
450
+
451
+ settings() returns the stim settings --json payload: every setting with its
452
+ effective value, origin and per-layer values. An invalid environment override
453
+ rejects with StimError STIM_BAD_ARG and its remedy. stats() returns the
454
+ stim stats --json payload with the last recorded agent-device and SwiftPM cache
455
+ usage instead of measuring them again.
456
+
457
+ setSetting(key, text, { scope?, signal?, beforeWrite? }) and
458
+ unsetSetting(key, { scope?, signal?, beforeWrite? }) share CLI validation and
459
+ return its masked JSON write result. beforeWrite(workspace) runs once after
460
+ resolving the target and before mutation or recording-lock waits; throwing
461
+ refuses the write. Server adapters use it to admit that exact workspace.
462
+ Later registry changes do not redirect the write or its returned setting.
463
+ Pending writes cancel. After commit, recording deletion finishes before the
464
+ promise settles, including when cancellation arrives during cleanup.
465
+
466
+ plan({ platform, slot?, signal? }) returns the configured ios/android/macos next-build
467
+ report, matching --plan --json, without building, launching or taking a device.
468
+ slot applies to ios/android. A predicted build refusal stays in the report;
469
+ a failure to produce a report rejects with StimError. Fingerprinting uses the
470
+ existing cancellable report helper. Native probes and cache downloads drain
471
+ before the report settles. Plans use the report budgets below. Providers must settle on abort;
472
+ cleanup exceeding 35 seconds refuses with STIM_CLAIM_UNAVAILABLE and retains
473
+ the download directory rather than deleting files a provider may still write.
474
+
475
+ doctor({ platform? }) and gc({ cache?, olderThan?, worktrees? }) read reports in
476
+ process with a 60 second execution budget. They accept signal and attempt
477
+ owned cleanup before rejecting. Deadline expiry rejects with
478
+ STIM_REPORT_TIMEOUT; a caller abort rejects with STIM_CANCELLED. A thrown
479
+ onProgress callback cancels the report and is propagated unchanged.
480
+ If child identity cannot be verified after a one-second retry budget, cleanup
481
+ refuses with STIM_CLAIM_UNAVAILABLE instead of cancellation or timeout. The
482
+ child cleanup error names the launcher PID; unverifiable processes and descendants are left
483
+ untouched. On POSIX, a launcher that exited before identity capture can only finish
484
+ successfully once its process group is proven gone.
485
+ After a termination request, cleanup waits at most one second for a child to
486
+ close. Overall cleanup has a 35 second budget, including temporary-worktree
487
+ removal; unfinished cleanup returns STIM_CLAIM_UNAVAILABLE and leaves unresolved
488
+ resources in place. Credential refresh waits asynchronously and checks
489
+ cancellation before writing.
490
+ On Windows, report probes require the built-in Windows PowerShell for process-tree containment.
491
+
492
+ requestMachineAccess({ ticket?, signal? }) requests build and device-host access
493
+ from configured remote machines without granting capabilities or repairing project
494
+ state. It returns the iOS doctor report and records the completed project check
495
+ after owned cleanup. A supplied ticket encodes 32 random bytes; omitting it
496
+ generates or reuses a pending ticket instead of inheriting the process environment.
497
+ A person approves each capability on the worker. Execution has a 120 second
498
+ budget and cleanup uses the report's 35 second budget. Desktop requests access
499
+ only through its matching-home bundled server's private desktop-machine-access
500
+ subscription; cancellation waits for cleanup and reconnect never repeats it.
501
+
502
+ warmWorktree({ signal?, beforeWarm? }) copies missing ignored state from the source
503
+ checkout into the client's linked worktree in process. It returns { exitCode },
504
+ 0 or 1, without changing process.exitCode. Existing entries and tracked paths
505
+ remain untouched. Copies share a source read claim and hold an exclusive target
506
+ claim. beforeWarm(workspace) admits each canonical workspace in the resolved
507
+ warming scope before registration or copying; throwing refuses warming.
508
+ Progress uses onProgress. Cancellation drains the copy child before removing
509
+ its partial entry or releasing claims. Unknown cleanup retains those claims
510
+ and returns STIM_CLAIM_UNAVAILABLE with exact recovery paths. The execution
511
+ budget is 90 minutes; cleanup still has the 35 second budget above. The API
512
+ does not refresh the source or install dependencies; use CLI worktree warm
513
+ --refresh for that.
514
+
515
+ removeWorktree({ target?, force?, signal?, beforeRemove? }) runs the CLI's worktree removal
516
+ safety checks and cleanup in process. target defaults to projectRoot; relative
517
+ paths resolve against it. It returns { removed, exitCode } without changing
518
+ process.exitCode. removed is true once a linked Git worktree is deleted, even
519
+ if later branch cleanup fails. Source checkout environment cleanup can succeed
520
+ with removed: false and exitCode: 0. force has the same destructive meaning as
521
+ CLI --force. Cancellation stops pending work and waits for owned cleanup;
522
+ completed actions remain applied. It uses the report budgets above.
523
+ beforeRemove(workspace) runs for every canonical workspace in the resolved
524
+ cleanup scope before effects, including registered descendants and pending
525
+ branch cleanup. Throwing refuses the operation.
526
+
527
+ gc() stays read-only. collectGarbage({ cache?, olderThan?, worktrees?, delete?,
528
+ idleMs?, signal? }) runs the same GC selections and actions as the CLI, including
529
+ an unscoped sweep. It returns { report, exitCode }; exitCode is 0 or 1 and does
530
+ not change process.exitCode. delete: true enables deletion. idleMs reclaims
531
+ owned devices idle for that many milliseconds, including without delete.
532
+ The existing CLI rules for cache selection, workspace safety and scoped homes apply.
533
+
534
+ A deletion or idle sweep takes this home's exclusive maintenance claim;
535
+ concurrent maintenance rejects with STIM_CLAIM_REFUSED and names the held claim.
536
+ Execution and cleanup use the report budgets above. Cancellation stops pending
537
+ lock waits and further actions; completed actions remain applied. An archive
538
+ whose history has started moving finishes publication before cancellation.
539
+ Unresolved owned cleanup rejects with STIM_CLAIM_UNAVAILABLE and retains its
540
+ actual ownership claims for recovery. Progress uses onProgress; GC does not
541
+ replace the caller's console methods.
542
+
543
+ doctor returns the doctor --json findings and metadata, with runningPath null
544
+ because the caller imports the library rather than executing a Stim CLI. It
545
+ never repairs the project. By default it does not record a check; record: true
546
+ registers a supported iOS/Android project and records the checked platforms only
547
+ after the report and owned cleanup succeed. Recording waits asynchronously for
548
+ the config lock, honors signal, and rejects unavailable state. Existing remote approval
549
+ checks can refresh their own pending credential records. gc returns the GC
550
+ JSON dry-run report; it never deletes or shuts down idle devices. Report reads
551
+ can refresh their own measured-usage caches. Supplying home or STIM_HOME retains
552
+ the CLI's scoped-home restrictions on machine-global resources.
553
+
554
+ logs({ tail, sources, minLevel, slot, since, grep, errorsOnly }) returns the raw
555
+ stim logs --json records, including native crash capture, agent actions and Expo
556
+ error context. It runs in process, with asynchronous chunked reads and bounded
557
+ record selection when tail is supplied. A new file generation gets a compact
558
+ metadata index; later queries reuse it and index only appended bytes.
559
+ Matching agent actions are also selected within tail; attribution still scans
560
+ each retained session generation to establish device ownership.
561
+ followLogs(options, onRecord) emits the requested backfill followed by new records
562
+ and returns a stop function. AbortSignal also stops the subscription.
563
+ An optional third callback, onBackfill(records), receives initial records instead
564
+ of onRecord. Its promise is awaited before reading another batch; with that
565
+ callback and no tail, the timeline is selected in batches of at most 500.
566
+ Appends still go to onRecord.
567
+ Agent attribution still retains its matching records during this initial read.
568
+ Local appends use filesystem hints with a ten-second repair scan; while a
569
+ directory watch is unavailable, attachment and reads retry every 500 ms.
570
+ Attachment also schedules one 500 ms recovery scan for watch startup.
571
+ Expo context uses a settle timer only while an error is pending. The separate
572
+ agent-action cursor and active hosted log queries keep their current sampling.
438
573
 
439
574
  run takes platform plus its options:
440
- ios: configuration, scheme, deviceType, runtime
575
+ ios: configuration, scheme, deviceType, runtime, simulatorApp
441
576
  android: variant, systemImage, deviceProfile
442
- macos: remoteBuild
577
+ macos: remoteBuild, remote
443
578
  web: headed (default false)
444
- iOS and Android also accept slot, metroCheck, buildCache, and remoteBuild.
579
+ iOS and Android also accept slot, metroCheck, buildCache, remoteBuild, remote,
580
+ device, wait and easProfile. device: true auto-selects a physical device; a
581
+ string pins its UDID or serial. wait takes seconds as a string, such as '30'.
582
+ wait: false refuses a full owned-device slot immediately; on a physical device
583
+ it bypasses another workspace's lease and can replace its running app, as CLI
584
+ --no-wait does. easProfile downloads an existing compatible development build;
585
+ it never starts an EAS build. iOS simulatorApp selects the simulator viewer.
586
+ remote selects the launch host using the CLI's existing placement and approval
587
+ rules; remoteBuild independently selects where compilation runs. macOS requires
588
+ a named hosting Mac; auto, eas and proxy are not supported there.
445
589
  All methods accept signal. run builds, installs and launches.
446
590
  Web requires a running server, just like stim web.
447
591
 
@@ -449,6 +593,12 @@ build produces an artifact without acquiring a device, starting a runtime, or
449
593
  stopping an existing session. Its result is inferred from platform:
450
594
  const result = await stim.build({ platform: 'ios' });
451
595
  console.log(result.facts.appPath);
596
+ console.log(result.facts.links.workspace);
597
+
598
+ Successful builds include facts.links.workspace, an HTTPS link to this workspace
599
+ on this Stim home. Agents can share it with an already-paired phone or Desktop,
600
+ including when Desktop is not installed on the build machine. Opening it only
601
+ navigates; it does not install or launch the built app.
452
602
 
453
603
  Build options:
454
604
  ios: scheme, configuration, arch (arm64, x86_64, all), buildCache, remoteBuild
@@ -464,22 +614,64 @@ writes its own build-artifact-<platform>.ndjson. No distribution archive or web
464
614
  pipeline is inferred. Use @stim-cli/ci's buildCI to export a portable artifact and
465
615
  diagnostics for your CI provider.
466
616
 
617
+ Unresolved native Metro cleanup keeps error.details.cause.retainedClaimRoots and
618
+ STIM_CLAIM_UNAVAILABLE even after cancellation. Web cancellation stops pending
619
+ browser-lock and verification waits. A new browser launch is accounted for and
620
+ stopped before cancellation finishes, or refuses with retained ownership when
621
+ cleanup cannot be confirmed.
622
+
467
623
  run returns { platform, facts }. iOS facts include udid; Android includes
468
624
  serial; both include bundleId, appPath, metroPort, cacheHit and launched.
469
625
  macOS facts include bundle, bundleId, executable, pid, build and launched.
626
+ A hosted macOS result has a null local pid and preserves true or 'unverified'
627
+ from the host's launch evidence.
470
628
  Web facts contain the owned browser's existing launch and page facts.
471
629
  Do not interpret 'bundling' or 'unverified' as proven application readiness.
472
630
 
473
- stop({ slot? }) returns { ok, outcomes, summary }. It acts on the workspace,
474
- including resources from earlier runs. Use a dedicated workspace in CI.
475
- Cancellation waits for the operation worker to exit; stop with a fresh signal
631
+ start({ remote?, resetCache?, waitSeconds?, signal?, beforeStart? }) starts or
632
+ reuses the managed dev server, using the same settings and ownership rules as
633
+ stim start. remote requests the configured public URL or tunnel. resetCache
634
+ restarts the server with a cleared transform cache. waitSeconds is a positive
635
+ whole number, defaulting to 60. The flat result is port, supervisorPid, mode,
636
+ logsDir, agentDevice and alreadyRunning.
637
+ beforeStart(workspace) runs once with the resolved app before usage recording
638
+ or effects; throwing refuses startup. Cancellation stops pending probes and lock
639
+ waits, then accounts for new children before returning. A verified supervisor
640
+ handed off to the workspace may remain running. Unconfirmed cleanup refuses with
641
+ STIM_CLAIM_UNAVAILABLE, taking precedence over cancellation or callback failure;
642
+ follow its remedy before retrying. Startup never retries automatically.
643
+
644
+ reload({ platform?, signal?, beforeReload? }) requests a JavaScript reload in
645
+ the live iOS/Android app or reloads the owned Chrome page. It returns the flat
646
+ stim reload --json facts: platform, deviceId, deviceName, appId, metroPort,
647
+ strategy, targets and reverseRestored. Choose ios, android or web when more
648
+ than one platform is live. A request does not prove completion; verify the UI
649
+ and inspect logs. A Metro broadcast can reach other connected clients.
650
+ beforeReload(workspace) runs once after resolving the workspace and before
651
+ usage recording or dispatch; throwing refuses the action. Reload uses the
652
+ 60 second execution and 35 second cleanup budgets described above, including
653
+ STIM_REPORT_TIMEOUT and STIM_CLAIM_UNAVAILABLE. Cancellation stops pending
654
+ probes and dispatch, but cannot retract a reload already sent. It never retries.
655
+
656
+ stop({ slot?, beforeStop? }) returns { ok, outcomes, summary }. It acts on the
657
+ workspace, including resources from earlier runs. Use a dedicated workspace in CI.
658
+ beforeStop(workspace) runs once with the resolved workspace before teardown;
659
+ throwing refuses the operation.
660
+ From a child directory, start and stop resolve the enclosing supported app.
661
+ Cancellation drains the operation's owned work, a read kills its child
662
+ processes, and both reject with STIM_CANCELLED. Cancelling stop while it waits
663
+ for another native operation ends that wait. Once teardown starts, stop finishes
664
+ cleanup before rejecting with STIM_CANCELLED. Its config, workspace-state, and lease lock waits are asynchronous.
665
+ Stop with a fresh signal
476
666
  after cancellation or partial failure. Always inspect cleanup.ok.
477
667
 
478
668
  diagnostics({ tail: 200, errors: false }) returns { directory, records } from
479
669
  the local timeline, even before a successful run. tail: 0 returns paths only.
480
670
  It does not fetch remote logs or capture new crashes. Stim failures reject with
481
671
  StimError carrying code, message, remedy and details (including the log path).
482
- An exception thrown by onProgress cancels its worker and propagates unchanged.
672
+ An exception thrown by onProgress stops further progress and propagates
673
+ unchanged: build or run is cancelled, and stop or a read
674
+ finishes its work first.
483
675
 
484
676
  Normal ownership, device creation, cache locks and coordination remain active.
485
677
  All concurrent artifact-cache writers must share the same coordinating home.
@@ -667,10 +859,10 @@ leased until <time>" for each one.`,
667
859
  agentDevice { stateDir }: absolute workspace agent-device state path;
668
860
  set AGENT_DEVICE_STATE_DIR to it (see guide logs)
669
861
  alreadyRunning true when nothing needed starting
670
- links { desktop }: a stim-desktop://workspace?path=<workspace>
671
- link that shows this workspace in Stim Desktop. Absent when
672
- no app on this Mac opens stim-desktop links. Plain output
673
- prints it on stderr as "Open in Stim Desktop: <link>"; so
862
+ links { desktop }: an HTTPS link containing this Stim home and
863
+ workspace IDs, opened by Desktop here or an already-paired phone. Absent when
864
+ no app on this machine opens stim-desktop links. Plain output
865
+ prints it on stderr as "Open in Stim: <link>"; so
674
866
  does worktree warm, which has no JSON payload
675
867
 
676
868
  stim macos --json
@@ -733,7 +925,7 @@ leased until <time>" for each one.`,
733
925
  configuration is part of it: -release-sim vs
734
926
  -debug-sim-arm64). A single-architecture simulator build
735
927
  ends in its architecture (-arm64 or -x86-64): a local Debug
736
- build compiles this Mac's, a --remote build the remote
928
+ build compiles this machine's, a --remote build the remote
737
929
  host's. A local non-Debug simulator build compiles every
738
930
  architecture the project lists and has no suffix
739
931
  With --eas-profile, fingerprint is computed by EAS CLI using the selected
@@ -756,7 +948,7 @@ leased until <time>" for each one.`,
756
948
  looked up", which is a different fact from "nothing was found"
757
949
  buildMachine selected auto, local or machine name, including cache hits
758
950
  builtOn here or machine name when a build ran; absent otherwise
759
- offloadedTo only on an app a remote Mac compiled: its
951
+ offloadedTo only on an app a remote machine compiled: its
760
952
  remote.machines entry (see \`guide settings\`). cacheHit is
761
953
  false for it. The Android payload carries it too
762
954
  offloadFallback only on an app compiled here after the run considered
@@ -909,7 +1101,7 @@ leased until <time>" for each one.`,
909
1101
  sets its own CMake compiler launcher, or a Gradle run whose
910
1102
  native work was all up to date. None of the three is an
911
1103
  error, and this field is separate from cacheHit. On an
912
- offloaded APK it is the remote Mac's ccache
1104
+ offloaded APK it is the remote machine's ccache
913
1105
  logs the workspace log directory
914
1106
  agentDevice { stateDir }: absolute workspace agent-device state path
915
1107
  durationMs wall time for the whole run
@@ -1010,21 +1202,39 @@ leased until <time>" for each one.`,
1010
1202
  freedMb, and is empty while under budget
1011
1203
  deviceHosts one { machine, state, dnsName?, deviceId?, requestedAt?, expiresAt?,
1012
1204
  host? } per remote.machines entry. state is "approved",
1013
- "pending", "not-asked", "revoked", "lapsed", "node-changed", "not-on-tailnet",
1014
- "tailscale-off", "unreachable", "invalid",
1015
- "credentials-unavailable" or "busy". Tokens stay private.
1016
- Only --fix asks for access or forgets removed names.
1017
- A remote Mac approved for device hosting does not imply
1205
+ "pending", "not-asked", "revoked", "lapsed", "key-changed", "outdated",
1206
+ "not-on-tailnet", "tailscale-off", "unreachable", "invalid",
1207
+ "credentials-unavailable" or "busy". Setup tickets stay private.
1208
+ Only --fix asks for access or forgets removed names. A
1209
+ peer that Tailscale reports as running an OS other than
1210
+ macOS is never asked or contacted and reports
1211
+ "not-asked" with no finding, even with a saved approval,
1212
+ since it can never host.
1213
+ A remote machine approved for device hosting does not imply
1018
1214
  build approval; see \`guide settings\`.
1019
1215
  host is { name, screenRecording, accessibility }, present
1020
1216
  only for approved machines whose worker reports it.
1021
- expiresAt is when a pending request lapses; "lapsed" is
1022
- a pending request whose expiry passed. --fix asks again.
1217
+ expiresAt is when a pending request's setup ticket
1218
+ expires, absent for a Stim Desktop ticket; "lapsed" is a
1219
+ pending request whose expiry passed. --fix asks again.
1220
+ "key-changed" means the worker did not prove the
1221
+ stim-server key pinned at approval. "outdated" means its
1222
+ stim-server speaks protocol 1; the finding names the
1223
+ stim-server service update command to run there.
1224
+ toolchain { entries, projectNode } on macOS, else null. entries is one { name, value,
1225
+ source, from? } for node, ANDROID_HOME, JAVA_HOME, stim,
1226
+ brew and adb: source "environment" when this process's
1227
+ environment provides it, "discovered" when stim-server's
1228
+ toolchain discovery would find it (from says where), or
1229
+ "missing". projectNode is { pin, spec, bin, version } when
1230
+ .nvmrc, .node-version, .tool-versions or package.json volta
1231
+ pins Node (bin and version are null when no install
1232
+ satisfies the pin), else null
1023
1233
  remoteMachines one { machine, state, dnsName?, deviceId?, requestedAt?,
1024
1234
  expiresAt?, offloadable?, reasons?, problems?, capacity?, host? } per
1025
1235
  remote.machines entry, for its build approval; state is "approved", "pending", "not-asked",
1026
1236
  "revoked" (revoked or denied), "lapsed" (the request lapsed
1027
- before approval), "node-changed",
1237
+ before approval), "key-changed", "outdated",
1028
1238
  "not-on-tailnet", "tailscale-off", "unreachable" or
1029
1239
  "invalid". host has the same shape and presence rule as in
1030
1240
  deviceHosts, as do expiresAt (a pending request's lapse
@@ -1033,13 +1243,15 @@ leased until <time>" for each one.`,
1033
1243
  simulator unless --platform android, Android emulator when
1034
1244
  --platform android or the app has android/ or uses Expo,
1035
1245
  plus macOS SwiftPM when Package.swift exists and no platform
1036
- is selected on a Mac), and reasons, each reason it would not,
1246
+ is selected on a Mac; a platform the machine cannot build at
1247
+ all, such as iOS on Linux, is left out while it can build
1248
+ another), and reasons, each reason it would not,
1037
1249
  prefixed "iOS: ", "Android: " or "macOS: " when only one platform
1038
1250
  has it, with a finding
1039
1251
  per reason (code build-machine-<reason>: unreachable,
1040
1252
  checkout, stim-build, arch, xcode, simulator-sdk, macos-sdk,
1041
1253
  cocoapods, bundler, runtime, jdk, android-sdk, ndk,
1042
- build-tools, compile-sdk, disk or busy). problems lists the same
1254
+ build-tools, compile-sdk, disk, busy or platform). problems lists the same
1043
1255
  reasons as { code, reason } with that code. capacity
1044
1256
  is the machine's offer: { running, max, diskFreeBytes,
1045
1257
  minDiskFreeBytes, cpus?, loadPerCore?, builds?, maxBuilds?,
@@ -1050,7 +1262,7 @@ leased until <time>" for each one.`,
1050
1262
  memory-culprit is a costs-time finding naming a process
1051
1263
  with an abnormally large footprint and, when it is safe to
1052
1264
  restart, the command
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
1265
+ 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 Machines > Add
1054
1266
 
1055
1267
  ON FAILURE
1056
1268
  \`start\`, \`ios\`, \`android\` and \`web\` all print the error contract instead,
@@ -1067,8 +1279,8 @@ ON FAILURE
1067
1279
 
1068
1280
  An \`ios\` or \`android\` run that \`stim stop\` or Ctrl-C interrupted
1069
1281
  reports STIM_CANCELLED and exits 130. A
1070
- run that exits on the interrupt at once (a second SIGINT, a physical-device
1071
- lease, Ctrl-C with no build tool running) exits 130 with no payload.
1282
+ run that exits on the interrupt at once (a second Ctrl-C, or Ctrl-C with no
1283
+ build tool or SimSlim to stop) exits 130 with no payload.
1072
1284
 
1073
1285
  \`stop --json\` prints { root, ok, supervisor, collectors, metro, device,
1074
1286
  port, metroTunnel, releasedLeases, macos? }. macos is { status: stopped | failed,
@@ -1267,7 +1479,9 @@ RULES
1267
1479
  deviceRecord,
1268
1480
  workspaceOutputs, recording, workspaceDirectory, project,
1269
1481
  buildLock, buildSlot, deviceLease, easSession, worktree,
1270
- cache.
1482
+ cache, gradleVersion, tempLeftover (one "done" labelled
1483
+ with the TMPDIR it cleared, plus a "kept" or "failed" per
1484
+ entry gc checked again or could not delete).
1271
1485
  label is a device, path or cache name; id is the UDID,
1272
1486
  AVD name or path behind it, or null. Under --cache
1273
1487
  parked, a "done" parkedDevice was erased and stays
@@ -1316,7 +1530,7 @@ RULES
1316
1530
  whole workspace directory
1317
1531
  linkedWorktrees { path, idleDays, mergedInto, pullRequest,
1318
1532
  pullRequestUnknown, willRemove, reason,
1319
- detail, eligibleAt } mergedInto is the
1533
+ detail, eligibleAt, buildState } mergedInto is the
1320
1534
  default branch HEAD is merged into
1321
1535
  ("origin/main"), or null. pullRequest is the
1322
1536
  branch's pull request { number, state:
@@ -1327,8 +1541,33 @@ RULES
1327
1541
  origin/main", "PR #12 closed", "idle 9d") or
1328
1542
  kept. eligibleAt is the ISO time a
1329
1543
  recent-activity worktree becomes removable,
1330
- else null. Without --worktrees, the source
1331
- checkout and roots outside git are left out
1544
+ else null. buildState is { bytes, dirs:
1545
+ { path, bytes } } for the in-tree build and
1546
+ dependency directories automatic maintenance
1547
+ clears (ios/build, android/app/build,
1548
+ android/build, android/.gradle, .build,
1549
+ node_modules, ios/Pods, at the root, a
1550
+ project root, or apps/* and packages/* for
1551
+ build directories) and a root .pnpm-store,
1552
+ paths relative to the worktree, largest
1553
+ first; du counts APFS clones in full. A bytes
1554
+ is null when du could not measure it within
1555
+ 10 seconds; sizes are cached for up to an
1556
+ hour while no entry of the directory was
1557
+ added or removed. null
1558
+ for a root outside git. Without --worktrees,
1559
+ the source checkout and roots outside git
1560
+ are left out
1561
+ unlistedWorktreeDirectories
1562
+ { path, repository, bytes, remedy } a
1563
+ directory directly under a repository's
1564
+ .worktrees/ that \`git worktree list\` does
1565
+ not show and that has no .git, or a .git file
1566
+ whose gitdir is gone; a worktree of another
1567
+ repository is left out. bytes is null when du
1568
+ fails. Report-only, never deleted: it may
1569
+ hold work. remedy is the rm -rf command to
1570
+ run after checking it
1332
1571
  parkedSimulators { udid, name, model, runtime, parkedAt, app,
1333
1572
  bytes, listed } app is the bundle id recorded
1334
1573
  at park, else null; with --older-than, only
@@ -1351,6 +1590,20 @@ RULES
1351
1590
  for AVD data with no registration)
1352
1591
  staleDevices { kind, id, name, project, slot, idleDays,
1353
1592
  bytes } only with --older-than
1593
+ finishedDevices { kind, id, name, project, slot, bytes, reason }
1594
+ owned devices of a workspace whose linked
1595
+ worktree gc keeps although its branch is
1596
+ merged or its pull request ended; --delete
1597
+ deletes them unless the workspace is in use,
1598
+ leased, driven, locked, pinned by
1599
+ maintenance.keep or used within
1600
+ gc.worktreeGraceMinutes
1601
+ ownedDeviceDisk { kind, count, bytes, unmeasured } disk of the
1602
+ owned simulators and emulators of registered
1603
+ workspaces, per platform; unmeasured counts the
1604
+ ones whose size could not be read; parked
1605
+ devices are under parkedSimulators and
1606
+ parkedEmulators
1354
1607
  staleDeviceRecords { kind, id, project, slot } --delete clears
1355
1608
  the record only
1356
1609
  staleLedgerEntries { kind: "ios" | "android" | "web", id }
@@ -1412,6 +1665,51 @@ RULES
1412
1665
  null with any --cache scope
1413
1666
  swiftpmCache <SwiftpmCacheUsage|null> report-only, never deleted;
1414
1667
  null with any --cache scope
1668
+ gradleVersions { home, olderThanDays, blocked, gradleRunning,
1669
+ gradleRunningPids, versions: [{ version, dirs,
1670
+ bytes, lastModifiedAt, willDelete, reason,
1671
+ detail }], leftovers } | null; only with
1672
+ --cache gradle-versions. blocked is null or
1673
+ { reason, detail } with machine-global,
1674
+ no-registry, unsafe-layout, listing-failed,
1675
+ unreadable-project, gradle-running or
1676
+ processes-unknown; gradleRunning is the
1677
+ gradle-running detail or null and
1678
+ gradleRunningPids the pids behind it; a
1679
+ version's reason is null, one of those,
1680
+ not-a-directory, walk-failed, too-many-entries
1681
+ or recently-modified. leftovers lists
1682
+ .stim-delete-* directories --delete finishes
1683
+ removing
1684
+ machineCaches { measuredAt, complete, notices, gradle, simRuntimes,
1685
+ deviceSupport, runnerBuilds } | null
1686
+ report-only, never deleted; null with any
1687
+ --cache scope. gradle: { home, unusedVersions:
1688
+ [{ version, bytes, dirs, remedy }],
1689
+ usedVersions: [{ version, projects }],
1690
+ unreadableProjects, modules }. simRuntimes:
1691
+ { checked, runtimes: [{ identifier,
1692
+ runtimeIdentifier, version, build, bytes,
1693
+ deletable, lastUsedAt, devices, remedy }] }.
1694
+ deviceSupport: { entries: [{ dir, bytes }],
1695
+ remedy }. runnerBuilds: { agentDeviceVersion,
1696
+ xcodeBuildVersion, groups: [{ packageVersion,
1697
+ xcodeBuildVersion, current, entries, inUse,
1698
+ bytes, oldestUseAt }], remedy }. Sizes and
1699
+ versions come from
1700
+ $STIM_HOME/machine-cache-usage.json, at most
1701
+ 10 minutes old
1702
+ tempLeftovers { root, groups: [{ kind, count, bytes,
1703
+ deleteCount, deleteBytes, kept: [{ reason,
1704
+ count }], remedy }] } | null recognized
1705
+ entries directly in TMPDIR (root, resolved).
1706
+ kind is stim (stim-claim-*, stim-host-*,
1707
+ stim-fakehome-*, stim-hosted-macos-*,
1708
+ stim-server-*, stim-ws-* directories), xctrace
1709
+ (instruments*.ktrace) or jest (Jest's
1710
+ jest_<uid in base 36> cache, never deleted). deleteCount
1711
+ is what --delete removes. null with STIM_HOME
1712
+ set or any --cache scope
1415
1713
  caches { name, dir, source, bytes, note, willEmpty,
1416
1714
  emptySkipped, scopedEmpty } alive, not
1417
1715
  garbage; willEmpty marks the ones --delete
@@ -1454,7 +1752,8 @@ RULES
1454
1752
  stim-server
1455
1753
  linkedWorktrees not-a-worktree | bare-repository |
1456
1754
  source-checkout-unknown | source-checkout |
1457
- locked | in-use | status-unreadable | dirty |
1755
+ locked | nested-worktree | in-use |
1756
+ status-unreadable | dirty |
1458
1757
  unpushed-unchecked | unpushed | submodules |
1459
1758
  not-merged | merge-unknown | last-use-unknown |
1460
1759
  recently-used | activity-unknown |
@@ -1798,7 +2097,7 @@ RULES
1798
2097
  device: its boot, adoption cleanup, or a physical
1799
2098
  device's lease and connection check. A boot that
1800
2099
  finishes during the build adds no device time. For a
1801
- device hosted on another Mac, device covers reserving
2100
+ device hosted on another machine, device covers reserving
1802
2101
  and preparing it there, install covers delivering the
1803
2102
  app, and launch starts when the host launches it. An
1804
2103
  --eas-profile run has no cache lookup, so its outcome
@@ -1940,7 +2239,7 @@ RULES
1940
2239
  lookup, and create no lastBuild record or failed-run stats.
1941
2240
  buildMachine selected auto, local or configured machine entry; always on new build records
1942
2241
  builtOn here or machine name; absent when no build ran (cache/refusal)
1943
- offloadedTo only on a run a remote Mac compiled: its name
2242
+ offloadedTo only on a run a remote machine compiled: its name
1944
2243
  offloadFallback
1945
2244
  only on a run that considered offloading and built here: one
1946
2245
  reason per machine when none took the build, or why the
@@ -2180,7 +2479,7 @@ RULES
2180
2479
  baseline. rekeyedBy is empty.
2181
2480
  placement with ios.remote or android.remote set to auto or a Mac,
2182
2481
  where the plan assumes the device runs: "on <machine>" or
2183
- "this Mac; auto may use <machines>"; absent otherwise
2482
+ "this machine; auto may use <machines>"; absent otherwise
2184
2483
  refusal { code, message, remedy } when the run would refuse:
2185
2484
  STIM_PREBUILD_FAILED for a tracked native dir the fingerprint
2186
2485
  leaves out, STIM_EAS_BUILD_MISSING for an EAS miss
@@ -2212,7 +2511,7 @@ RULES
2212
2511
  A bucket carries runs, failed, hits, misses, coldRuns, coldRunMs, hitRuns,
2213
2512
  hitRunMs, timeSavedMs, firstRunAt and lastRunAt, plus lastColdBuildMs and
2214
2513
  lastPodsMs once the project has compiled or installed pods, and
2215
- offloadedRuns, offloadedRunMs and lastOffloadHost once a remote Mac
2514
+ offloadedRuns, offloadedRunMs and lastOffloadHost once a remote machine
2216
2515
  compiled one. An offloaded run is a miss but not a cold run, so the cold
2217
2516
  average, time saved and build estimates stay local. Milliseconds are
2218
2517
  integers.
@@ -2332,10 +2631,10 @@ BUILD PLACEMENT (\`offload\`)
2332
2631
  decision, reason, machine?, buildMs?, slotWaitMs?, deviceSlotWaitMs?, localEstimateMs?, failed? }.
2333
2632
  slotWaitMs and deviceSlotWaitMs are whole milliseconds waiting for build
2334
2633
  and owned-device slots respectively, present only when positive.
2335
- decision is "here", "offloaded", or "fell-back" (it tried a remote Mac
2634
+ decision is "here", "offloaded", or "fell-back" (it tried a remote machine
2336
2635
  and built here). reason is why: the run's \`placement:\` reason, such as
2337
2636
  "load 0.6/core, 1 of 3 build slots busy here" (auto keeps the build here
2338
- while this Mac has room) or "remote.buildMode is off" (which prints no line),
2637
+ while this machine has room) or "remote.buildMode is off" (which prints no line),
2339
2638
  or for a fallback the cause, prefixed by the machine it concerns, such as
2340
2639
  "mini: no less loaded (load 1.2/core there, 0.6/core here)". buildMs is the compile here, or the offloaded build's
2341
2640
  total (offer, sync, build and fetch); localEstimateMs is this project's
@@ -2344,7 +2643,7 @@ BUILD PLACEMENT (\`offload\`)
2344
2643
  placements lists them newest first: the last 100 from the last 7 days.
2345
2644
  Per machine, \`total\` keeps { offloaded, offloadedMs, savedMs, fallbacks,
2346
2645
  lastOffloadAt?, lastFallbackAt? } for good and \`today\` counts { offloaded,
2347
- offloadedMs, savedMs, fallbacks } from the placements on this Mac's local
2646
+ offloadedMs, savedMs, fallbacks } from the placements on this machine's local
2348
2647
  calendar day. savedMs sums localEstimateMs minus buildMs over offloaded
2349
2648
  builds that had an estimate, so it is an ESTIMATE and is negative when the
2350
2649
  machine was slower. \`offload.today\` counts the day's placements by
@@ -2382,7 +2681,7 @@ comes from \`start --remote\`, eas/proxy in \`ios.remote\`, or \`android.remote\
2382
2681
  HOSTED IOS METRO
2383
2682
  ios --remote <machine> keeps Metro local and needs no start --remote.
2384
2683
  The supervisor watches private gateway requests in workspace state, binds
2385
- only this Mac's Tailscale address and accepts only the pinned hosting peer
2684
+ only this machine's Tailscale address and accepts only the pinned hosting peer
2386
2685
  with a per-session secret. The host's bridge is loopback-only. Port and secret
2387
2686
  stay in 0600 workspace state and never appear in status, JSON or logs.
2388
2687
  metro.tunnel and metro.publicUrl are ignored. Supervisor restart recreates
@@ -2522,6 +2821,12 @@ WHAT THE SUPERVISOR IS
2522
2821
  launches stay UNVERIFIED: read \`stim logs --source metro\`
2523
2822
  for the bundle lines.
2524
2823
 
2824
+ expo-child and command-child put the Node the app pins (.nvmrc,
2825
+ .node-version, .tool-versions nodejs, or package.json volta.node) first
2826
+ on PATH when an nvm, fnm, Volta, asdf or mise install satisfies the pin
2827
+ and the node already on PATH does not; the metro log then records a
2828
+ project_node event naming it. bare-inproc runs on the supervisor's Node.
2829
+
2525
2830
  In expo-child mode, remote intent plus metro.tunnel "expo" makes \`start\` pass
2526
2831
  \`--tunnel\` and EXPO_UNSTABLE_TUNNEL_V2=1 (the legacy ws-tunnel path is
2527
2832
  locked to port 8081, which every reserved port but the first collides with)
@@ -2829,6 +3134,18 @@ FLAGS
2829
3134
  mode only, on stderr. With --errors, an Expo error record
2830
3135
  also carries context (see THE RECORD).
2831
3136
 
3137
+ Local follow uses filesystem change hints with a ten-second repair scan for
3138
+ missed events or a replaced directory. Until a directory watch is available,
3139
+ it retries and scans every 500 ms, with one recovery scan 500 ms after attachment.
3140
+ On Windows a moved log directory can require the ten-second repair scan.
3141
+ Expo context settling uses a timer only
3142
+ while an error is pending; otherwise idle files are not repeatedly scanned.
3143
+ Local agent actions also follow their state and claim directories, coalescing file
3144
+ changes instead of re-reading every 500 ms while idle.
3145
+ While following, hosted targets join and leave from local state notifications.
3146
+ Missed notifications are reconciled about every 10 seconds. Active hosts still
3147
+ serve bounded log queries.
3148
+
2832
3149
  --ERRORS, PRECISELY
2833
3150
  Level error or fatal, from metro, client and build, plus device records with
2834
3151
  event native_crash (app/device/time-correlated OS reports or fatal app console output)
@@ -2916,7 +3233,7 @@ FLAGS
2916
3233
  responses, do not cut the stack short. Human output joins the lines to the
2917
3234
  message. --json leaves msg as captured and adds them to the error record as
2918
3235
  a context array of strings; each record is still one line. --follow holds an
2919
- Expo error until a poll, about every 500 ms, adds no more of its lines, so
3236
+ Expo error until a 500 ms settle check adds no more of its lines, so
2920
3237
  it can print up to about a second after Expo does. Non-follow human output
2921
3238
  also shows bare React Native symbolication as separate context, because
2922
3239
  Metro does not provide an error correlation identifier. Context never
@@ -2965,16 +3282,16 @@ failures before a later launch marker are hidden. Machine passes are reported in
2965
3282
  status and maintenance/maintenance.ndjson; this command reads workspace logs.
2966
3283
 
2967
3284
  REMOTE REQUEST RECORDS
2968
- A request to another Mac that fails is a warn record with src: build in the
3285
+ A request to another machine that fails is a warn record with src: build in the
2969
3286
  run's build log: remote_connect_failed { host, capability, ms, timeoutMs, msg,
2970
- code } (could not connect or say hello, e.g. "no reply in time" when the Mac
3287
+ code } (could not connect or say hello, e.g. "no reply in time" when the machine
2971
3288
  did not answer hello) and remote_request_failed { method, ms, code, msg }.
2972
3289
  Timings of requests that succeed are debug records (below).
2973
3290
  stim-server writes to its service log (~/Library/Logs/Stim/<label>.log), with
2974
3291
  no tokens or tickets: "request failed method=<m> client=<device id> run=<runId>
2975
3292
  ms=<n> error=<code>" for an error reply (once a minute per client, method and
2976
3293
  code) and "host_connect ... error=<reason_with_underscores>" when its connection to a hosting
2977
- Mac fails. With debug.logs on or STIM_DEBUG=1 for the server it logs every
3294
+ machine fails. With debug.logs on or STIM_DEBUG=1 for the server it logs every
2978
3295
  request instead: "debug request method=<m> client=<id> run=<runId> ms=<n>
2979
3296
  slow=true error=<code> whois=<ms> probe=<ms>" (slow means 1000 ms or more;
2980
3297
  whois and probe are the hello's Tailscale identity lookup and its wait for
@@ -2991,14 +3308,14 @@ stim logs does not show them. Read them with jq:
2991
3308
  jq -c 'select(.event=="exec" and .ms>1000)' ~/.stim/logs/debug/cli.ndjson
2992
3309
  Events: run_start, run_end { exit, ms }, exec { program, ms, ok, exit? }, and
2993
3310
  remote_connect and remote_request { ms, ok, code? } for requests to another
2994
- Mac. They carry no arguments, tokens or tickets.
3311
+ machine. They carry no arguments, tokens or tickets.
2995
3312
 
2996
3313
  Placement records use src: placement and sit in the run's build log
2997
3314
  (build-*.ndjson), so a new run replaces the previous run's. A run writes one
2998
- record per decision it makes (a build that compiles and has a remote Mac to
3315
+ record per decision it makes (a build that compiles and has a remote machine to
2999
3316
  consider, or ios/android --remote auto), with event build_placement,
3000
3317
  device_placement or placement_fallback. A cache hit, a project with no paired
3001
- remote Mac and a named --remote target write none:
3318
+ remote machine and a named --remote target write none:
3002
3319
  stim logs --source placement
3003
3320
  stim logs --source placement --json
3004
3321
  Fields: kind (build or device), platform, settings [{ key, value, from }] with
@@ -3006,7 +3323,7 @@ from flag, env, setting or default (remote.build and remote.buildMode for a
3006
3323
  build, ios.remote or android.remote, plus remote.easFallback when it is on, for
3007
3324
  a device), candidates [{ machine, code,
3008
3325
  msg, detail? }], choice { machine, code, msg } with machine local when the run
3009
- stays on this Mac, and fallback { code, msg, machine? } when a remote Mac that
3326
+ stays on this machine, and fallback { code, msg, machine? } when a remote machine that
3010
3327
  was meant to take the run did not (level warn). The msg is the same text the
3011
3328
  placement: and build: phase lines print.
3012
3329
  Candidate codes: accepted, unreachable, busy, disk, load, version-mismatch
@@ -3204,6 +3521,13 @@ WHAT WRITES WHAT
3204
3521
  };
3205
3522
  //#endregion
3206
3523
  //#region src/guide/errors.ts
3524
+ const PROVISION_CONTEXT = `\`stim device provision ios [udid]\` is the one Stim command that changes an Apple
3525
+ Developer account: it registers the phone with the project's team and creates
3526
+ or refreshes the iOS App Development profile (and, when the keychain holds
3527
+ none, an Apple Development certificate) by running xcodebuild with
3528
+ -allowProvisioningUpdates -allowProvisioningDeviceRegistration. No build, and
3529
+ no \`ios --device\` run, ever does this. Agents run it only when the user
3530
+ explicitly asks for this step.`;
3207
3531
  const errors = {
3208
3532
  summary: "Every refusal Stim can print: an index of codes, and one section for each",
3209
3533
  sectionHint: "<CODE>",
@@ -3215,10 +3539,11 @@ Branch on the code, never on the message.`,
3215
3539
  STIM_HOSTING_REFUSED: {
3216
3540
  summary: "a hosting Mac refused or could not confirm its native session; no fallback after reservation",
3217
3541
  body: () => `STIM_HOSTING_REFUSED
3218
- The message names the hosting Mac and its reason: unreachable or changed
3219
- tailnet node, no installed simulator or emulator choice, no capacity, elevated or unknown
3542
+ The message names the hosting Mac and its reason: unreachable, a changed
3543
+ server key, a stim-server too old to speak protocol 2 (stim doctor names
3544
+ the update command), no installed simulator or emulator choice, no capacity, elevated or unknown
3220
3545
  memory pressure, or an unresolved reservation or delivery. Stim boots nothing
3221
- locally and never tries another Mac after choosing a named host or creating a
3546
+ locally and never tries another machine after choosing a named host or creating a
3222
3547
  hosted session. Auto skips declined offers before building, then uses the
3223
3548
  local queue if none admits. A reserve refusal after an accepted offer fails
3224
3549
  with this code and asks to retry; it does not re-place the built architecture.
@@ -3236,7 +3561,7 @@ Branch on the code, never on the message.`,
3236
3561
  on the host to enable handoff and native logs (hello features hosted-ios-data
3237
3562
  for iOS and hosted-android-data for Android).
3238
3563
  A name outside remote.machines is STIM_BAD_ARG. Missing or pending hosting
3239
- approval keeps the doctor --fix and stim-server devices grant remedies.`
3564
+ approval keeps the doctor --fix and stim-server setup remedies.`
3240
3565
  },
3241
3566
  STIM_EAS_BUILD_MISSING: {
3242
3567
  summary: "no completed EAS development build matches; build only with session authorization",
@@ -3340,7 +3665,7 @@ Branch on the code, never on the message.`,
3340
3665
  the source checkout, then warm again.`
3341
3666
  },
3342
3667
  STIM_BUILD_FAILED: {
3343
- summary: "xcodebuild or gradle failed; the Android missing-SDK and APK refusals; a damaged compilation-cache object",
3668
+ summary: "xcodebuild or gradle failed; the Android missing-SDK and APK refusals",
3344
3669
  body: () => `STIM_BUILD_FAILED
3345
3670
  xcodebuild or gradle failed. The EXTRACTED diagnostics are printed (capped),
3346
3671
  not the transcript. Read the log path on the next line for the rest.
@@ -3367,17 +3692,40 @@ Branch on the code, never on the message.`,
3367
3692
  reports UP-TO-DATE and repackages nothing. Stim installs it. Gradle owns task
3368
3693
  freshness and the fingerprint owns cache freshness; Stim does not second-guess
3369
3694
  either from the file's mtime.
3370
-
3371
- "failed to scan dependencies for source ..." on pods you did not touch (ios)
3372
- The compilation cache holds a damaged object. Xcode reports it per source
3373
- file, so it names whichever targets reach the object first -- often pods such
3374
- as sqlite3, nanopb or libwebp -- and the list changes between runs. The
3375
- transcript carries the cause:
3695
+ A damaged compilation-cache object stays STIM_BUILD_FAILED when Stim did
3696
+ not give the build its shared cache; STIM_COMPILATION_CACHE_CORRUPT lists
3697
+ the messages and the gc remedy.`
3698
+ },
3699
+ STIM_COMPILATION_CACHE_CORRUPT: {
3700
+ summary: "an iOS build failed on a damaged shared compilation cache; Stim emptied it, rerun the command",
3701
+ body: () => `STIM_COMPILATION_CACHE_CORRUPT (ios)
3702
+ The build used the shared compilation cache ($STIM_HOME/compilation-cache)
3703
+ and the compiler reported a damaged object in it. A cache write that a full
3704
+ disk or a killed build cut short leaves such an object, and every later build
3705
+ that reaches it fails the same way. Stim matches only the compiler's own
3706
+ corruption errors in the transcript:
3707
+ LLVM ERROR: OnDiskCAS: corrupt internal reference
3708
+ error: CAS operation failed: ...
3376
3709
  error: CAS-based dependency scan failed: not a IncludeTreeRoot node kind
3377
- A cache write that a full disk or a killed build cut short leaves such an
3378
- object, and upgrading the CLI does not clear it. Empty that one cache with
3379
- \`gc --delete --cache "compilation cache"\`, then build again. The next
3380
- build is a cold one.`
3710
+ Xcode reports them per source file, so the failing targets are whichever
3711
+ reach the object first -- often pods you did not touch -- and they change
3712
+ between runs.
3713
+ Stim empties the whole cache at once, the way
3714
+ \`gc --delete --cache "compilation cache"\` does, and fails the build. It
3715
+ does not retry. RUN THE SAME COMMAND AGAIN: that build starts with an empty
3716
+ cache, so it is a cold one. The emptying is recorded in the maintenance log.
3717
+ Every iOS build that compiles with the cache holds a shared claim on it,
3718
+ <cache>.claims beside the cache directory, while xcodebuild runs. Stim
3719
+ empties the cache only under that set's exclusive claim, here, in gc and in
3720
+ maintenance. When another build holds it or its claim is unresolved, Stim
3721
+ leaves the cache alone, because that build may be using it, and the message
3722
+ says so. Once nothing else is building, run
3723
+ \`gc --delete --cache "compilation cache"\`, then run the command again.
3724
+ A build on a build worker gets the same handling on the worker, which
3725
+ empties its own cache for that client. With --remote-build <machine> the
3726
+ build fails with this code and the worker's message; otherwise it falls
3727
+ back to building here, and the fallback line names this code. Xcode builds
3728
+ run outside Stim take no claim, so a cache they share is not guarded.`
3381
3729
  },
3382
3730
  STIM_PATH_TOO_LONG: {
3383
3731
  summary: "Windows only: the project root leaves no room for the NDK object paths ninja must open",
@@ -3387,12 +3735,17 @@ Branch on the code, never on the message.`,
3387
3735
  after their mangled absolute source path, so each carries the project root
3388
3736
  twice unless CMake shortens it, and CMake only shortens a name that then
3389
3737
  fits the object path limit Stim's Gradle shim sets. The message names the
3390
- longest known object (measured on the e2e fixture, for the ABI being
3391
- built) and the longest root it leaves room for. Refused BEFORE Gradle runs
3392
- -- a cache hit still installs -- because the build would otherwise fail
3393
- deep inside ninja ("mkdir ... No such file or directory" or "Filename
3394
- longer than 260 characters"). Map the project to a drive letter (\`subst
3395
- X: <root>\`, then run Stim from X:\\) or move it under a shorter root.
3738
+ longest known object for the ABI being built and the longest root it
3739
+ leaves room for. A project with react-native-gesture-handler 3, whose
3740
+ shared shadow nodes have the longest known object path, fits a root of at
3741
+ most 24 characters for an x86_64 Debug build and 21 for arm64-v8a; other
3742
+ projects fit 51 and 48. Refused BEFORE Gradle
3743
+ runs -- a cache hit still installs -- because the build would otherwise
3744
+ fail deep inside ninja ("mkdir ... No such file or directory" or
3745
+ "Filename longer than 260 characters"). Move or clone the project under a
3746
+ short real path such as C:\\src\\<name> and run Stim there. A subst drive
3747
+ does not help: Node's realpath and Expo autolinking resolve it back to the
3748
+ long path, and Gradle then sees two roots.
3396
3749
  A custom Gradle buildStagingDirectory can be shorter than the default; this
3397
3750
  preflight does not inspect that configured path and may refuse it too.
3398
3751
  \`doctor --platform android\` reports the same root as a finding for the
@@ -3534,7 +3887,7 @@ Branch on the code, never on the message.`,
3534
3887
  works. See stim guide macos.`
3535
3888
  },
3536
3889
  STIM_OFFLOAD_REFUSED: {
3537
- summary: "the selected remote Mac or automatic build pool cannot build this app",
3890
+ summary: "the selected remote machine or automatic build pool cannot build this app",
3538
3891
  body: () => `STIM_OFFLOAD_REFUSED
3539
3892
 
3540
3893
  Automatic placement also refuses when local is excluded in remote.buildPoolDisabled
@@ -3544,20 +3897,20 @@ builds are not interrupted and a cache hit needs no compiler.
3544
3897
 
3545
3898
  A named --remote-build selection is strict. The message names the machine
3546
3899
  and why it cannot take or finish the build: not configured or paired, approval
3547
- pending or denied, unreachable or changed pinned identity, incompatible
3900
+ pending or denied, unreachable, changed pinned server key, a stim-server
3901
+ too old to speak protocol 2, incompatible
3548
3902
  toolchain/runtime/CPU, low disk, busy, sync/build failure, artifact failure,
3549
3903
  or a checkout that changed while it built. Unsupported build kinds also refuse.
3550
3904
  No local xcodebuild, Gradle or SwiftPM compile or alternate machine follows a
3551
- refusal. Prebuild and pod install still run on this Mac before a named offload.
3905
+ refusal. Prebuild and pod install still run on this machine before a named offload.
3552
3906
  Unlisted or unpaired names refuse at setup before consulting the cache, without
3553
3907
  a build record or failed-run stats. A listed paired name with a cache hit needs
3554
3908
  no build and does not contact the selected machine.
3555
3909
 
3556
3910
  Run stim doctor --fix to ask for build access if not paired. Check
3557
- stim settings get remote.machines. A person on the worker finds the id with
3558
- stim-server devices and approves this Mac with stim-server devices grant <id> --build,
3559
- or runs stim-server setup on the worker with its node, ticket and expiry
3560
- (guide settings). Agents never run setup or approve requests.
3911
+ stim settings get remote.machines. A person on the worker approves this machine
3912
+ by running the stim-server setup command doctor --fix printed, with its ticket
3913
+ and expiry (guide settings). Agents never run setup or approve requests.
3561
3914
  Rerun with --remote-build auto for normal placement and local fallback, or
3562
3915
  --remote-build local to keep the build here.
3563
3916
  `
@@ -3565,12 +3918,14 @@ Rerun with --remote-build auto for normal placement and local fallback, or
3565
3918
  STIM_WORKTREE_SERVICE: {
3566
3919
  summary: "worktree remove refuses a folder a stim-server service runs from",
3567
3920
  body: () => `STIM_WORKTREE_SERVICE
3568
- A LaunchAgent that \`stim-server service install\` wrote (it carries a
3569
- StimService marker in ~/Library/LaunchAgents/<label>.plist, default label
3570
- dev.stim.server) runs a program from inside this worktree. Removing the
3571
- folder would delete the running server's code. The refusal also covers a
3572
- service that is installed but stopped, because launchd loads it again at the
3573
- next login, and a marked plist Stim could not read. \`--force\` does not
3921
+ A service that \`stim-server service install\` wrote runs a program from
3922
+ inside this worktree: on macOS a LaunchAgent with a StimService marker in
3923
+ ~/Library/LaunchAgents/<label>.plist, on Linux a systemd user unit with an
3924
+ [X-Stim] Managed=true section in ~/.config/systemd/user/<label>.service
3925
+ (default label dev.stim.server). Removing the folder would delete the
3926
+ running server's code. The refusal also covers a service that is installed
3927
+ but stopped, because launchd or systemd starts it again, and a marked file
3928
+ Stim could not read. \`--force\` does not
3574
3929
  override it, and gc leaves the worktree in place.
3575
3930
  Reinstall the service from another build (stim-server service install --label
3576
3931
  <label> from a checkout that stays), or remove it with stim-server service
@@ -3671,10 +4026,11 @@ spending a build or a bundle, that the check can succeed.`,
3671
4026
  The built or cached .app has no embedded.mobileprovision, or
3672
4027
  \`openssl smime\` could not decode the one it has. The first means the build
3673
4028
  produced an unsigned app -- almost always a simulator-sliced artifact.
3674
- Set a team and a Development profile for the target's configuration in
3675
- Xcode > Signing & Capabilities, then BUILD ONCE FROM XCODE to install the
3676
- profile. Stim will not do that step: registering a device or minting a
3677
- profile changes your Apple Developer account, so Stim never passes
4029
+ Set a team for the target's configuration in Xcode > Signing &
4030
+ Capabilities, then run \`stim device provision ios [udid]\` (see
4031
+ STIM_PROVISION_UNCONFIRMED) or BUILD ONCE FROM XCODE to install the profile.
4032
+ No build does that step: registering a device or minting a profile changes
4033
+ your Apple Developer account, so builds never pass
3678
4034
  -allowProvisioningUpdates.`
3679
4035
  },
3680
4036
  STIM_PROFILE_MISMATCH: {
@@ -3692,8 +4048,10 @@ spending a build or a bundle, that the check can succeed.`,
3692
4048
  refuses rather than guessing. Local device runs need a development
3693
4049
  profile;
3694
4050
  - it is a development or ad hoc profile whose device list does not name
3695
- this UDID. Register the UDID at developer.apple.com, regenerate the
3696
- profile, and build once from Xcode.
4051
+ this UDID. Run \`stim device provision ios <udid>\`, which registers
4052
+ the UDID and refreshes the Development profile after asking, or
4053
+ register it at developer.apple.com, regenerate the profile, and build
4054
+ once from Xcode.
3697
4055
  With --eas-profile, follow the EAS device:create and build commands in the
3698
4056
  refusal instead. Registration, signing changes and cloud builds need session
3699
4057
  authorization. See stim guide lifecycle eas.`
@@ -3727,6 +4085,44 @@ spending a build or a bundle, that the check can succeed.`,
3727
4085
  and confirm exactly one identity matches the name. The cache entry itself is
3728
4086
  never modified -- the failure is on a temporary copy, and the run builds
3729
4087
  fresh.`
4088
+ },
4089
+ STIM_PROVISION_UNCONFIRMED: {
4090
+ summary: "device provision ios listed its account changes and nobody confirmed them; nothing changed",
4091
+ separator: "--- iOS PROVISIONING CODES (`stim device provision ios`, and only there) ---",
4092
+ context: PROVISION_CONTEXT,
4093
+ body: () => `STIM_PROVISION_UNCONFIRMED
4094
+ The command printed what it would change -- the team, the phone, each bundle
4095
+ id whose Development profile it would create or refresh, and whether it uses
4096
+ the Xcode account or an App Store Connect API key -- and nobody confirmed:
4097
+ the terminal prompt was answered with anything but y, or there was no
4098
+ terminal (or --json) and no --yes. Nothing changed. \`--json\` adds the plan.
4099
+ Confirm in a terminal, or pass --yes once the account owner agreed. Agents
4100
+ never add --yes on their own.`
4101
+ },
4102
+ STIM_PROVISION_FAILED: {
4103
+ summary: "device provision ios could not provision: no account or key, team not accessible, or project signing",
4104
+ context: PROVISION_CONTEXT,
4105
+ body: () => `STIM_PROVISION_FAILED
4106
+ Before xcodebuild runs: the app has no Xcode project, xcodebuild cannot
4107
+ report the scheme's build settings, a signed target names no development
4108
+ team or uses manual signing (xcodebuild creates profiles only for automatic
4109
+ signing), the temporary DerivedData cannot be created, or a native app uses
4110
+ Swift packages that Stim has not resolved yet (no workspace-state.json in
4111
+ its package directory): run \`stim ios\` or \`stim ios --device <udid>\` once
4112
+ to resolve them, then provision. Packages resolved for an older
4113
+ Package.resolved fail inside xcodebuild, whose error is quoted. The remedy
4114
+ names the Xcode or app-config change. From xcodebuild: "No Accounts" means Xcode has no Apple
4115
+ ID signed in and no App Store Connect key is set -- sign in under Xcode >
4116
+ Settings > Accounts, or set ios.appStoreConnectKeyPath,
4117
+ ios.appStoreConnectKeyId and ios.appStoreConnectIssuerId (stim guide
4118
+ settings); "No Account for Team" means that account or key cannot reach the
4119
+ project's team; a 401 means Apple rejected the key. Any other xcodebuild
4120
+ error is quoted, with the key path replaced. An interrupt or the 15-minute
4121
+ timeout also ends here. Once xcodebuild has run, the refusal still lists
4122
+ any profile it installed and any new keychain identity (\`--json\`:
4123
+ \`changes\`), because Apple may already have registered the phone; running
4124
+ the command again is safe. A phone that is not paired or has Developer Mode
4125
+ off refuses earlier, with STIM_NO_DEVICE.`
3730
4126
  },
3731
4127
  STIM_NO_LAN_ADDRESS: {
3732
4128
  summary: "the Mac has no non-internal IPv4 interface; a tunnel cannot help a phone",
@@ -4324,7 +4720,8 @@ captured" (in metro.ndjson, bare RN)
4324
4720
  a changed pin while this workspace's dev server runs or its supervisor
4325
4721
  cannot be verified,
4326
4722
  \`--device\` with an empty
4327
- serial or UDID, \`--device\` together with \`--remote\`, \`ios --runtime\`
4723
+ serial or UDID, \`--device\` together with \`--remote\` other than
4724
+ \`local\`, \`ios --runtime\`
4328
4725
  on an eas/proxy run (\`--remote\` or ios.remote; that backend picks the
4329
4726
  iOS version), \`ios --device-type\` on the proxy backend or with an
4330
4727
  eas-cli older than 22.2.0 on the eas backend, \`android --system-image\` or
@@ -4392,20 +4789,34 @@ captured" (in metro.ndjson, bare RN)
4392
4789
  \`workspace removal\` -- both are \`stim worktree remove\`). Let it finish,
4393
4790
  then run the command again. \`start --remote\` reports this same case as
4394
4791
  STIM_WORKTREE_REMOVAL_IN_PROGRESS instead.`
4792
+ },
4793
+ STIM_REPORT_TIMEOUT: {
4794
+ summary: "An in-process report or lifecycle operation exceeded its execution budget",
4795
+ body: () => `STIM_REPORT_TIMEOUT
4796
+
4797
+ An API operation exceeded its execution budget: 90 minutes for warmWorktree(),
4798
+ 60 seconds for doctor(), gc(), collectGarbage(), reload(), plan() and removeWorktree().
4799
+ Stim attempts only that operation's cleanup before rejecting, with a 35 second
4800
+ cleanup budget. Unfinished cleanup returns STIM_CLAIM_UNAVAILABLE instead.
4801
+ Other clients are unaffected. Retry after the machine is less busy, or narrow
4802
+ the report with doctor({ platform }) or gc({ cache }). doctor() and gc() remain
4803
+ read-only. collectGarbage() can mutate resources; completed actions remain applied.
4804
+ Inspect what remains with gc() before retrying a mutation.
4805
+ `
4395
4806
  },
4396
4807
  STIM_CANCELLED: {
4397
4808
  summary: "an ios or android run was interrupted by `stim stop` or Ctrl-C before it finished",
4398
4809
  body: () => `STIM_CANCELLED (ios, android; exit 130)
4399
4810
  "The ios run was cancelled by \`stim stop\` (pid <n>) before it finished."
4400
- The run received SIGINT: from \`stim stop\`, which interrupts a build it
4401
- leaves with nothing to deploy to, or from Ctrl-C. It forwarded the interrupt
4811
+ The run was interrupted: by \`stim stop\`, which cancels a build it leaves
4812
+ with nothing to deploy to, or by Ctrl-C. It forwarded the interrupt
4402
4813
  to the build tool it was running (xcodebuild, Gradle, pod install, expo
4403
4814
  prebuild) and failed through its own cleanup; the next line names the step it
4404
4815
  stopped at. An interrupted build stores no artifact, so the next run looks
4405
4816
  up the cache as usual. Stopped between build tools, a run that \`stop\`
4406
4817
  cancelled starts no further build tool and stops before it installs. A
4407
- second SIGINT exits at once, and so does a run that holds a physical-device
4408
- lease; those exits print no payload. Run the same command again when you want the
4818
+ second Ctrl-C exits at once, and so does a Ctrl-C with no build tool or
4819
+ SimSlim to stop; those exits print no payload. Run the same command again when you want the
4409
4820
  app on a device.`
4410
4821
  },
4411
4822
  STIM_STOP_BLOCKED: {
@@ -4416,6 +4827,10 @@ captured" (in metro.ndjson, bare RN)
4416
4827
  - "... did not exit within 60s of SIGINT": the run was interrupted but is
4417
4828
  still running. Wait for it, or end it with the \`kill <pid>\` the remedy
4418
4829
  prints, then run \`stim stop\` again.
4830
+ - "... did not exit within 60s of the stop request": the run is inside a
4831
+ process that embeds Stim, such as stim-server, and runs other work too.
4832
+ Stop does not signal that process. Wait for the run, or cancel it from
4833
+ the client that started it, then run \`stim stop\` again.
4419
4834
  - "... pid <n> is no longer that run; the build tool it started (pid <m>)
4420
4835
  still holds the claim": the run itself died and its build tool keeps the
4421
4836
  claim. Stim signals only a claim owner whose recorded identity it can
@@ -4773,14 +5188,6 @@ not on any remote" (worktree remove)
4773
5188
  Expo build-cache provider would each use a different store. Set the named
4774
5189
  variable to an absolute path, or unset it to use the default. Metro and the
4775
5190
  cache provider, which cannot refuse, ignore a relative value with a warning.`
4776
- },
4777
- STIM_WORKER_FAILED: {
4778
- summary: "a programmatic API worker exited without returning an operation result",
4779
- body: () => `STIM_WORKER_FAILED (programmatic API)
4780
- The worker process ended without a structured result. StimError.details
4781
- includes its final stderr output. Inspect that output and call diagnostics()
4782
- for existing workspace logs, then stop() with a fresh signal to clean up
4783
- any resources created before the failure. See stim guide api.`
4784
5191
  },
4785
5192
  STIM_RUN_FAILED: {
4786
5193
  summary: "a programmatic operation failed without a more specific Stim code",
@@ -4915,6 +5322,16 @@ with eas or proxy starts it the way \`stim start --remote\` does. A named
4915
5322
  hosting Mac keeps Metro local through a private bridge; see hosted-ios. Step 2 remains useful to warm
4916
5323
  Metro while other work happens.
4917
5324
 
5325
+ Each time Stim boots an owned local simulator or emulator, it mutes it: iOS
5326
+ simulators to volume zero through Xcode's devicectl, and Android 15 and later
5327
+ emulators to media volume zero. A device that is already running keeps its
5328
+ volume. Turn sound on for one device with Sound (iOS) or Media sound
5329
+ (Android) in its Stim Desktop options; the choice lasts until that device next
5330
+ boots. Android 14 and older images have no shell command that can set media
5331
+ volume, so startup warns, as it does whenever a mute is not confirmed. Host
5332
+ volume, Android alarm and ringtone volumes, hosted devices and physical
5333
+ devices are unchanged.
5334
+
4918
5335
  Repeat step 3 whenever a NATIVE input changes. A JS-only edit needs nothing --
4919
5336
  that is what Fast Refresh over the running dev server is for. \`stim reload\` is
4920
5337
  not part of the normal workflow. It is the explicit recovery path when Fast
@@ -5036,9 +5453,10 @@ STOP DURING A BUILD
5036
5453
  lock waiting for \`stim ios\` (pid 41233, running for 12m04s) in this workspace to finish
5037
5454
  \`stop\` does not wait out a build it leaves with nothing to deploy to: a
5038
5455
  plain \`stop\`, \`stop --slot <name>\` for the slot the build targets, or
5039
- for the workspace's only device. It sends that run SIGINT, only after
5040
- proving the run's recorded process identity, and waits up to 60 seconds for
5041
- it to exit. The run stops its build tool, stores nothing from the
5456
+ for the workspace's only device. It asks that run to stop through the
5457
+ workspace state and, when the run is a CLI process, also sends it SIGINT,
5458
+ only after proving the run's recorded process identity. It waits up to 60
5459
+ seconds for the run to exit. The run stops its build tool, stores nothing from the
5042
5460
  interrupted build, and exits 130 with STIM_CANCELLED. A run that does not
5043
5461
  exit in time, or whose build tool outlived it, is refused with
5044
5462
  STIM_STOP_BLOCKED naming its pid and claim. \`stop --slot <name>\` while the
@@ -5068,11 +5486,20 @@ WAITING FOR A CHANGE
5068
5486
  changes, never two identical ones in a row. In a watch line, a usage
5069
5487
  figure repeats its value in the previous line until it moves by a step: an owner's cpuPercent 5 points, an
5070
5488
  owner's or environment's memoryMb and capacity.committedMb 16 MB. An owner's row
5071
- changes as a whole, so its residentMb and processes update with it. It reacts to Stim state files,
5072
- the EAS session ledger, adb device arrivals and departures, and simulator
5073
- state, and recomputes every 30 seconds as a fallback. A log append updates
5074
- only the log error count and device activity, no sooner than 15 seconds
5075
- after the previous refresh. Ctrl+C, SIGTERM or closing its stdout ends it
5489
+ changes as a whole, so its residentMb and processes update with it. A change
5490
+ in only a bookkeeping timestamp prints no line: each pullRequest.checkedAt,
5491
+ maintenance.lastChecks, maintenance.lastPass.startedAt and durationMs, and
5492
+ maintenance.running.startedAt. A line that prints carries their current
5493
+ values. It reacts to Stim state files,
5494
+ the EAS session ledger, git HEAD, index and ref changes in the worktrees'
5495
+ repositories, the exit of a recorded supervisor, Metro, build, browser or
5496
+ macOS app process (checked every 2 seconds), adb device arrivals and
5497
+ departures, and simulator boots, shutdowns, creations and deletions (from
5498
+ CoreSimulator's device set and metadata files, with a simctl check every 30 seconds), and
5499
+ recomputes every 30 seconds as a fallback. A log append updates the log-derived facts
5500
+ within about a second; device activity follows at most every 5 seconds. The
5501
+ first line does not wait for git: worktree git summaries can be null in it,
5502
+ and the next line carries them. Ctrl+C, SIGTERM or closing its stdout ends it
5076
5503
  with exit 0. On Linux, so does the exit of the process that started it
5077
5504
  when its stdout is a pipe. It notices within a few seconds even when
5078
5505
  nothing changes, except a closed stdout pipe on Linux whose starter is
@@ -5121,7 +5548,7 @@ C-family header graphs, Swift package graphs and unresolved external input
5121
5548
  paths or compiler overrides. The miss reason explains the exclusion. --plan
5122
5549
  refuses such a prediction; omit --plan to prepare and build the app.
5123
5550
 
5124
- A compatible approved build Mac can compile a native Xcode app with
5551
+ A compatible approved remote Mac can compile a native Xcode app with
5125
5552
  --remote-build auto or --remote-build <name>. Both peers must support
5126
5553
  native-xcode-build. This first worker path requires a verified artifact identity
5127
5554
  and all inputs contained in the repository and visible to git. Ignored or
@@ -5168,8 +5595,34 @@ density/split APK sets and EAS/proxy targets are unsupported. Set
5168
5595
  org.gradle.configureondemand=false; configuration cache remains supported.
5169
5596
  Stim verifies the existing APK signature; it does not sign it.
5170
5597
 
5171
- Stim artifact caching is unavailable because arbitrary Gradle inputs are not fully
5172
- tracked. Build workers require a project-scoped \`android.offloadInputs\` declaration:
5598
+ Local APK caching requires project-scoped \`android.artifactInputs\`:
5599
+ \`{"complete":true,"ignored":[],"outputs":["build","app/build"],"localFiles":["../signing/release.keystore"],"environment":["SIGNING_PASSWORD"]}\`.
5600
+ complete promises a repeatable build from Git-visible source, declared ignored
5601
+ files, exact local files and named environment values, with pinned dependencies
5602
+ and unmodified pinned toolchains. Declare custom signing, JVM user.home, alternate
5603
+ toolchain and external configuration inputs; arbitrary Gradle reads cannot be
5604
+ inferred. Local files may be absolute or project-relative; absence is hashed.
5605
+ Default debug keystores, JDK release, SDK version metadata, Gradle user properties
5606
+ and init scripts also contribute. SDK directory paths alone do not change the
5607
+ key; other local.properties contents do. Keys must be plain and unescaped, and
5608
+ values must fit on one line; Java Properties escapes in sdk.dir values are supported.
5609
+ Unsupported syntax disables artifact caching. The AGP SDK must match ANDROID_HOME.
5610
+ Inputs are checked again after compilation; a change prevents publication.
5611
+ Cached APK bytes, package, signing certificate and target ABI are verified.
5612
+ A configured provider with a files capability shares the completed APK and
5613
+ verification receipt. Stim checks the local cache first, verifies downloaded
5614
+ bytes, then fills the local cache. Invalid shared entries are cache misses;
5615
+ providers without files are skipped. Private input contents are neither logged
5616
+ nor transferred. This contract grants no build-worker transfer access.
5617
+ Without it, native builds remain uncached and Gradle retains its incremental and
5618
+ task caches. optimizations.buildCache=false disables artifact reads and writes.
5619
+ --no-build-cache skips reads and shared-build waits, but stores the fresh build.
5620
+ Native Android --plan verifies the same key and cached APK without running Gradle;
5621
+ unresolved ABI (including legacy unowned emulator assignments), missing declarations,
5622
+ hosted placement and CAS setup refuse it.
5623
+
5624
+
5625
+ Build workers require a project-scoped \`android.offloadInputs\` declaration:
5173
5626
  \`{"complete":true,"ignored":["app/src/main/assets/generated.json"],"outputs":["build","app/build"]}\`.
5174
5627
  Use \`stim android --remote-build <approved-mac>\` after reviewing this declaration.
5175
5628
  Paths are exact repository-relative paths. complete affirms that Git-visible
@@ -5179,8 +5632,17 @@ homes. External inputs, directory links, submodules and custom local.properties
5179
5632
  refuse; an SDK-only local.properties stays local. Only declared directories
5180
5633
  reported by AGP and previously produced by this worker remain warm, along with
5181
5634
  worker project .gradle/.kotlin state. Undeclared bytes are removed. Gradle owns
5182
- incremental, task-cache and configuration-cache reuse; transfer digests never
5183
- become reusable APK cache keys. Older hosts refuse before source upload.
5635
+ incremental, task-cache and configuration-cache reuse. A transferred
5636
+ android.artifactInputs declaration also lets the worker reuse verified APKs under
5637
+ its own complete inputs: worker toolchain, signing files and environment values,
5638
+ not the caller's. The returned fingerprint remains null; transfer digests never
5639
+ become reusable APK cache keys. Workers with native-gradle-peer-cache can offer a
5640
+ completed APK for declared source and resolved variant, ABI and build options,
5641
+ rechecking current private inputs against the saved receipt at offer and fetch.
5642
+ A hit skips source upload and compilation even while that worker is busy or
5643
+ this machine has room. A miss follows normal placement; older Gradle workers
5644
+ still build normally. Off mode, explicit local builds and disabled remote cache
5645
+ reads skip peer lookup. Hosts without native-gradle-build refuse before upload.
5184
5646
 
5185
5647
  outputs must list every build directory of this build's projects that the build
5186
5648
  creates: the root build directory and each module's, library modules included.
@@ -5190,7 +5652,7 @@ Included builds (includeBuild, such as a build-logic plugin build) and
5190
5652
  externalNativeBuild staging (.cxx) are not reported by AGP and cannot be
5191
5653
  declared, so a remote build that creates them fails and Stim builds locally.
5192
5654
 
5193
- Native Android \`--plan\` refuses without executing Gradle; \`doctor\` reports native
5655
+ Native Android \`--plan\` requires a complete artifact input declaration and does not execute Gradle; \`doctor\` reports native
5194
5656
  prerequisites. \`reload\` refuses because the app has no Metro runtime. Re-run
5195
5657
  \`stim android\` after an edit and use \`stim stop\` for scoped cleanup.`
5196
5658
  },
@@ -5252,12 +5714,13 @@ A person on the host approves device-host access, separately from build, read
5252
5714
  and control. The setup in hosted-ios also serves Android. android.remote sets
5253
5715
  a workspace default. eas and proxy remain backends. --remote auto or
5254
5716
  android.remote = auto places automatically; see hosted-ios for the shared
5255
- policy. No flag or setting runs on this Mac.
5717
+ policy. No flag or setting runs on this machine.
5256
5718
 
5257
5719
  A named Mac is strict: refusal, unreachable, declined or failed preparation
5258
- returns STIM_HOSTING_REFUSED without fallback. --device, a different recorded
5259
- machine, a running local owned emulator in the slot, and --no-metro-check for
5260
- hosted Metro development apps refuse STIM_BAD_ARG. Run stim stop before switching placement.
5720
+ returns STIM_HOSTING_REFUSED without fallback. --device with --remote
5721
+ <machine>, a different recorded machine, a running local owned emulator in
5722
+ the slot, and --no-metro-check for hosted Metro development apps refuse
5723
+ STIM_BAD_ARG. Run stim stop before switching placement.
5261
5724
  Unreadable android.host blocks only its slot; restore its recorded machine and
5262
5725
  session from the host before stopping that slot.
5263
5726
 
@@ -5273,7 +5736,7 @@ bridge; the host reverses the client's Metro port into that bridge on its exact
5273
5736
  ledger-owned serial and sets debug_http_host to localhost:<clientMetroPort>.
5274
5737
  Each run and stim reload android restores the reverse, including after a host
5275
5738
  adb server restart. Reload broadcasts through the local Metro and never runs
5276
- adb against the host serial on this Mac. Release variants skip Metro. Launch
5739
+ adb against the host serial on this machine. Release variants skip Metro. Launch
5277
5740
  is true only with client bundle evidence or a live host release process;
5278
5741
  bundling requires a bundle request and unverified has no launch evidence.
5279
5742
 
@@ -5303,7 +5766,7 @@ arrive through local Metro.
5303
5766
 
5304
5767
  AGENT CONTROL
5305
5768
 
5306
- Set hosting.agentDriver to agent-device on the hosting Mac. Both Macs need
5769
+ Set hosting.agentDriver to agent-device on the hosting Mac. Both machines need
5307
5770
  agent-device 0.21.22 or later. The host advertises hosted-android-agent and
5308
5771
  starts one loopback daemon per installed Android session. Its policy allows
5309
5772
  only the exact ledger-owned emulator serial and denies device shutdown.
@@ -5344,8 +5807,8 @@ on the owned emulator until Stim deletes it. A daemon exit rotates only that
5344
5807
  session's grant; rerun stim android to refresh its config.
5345
5808
 
5346
5809
  Stim Desktop and the phone app view and control the emulator through this
5347
- Mac's local stim-server relay, with an "on <machine>" label. Turn on Serve to
5348
- phones in Desktop and pair the phone with this Mac, not the hosting Mac.
5810
+ machine's local stim-server relay, with an "on <machine>" label. Turn on Serve to
5811
+ phones in Desktop and pair the phone with this machine, not the hosting Mac.
5349
5812
  frames.subscribe and control.begin address workspace, platform android and
5350
5813
  slot, including named slots. The host verifies its exact ledger-owned serial,
5351
5814
  AVD identity and ABI before capture or input; clients never use that serial
@@ -5359,9 +5822,9 @@ are unavailable through the scrcpy path.
5359
5822
 
5360
5823
  Reruns send only missing manifest and APK digests from the session-scoped blob
5361
5824
  store. The host verifies reused bytes; each new attempt still installs the APK.
5362
- When an Android build ran on the hosting Mac's pinned node, the host takes only
5825
+ When an Android build ran on the hosting Mac's own stim-server, the host takes only
5363
5826
  the single App.apk matching the client's manifest. The build and hosting approvals
5364
- must belong to the same tailnet node. Handoff failure falls back to upload,
5827
+ must belong to the same client key. Handoff failure falls back to upload,
5365
5828
  retrying a busy host for at most one minute.
5366
5829
 
5367
5830
  stim logs, including --errors and --json, pulls native logcat records as src: device.
@@ -5392,8 +5855,7 @@ If Stim is not installed globally, replace stim with npx stim.
5392
5855
 
5393
5856
  stim settings set remote.machines '["janics-mac-mini"]'
5394
5857
  stim doctor --fix
5395
- # A person there approves: stim-server devices grant <id> --device-host
5396
- # Or runs stim-server setup on the worker with its client node, ticket and expiry.
5858
+ # A person there runs the stim-server setup command doctor printed.
5397
5859
  stim doctor
5398
5860
  stim ios --remote janics-mac-mini --device-type "iPhone 17 Pro" --runtime "iOS 27.0"
5399
5861
  stim status --json
@@ -5401,7 +5863,7 @@ If Stim is not installed globally, replace stim with npx stim.
5401
5863
  stim stop
5402
5864
 
5403
5865
  The same setup serves macos --remote. Approval is separate from build, read
5404
- and phone control. Set ios.remote to the machine name for a default (machine scope for the whole Mac);
5866
+ and phone control. Set ios.remote to the machine name for a default (machine scope for every app on this machine);
5405
5867
  no flag or setting runs here. eas and proxy keep their remote backend meanings.
5406
5868
  --remote auto (or ios.remote = auto / android.remote = auto) stays here when
5407
5869
  there is a free concurrency.maxDevices slot (0 means unlimited), no device
@@ -5413,7 +5875,8 @@ members from new automatic placement only; an excluded local member is never a
5413
5875
  fallback. Existing sessions keep their owner and named placement bypasses the pool.
5414
5876
  See stim guide settings for separate build/device membership controls.
5415
5877
 
5416
- Otherwise Stim asks every approved remote.machines host in parallel, with a
5878
+ Otherwise Stim asks every approved remote.machines host in parallel, skipping
5879
+ one that tailscale status reports as running an OS other than macOS, with a
5417
5880
  3-second probe timeout and this run's model/runtime or image/profile selectors.
5418
5881
  A compatible choice, no declined reason, normal memory pressure and available
5419
5882
  capacity are required. With a free local slot, a host must report a lower load;
@@ -5422,11 +5885,11 @@ Hosts rank by the build preference (flag > STIM_REMOTE_BUILD > remote.build)
5422
5885
  when it names a machine, then lowest load, most free memory and remote.machines order. Automatic build
5423
5886
  offload resolves later, after the device architecture is known.
5424
5887
 
5425
- When no host admits and this Mac is at its concurrency.maxDevices cap (or runs
5888
+ When no host admits and this machine is at its concurrency.maxDevices cap (or runs
5426
5889
  are queued ahead), remote.easFallback (default false; EAS Simulator is billed)
5427
5890
  runs the device on an EAS Simulator exactly as --remote eas would: the build
5428
5891
  stays here, nothing starts an EAS cloud build, stop ends the session, and
5429
- status reports it under remoteDevices. A busy Mac with a free slot never uses
5892
+ status reports it under remoteDevices. A busy machine with a free slot never uses
5430
5893
  it. Before choosing it Stim checks, without starting a session, that eas-cli
5431
5894
  has simulator commands (and 22.2.0+ for --device-type), eas
5432
5895
  simulator:availability says this project's account can use it, agent-device
@@ -5434,7 +5897,7 @@ is on PATH, the slot is default, no --runtime, --system-image or
5434
5897
  --device-profile flag is given, no EAS Simulator session of this workspace runs
5435
5898
  another platform or model, and a Debug run's Metro is reachable (not
5436
5899
  metro.tunnel off; on an Expo tunnel, run stim start --remote first). A recorded
5437
- EAS session is not sticky: once this Mac has room, auto runs here and the
5900
+ EAS session is not sticky: once this machine has room, auto runs here and the
5438
5901
  session bills until stim stop. If any check fails, or the
5439
5902
  setting is off, auto runs here and may wait in the existing FIFO device
5440
5903
  slot queue; --no-wait and --wait 0 refuse with STIM_AT_CAPACITY. The placement
@@ -5449,7 +5912,8 @@ A reserve refusal after a successful offer can race with another run. It fails
5449
5912
  with STIM_HOSTING_REFUSED naming the host and asking to retry. Stim does not
5450
5913
  try the next host or local placement after the build has targeted that host's
5451
5914
  architecture, and never falls back once a hosted session exists. A named machine remains strict. --device
5452
- cannot combine with auto. eas/proxy and macOS placement are unchanged.
5915
+ cannot combine with --remote auto, and a --device run ignores ios.remote and
5916
+ android.remote. eas/proxy and macOS placement are unchanged.
5453
5917
  --simulator-app is honoured locally with auto and ignored with a note on a host.
5454
5918
  Placement measures budgets without reclaiming; only a new local run reclaims.
5455
5919
  Android --remote <machine> uses an owned emulator on that Mac; see hosted-android.
@@ -5459,7 +5923,7 @@ here or elsewhere. --device cannot use a hosting Mac, and --simulator-app
5459
5923
  refuses. The host boots headless and ignores this Mac's iosSimulatorApp setting.
5460
5924
  --slot, --device-type and --runtime select the hosted simulator; --scheme,
5461
5925
  --configuration and --eas-profile select the app as usual. The build targets
5462
- the offered simulator architecture, not this Mac's. --remote-build remains
5926
+ the offered simulator architecture, not this machine's. --remote-build remains
5463
5927
  independent and selects a compatible build worker for a Debug cache miss.
5464
5928
 
5465
5929
  The host's own owned devices count toward concurrency.maxDevices.
@@ -5482,7 +5946,7 @@ sessions.
5482
5946
 
5483
5947
  Stim builds or fetches before reserving, records the session immediately, then
5484
5948
  uploads the app on every run. React Native and Expo Debug keep Metro here; the supervisor owns a
5485
- private gateway bound to this Mac's Tailscale address, pinned to the host peer
5949
+ private gateway bound to this machine's Tailscale address, pinned to the host peer
5486
5950
  and protected by a per-session secret. The host exposes only a loopback bridge.
5487
5951
  No start --remote is needed; metro.tunnel and metro.publicUrl are ignored.
5488
5952
  Debug requires a running supervisor with private gateway support before reserving
@@ -5494,7 +5958,7 @@ apps use process mode in every configuration and require no Metro; see native-io
5494
5958
 
5495
5959
  A rerun on the same named Mac reattaches and delivers a new app attempt. A
5496
5960
  stopped or missing session is replaced; an unreachable or unknown owner refuses
5497
- replacement. Switching between this Mac and a hosting Mac, or to a different
5961
+ replacement. Switching between this machine and a hosting Mac, or to a different
5498
5962
  hosting Mac, refuses for that slot: run stim stop first.
5499
5963
  Status adds ios.host { machine, session, selected, device: { name, runtime },
5500
5964
  agent, state } per slot. A shutdown local simulator is replaced in status; a
@@ -5562,7 +6026,8 @@ tail before parking or deletion, including on revocation or server close; stop c
5562
6026
  back afterwards. If the final collection drops a backlog interval and eventually
5563
6027
  succeeds, a device warning record names that interval. A damaged collection checkpoint is ignored and
5564
6028
  rebuilt; it never blocks reading collected records. The client waits up to
5565
- 180 seconds for stop. The final collection and worker termination paths fit within
6029
+ 280 seconds for stop. The final collection, parking, the stop that follows a failed
6030
+ park and worker termination paths fit within
5566
6031
  that wait; an in-flight handoff copy and closing Metro or view transports are
5567
6032
  outside those worker bounds.
5568
6033
  Collected records remain in the session home after parking or deletion;
@@ -5574,7 +6039,7 @@ host gets an update note, uses uploads and shows logs already copied here.
5574
6039
 
5575
6040
  AGENT CONTROL
5576
6041
 
5577
- On the hosting Mac, set hosting.agentDriver to agent-device. Both Macs need
6042
+ On the hosting Mac, set hosting.agentDriver to agent-device. Both machines need
5578
6043
  agent-device 0.21.20 or later (the policy and remote-config contract). The host
5579
6044
  starts one loopback daemon per installed iOS session under a child-aware
5580
6045
  ownership claim. Its daemon policy pins exactly that simulator UDID and denies
@@ -5631,8 +6096,8 @@ daemon startup or grant issuance on client support. The client checks the host's
5631
6096
  hosted-ios-agent feature before using a grant.
5632
6097
 
5633
6098
  Stim Desktop and the phone app view and control the simulator through this
5634
- Mac's stim-server relay, with an "on <machine>" label. Turn on Serve to phones
5635
- in Desktop; pair the phone with this Mac, not the hosting Mac. frames.subscribe
6099
+ machine's stim-server relay, with an "on <machine>" label. Turn on Serve to phones
6100
+ in Desktop; pair the phone with this machine, not the hosting Mac. frames.subscribe
5636
6101
  and control.begin address workspace, platform ios and slot, including named
5637
6102
  slots. The relay shares one authenticated host connection with hosted macOS
5638
6103
  viewers. Stopped sessions show a rerun remedy; unavailable hosts report why
@@ -6202,8 +6667,8 @@ PREDICTING THE NEXT BUILD (--plan)
6202
6667
  architecture or ABI from that Mac's device offer, the question a run asks
6203
6668
  before it reserves anything. With auto it repeats the placement decision a
6204
6669
  run would make now and reports where in the payload's placement field, for
6205
- example "this Mac; auto may use janics-mac-mini". When the plan stays on
6206
- this Mac and a listed Mac would build for another architecture, placement
6670
+ example "this machine; auto may use janics-mac-mini". When the plan stays on
6671
+ this machine and a listed Mac would build for another architecture, placement
6207
6672
  says so, since that Mac's key differs. The plan only predicts the key: a
6208
6673
  run reads the architecture of the device it actually gets. A Mac that does
6209
6674
  not answer, ios.remote or android.remote set to eas or proxy, or auto that
@@ -6399,7 +6864,8 @@ then compiles through the cache the selection leaves -- ccache, or none when
6399
6864
  compilerCache is none. It never refuses. See
6400
6865
  https://stim.appandflow.com/docs/android-cas for setup, evidence, and limits.
6401
6866
  Compiler/PCH modes have separate generated directories under each module's
6402
- .cxx/stim-<profile> (or custom staging root). Switching modes in Stim selects
6867
+ .cxx/stim-<profile> (.cxx/<first 4 profile characters> on Windows, or custom
6868
+ staging root). Switching modes in Stim selects
6403
6869
  the matching directory; direct Gradle builds keep their own configuration.
6404
6870
  Generated CAS directories still depend on Stim's environment and adapter paths.
6405
6871
 
@@ -6431,8 +6897,8 @@ THE BUILD CACHE HAS THREE LEVELS
6431
6897
  @stim-cli/cache contract (see \`guide settings\`). Consulted only when
6432
6898
  level one misses, and its hit is stored into level one before install.
6433
6899
  The same contract serves the Metro transform cache.
6434
- 3. On an EXPO project only, the provider the project ALREADY configured for
6435
- Expo (\`expo.buildCacheProvider\` -- "eas", or a module of its own).
6900
+ 3. On an EXPO project only, EAS when the project configures it through
6901
+ Expo (\`expo.buildCacheProvider\` -- "eas").
6436
6902
  Consulted only when levels one and two miss, bounded so a slow or expired
6437
6903
  remote cannot stall the loop, and a hit is copied into level one on the
6438
6904
  way past so the next workspace on this machine gets it for free. After a
@@ -6441,6 +6907,14 @@ THE BUILD CACHE HAS THREE LEVELS
6441
6907
  tier because its run-options contract cannot distinguish ABIs; levels one
6442
6908
  and two remain ABI-keyed and active.
6443
6909
 
6910
+ The "eas" tier needs eas-build-cache-provider in the app and an installed
6911
+ eas-cli in the app or on PATH. Stim runs its fixed EAS CLI commands without
6912
+ loading the provider SDK into the caller. Fingerprint generation uploads
6913
+ fingerprint metadata to EAS. Fingerprint and transfer commands share one deadline per phase; timeout or
6914
+ cancellation stops that phase's CLI process before returning.
6915
+ Stim never installs eas-cli during cache lookup. A custom Windows batch
6916
+ launcher needs a project eas-cli installation so Stim can stop Node directly.
6917
+
6444
6918
  Stim never configures a provider and never suggests changing one: a
6445
6919
  project without one is a perfectly ordinary local-only project (doctor does
6446
6920
  not ask for one either -- a provider only serves builds run OUTSIDE Stim).
@@ -6935,6 +7409,8 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
6935
7409
  install requires an exclusive claim. It releases shared before waiting for
6936
7410
  exclusive, then checks the checkout again before fetching or writing. Every
6937
7411
  copy holds shared, so no copy reads a node_modules a refresh is rewriting.
7412
+ Each copy also holds an exclusive claim on its target, so concurrent warms
7413
+ into the same linked worktree wait instead of racing destination checks.
6938
7414
  The lock is keyed on the repository root, so
6939
7415
  two apps of one monorepo share it. It is the same ownership claim the build
6940
7416
  locks take: the holder's process IDENTITY decides, so a holder that dies frees
@@ -6964,9 +7440,10 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
6964
7440
  paths eligible under the source checkout's Git ignore rules, including .env
6965
7441
  and local configuration. The source's nonempty
6966
7442
  .worktreeexclude replaces its resolved worktree.exclude setting. Nested
6967
- registered worktrees, .DS_Store, .DerivedData, .idea, .jj, and
6968
- android/build/generated/autolinking caches are excluded, including inside
6969
- newly copied directories. Gradle regenerates autolinking
7443
+ registered worktrees, .DS_Store, .DerivedData, .idea, .jj, a project-local
7444
+ .pnpm-store, and android/build/generated/autolinking caches are excluded,
7445
+ including inside newly copied directories; pnpm refills its store from the
7446
+ network or its global store. Gradle regenerates autolinking
6970
7447
  for the destination checkout on its next build. Warm also skips paths
6971
7448
  overlapping a nested destination worktree or below a symlink ancestor.
6972
7449
  Tracked .idea settings come from Git and stay untouched by warm.
@@ -7015,8 +7492,9 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
7015
7492
  filled in. Warm does not copy tracked changes, switch branches, or build,
7016
7493
  and it installs dependencies only under \`--refresh\`, in the source checkout. stdout stays empty; stderr reports copied, kept,
7017
7494
  and failed entry counts. A copy failure exits 1 and reports incomplete;
7018
- files copied before a failure remain. Inspect the named failed entry
7019
- before retrying: a partially copied directory will be kept on the retry.
7495
+ completed entries remain. After its child drains, warm removes its own
7496
+ failed partial entry so a retry can copy it again. Unverifiable child cleanup
7497
+ leaves that entry and its ownership claims in place with recovery instructions.
7020
7498
  A completed copy is not proof that dependencies match this branch. Follow
7021
7499
  any lockfile remedies before building, and install missing dependencies
7022
7500
  with the project's package manager when the source has none to copy.
@@ -7105,7 +7583,8 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
7105
7583
  body: () => ` \`android --device [serial]\` installs and launches on a physical device
7106
7584
  connected to this machine instead of this workspace's owned emulator. With
7107
7585
  no serial it takes the first device it can lease (\`guide lifecycle lease\`).
7108
- It cannot be combined with --remote.
7586
+ It ignores android.remote and cannot be combined with --remote other than
7587
+ --remote local.
7109
7588
 
7110
7589
  A \`--device\` run LEASES the device from just after the build until it
7111
7590
  exits, so a second workspace cannot install over it mid-run
@@ -7121,7 +7600,8 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
7121
7600
  \`android --device\` selects a connected phone: with no UDID it takes the
7122
7601
  first device it can lease (\`guide lifecycle lease\`), and an iPhone
7123
7602
  that is unpaired or has Developer Mode off is refused with the fix. It
7124
- cannot be combined with --remote, and it never creates, boots, or deletes
7603
+ ignores ios.remote, cannot be combined with --remote other than --remote
7604
+ local, and never creates, boots, or deletes
7125
7605
  hardware -- there is no capacity check, no simulator creation, no boot wait,
7126
7606
  and no owned-device registry entry. Like
7127
7607
  \`android --device\`, it leases the phone for the run
@@ -7183,6 +7663,33 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
7183
7663
  a CACHED app falls back to a full build; on a freshly built one it exits on
7184
7664
  its own code, because building again would produce the same app.
7185
7665
 
7666
+ PROVISIONING A NEW PHONE is a separate, explicit step, never part of a
7667
+ build: \`stim device provision ios [udid]\` finds the Xcode project the way
7668
+ \`ios\` does (ios/ for React Native and Expo, the app's own .xcodeproj or
7669
+ .xcworkspace for a native app), reads every target of the scheme that is
7670
+ signed with a profile, on any platform (\`--scheme\`, \`--configuration\`,
7671
+ or ios.scheme / ios.configuration), prints the team, the phone and each
7672
+ bundle id whose Development profile it would create or refresh, and asks
7673
+ [y/N]; Ctrl+C or end of input is no, and \`--yes\` replaces the prompt
7674
+ without a terminal. It then runs xcodebuild -allowProvisioningUpdates
7675
+ -allowProvisioningDeviceRegistration for that phone in a throwaway
7676
+ DerivedData and stops it at the first build task, which xcodebuild starts
7677
+ only once planning has resolved signing, before compilation. A native app
7678
+ with Swift packages uses the packages \`ios\` already resolved, with
7679
+ automatic package resolution off, and refuses until \`stim ios\` has
7680
+ resolved them. That stop and a Ctrl+C send xcodebuild
7681
+ SIGINT, then SIGTERM after 10s and SIGKILL 5s later; a SIGTERM sent to Stim,
7682
+ and the 15-minute timeout, send SIGTERM, then SIGKILL after 5s. A second
7683
+ signal kills xcodebuild before Stim exits. The DerivedData is always
7684
+ removed. It authenticates with the Xcode account, or with
7685
+ the App Store Connect API key in ios.appStoreConnectKeyPath,
7686
+ ios.appStoreConnectKeyId and ios.appStoreConnectIssuerId (or
7687
+ STIM_ASC_KEY_PATH, STIM_ASC_KEY_ID, STIM_ASC_ISSUER_ID, each overriding its
7688
+ own setting); it never reads the key and never prints its path. It reports
7689
+ the installed profile and any new keychain identity, and on a failure or
7690
+ interrupt it still reports what had already changed. It changes the team's
7691
+ developer account: agents run it only on the user's explicit go-ahead.
7692
+
7186
7693
  DEBUG REACHES METRO OVER THE LAN, because a phone shares no loopback with
7187
7694
  the host and USB carries no reverse forward. Stim picks a non-internal IPv4
7188
7695
  address (en0 first, RN's own order from react-native-xcode.sh), gates it as
@@ -7558,11 +8065,16 @@ scoped STIM_HOME homes unless STIM_MAINTENANCE is explicit, so a scratch home
7558
8065
  or CI run never deletes on its own.
7559
8066
  Commands trigger a detached pass when a check is due: disk and memory pressure
7560
8067
  (every minute), directory sizes (hourly), finished worktrees (every 15
7561
- minutes) and the age sweep (daily). guide, settings and help do not trigger
7562
- it; gc --delete also skips the hook. status --watch also triggers checks.
8068
+ minutes) and the age sweep (daily). guide, settings, help and every gc
8069
+ invocation do not trigger it; gc without --delete prints the maintenance plan
8070
+ itself.
8071
+ status --watch and every running dev server supervisor also trigger checks
8072
+ each minute, so the disk check runs while no command does.
7563
8073
  Size, worktree and sweep checks defer under high load. Attempts back off for at
7564
8074
  least one minute. Measured directories are workspace build outputs and Stim's
7565
- shared native, Metro, ccache, Swift compilation and registered caches.
8075
+ shared native, Metro, ccache, Swift compilation and registered caches. The size
8076
+ check also records the disk of each owned and parked simulator and emulator
8077
+ (category owned-devices); status prints the total as "owned devices".
7566
8078
  Pressure checks read free disk on the Stim home, projects and worker root
7567
8079
  volumes. On macOS the memory signal is the sysctl pressure level, with no
7568
8080
  signal if sysctl fails. Other platforms use os.freemem(); macOS never falls
@@ -7580,20 +8092,83 @@ In on mode a pass runs, cheapest to rebuild first:
7580
8092
  maintenance.capTargetPercent of caches.buildCacheMaxGb or
7581
8093
  caches.metroCacheMaxGb, and entries unused for olderThanDays
7582
8094
  4. the Swift compilation cache, emptied whole, only below
7583
- budget.hardFloorDiskGb and with no build lock or slot held
8095
+ budget.hardFloorDiskGb, and kept while an iOS build holds its shared
8096
+ claim (taken for each xcodebuild run that compiles with it) or that
8097
+ claim is unresolved; the emptying holds the exclusive claim
7584
8098
  5. linked worktrees whose branch or pull request finished (gc's finished
7585
8099
  worktree rules and gc.worktreeGraceMinutes; maintenance.removeFinishedWorktrees)
8100
+ 6. owned simulators and emulators of a finished worktree that is kept, such
8101
+ as a dirty one (not one locked with git worktree lock), once it finished
8102
+ and was idle for gc.worktreeGraceMinutes (at least protectRecentHours),
8103
+ and of any
8104
+ workspace idle for maintenance.deviceIdleDays (3; 0 turns it off) in the
8105
+ age sweep. Devices are recreated on the next run.
7586
8106
  Disk-driven steps stop once free space is back above the floor. Kept for
7587
8107
  every pass: a workspace that is in use, holds a lock, was used within
7588
8108
  maintenance.protectRecentHours, is pinned with maintenance.keep, or is the
7589
8109
  workspace of the command that started the pass; cache entries used within
7590
8110
  protectRecentHours, named by a project's last builds, a parked device or a
7591
8111
  live build lock, or in a Metro store with a running or unverifiable dev
7592
- server. Anything the pass cannot verify is kept. A pass never shuts down idle devices or dev servers or stops watchman; removing a
8112
+ server. Anything the pass cannot verify is kept. A device is deleted only when
8113
+ its workspace is idle: no dev server, native run, build, lease, managed lock,
8114
+ driven or busy device, and no recorded use within the window. Below disk
8115
+ pressure level 3 a pass never stops a dev server or watchman; removing a
7593
8116
  finished worktree or orphaned directory does tear down that workspace's own
7594
8117
  dev server, owned devices and Chrome profile, as gc --delete does. ccache evicts by itself under
7595
8118
  caches.ccacheMaxGb.
7596
8119
 
8120
+ DISK PRESSURE LEVELS
8121
+ Level 1 is free disk under the floor above (budget.minFreeDiskGb, 20 GB).
8122
+ Level 2 is under maintenance.urgentFreeDiskGb (12), and level 3 is under
8123
+ maintenance.criticalFreeDiskGb (6) or budget.hardFloorDiskGb, where builds
8124
+ refuse. When level 1 leaves free disk under the floor, a pass goes on with
8125
+ level 2, which runs until free disk is back above the floor; level 3 runs
8126
+ only while free disk stays under the level 3 floor. At levels 2 and 3 the
8127
+ protection window is maintenance.pressureProtectMinutes (10) instead of
8128
+ protectRecentHours. Each step re-measures first and stops once free disk is
8129
+ back above its level's floor, cheapest to rebuild first.
8130
+ Level 2:
8131
+ 1. build outputs of workspaces unused for the short window
8132
+ 2. parked simulators and emulators, deleted through centralized teardown
8133
+ 3. build directories of idle worktrees: ios/build, android/app/build,
8134
+ android/build, android/.gradle and .build, at a project root, the
8135
+ worktree root, or apps/* and packages/*
8136
+ 4. owned simulators and emulators of workspaces idle for the short window
8137
+ 5. Stim temp directories and xctrace recordings in TMPDIR unmodified for
8138
+ 24 hours, with gc --delete's checks (see stim guide cleanup gc)
8139
+ Level 3:
8140
+ 1. the Swift compilation cache, emptied whole under the same build rule;
8141
+ when a build keeps it, the last pass's blocked reasons name its size
8142
+ and the build
8143
+ 2. finished worktrees, and clean, pushed worktrees with no commits of
8144
+ their own beyond the default branch, with the short window as grace
8145
+ period (maintenance.removeFinishedWorktrees still applies)
8146
+ 3. workspaces whose dev server runs but that are otherwise inactive for
8147
+ the short window: their dev server stops and their owned simulators
8148
+ and emulators are deleted
8149
+ 4. node_modules and ios/Pods at the root or a project root of idle linked
8150
+ worktrees, never the source checkout
8151
+ 5. idle Gradle and Kotlin daemons, stopped as gc --delete --cache
8152
+ gradle-daemons stops them (never during a Stim build, never a busy or
8153
+ unverified one, never with STIM_HOME set), only when every Gradle
8154
+ process that keeps step 6 from running is such a daemon; the pass
8155
+ waits up to 3 seconds for them to exit
8156
+ 6. Gradle version caches and wrapper distributions no registered project
8157
+ pins, once unmodified for maintenance.gradleVersionIdleDays days (7;
8158
+ 0 turns steps 5 and 6 off), with the same checks as gc --cache
8159
+ gradle-versions, run again after step 5 so a daemon that did not exit
8160
+ still keeps them
8161
+ A directory is cleared only when it is a real directory inside its
8162
+ worktree, git tracks nothing in it, its worktree is not in use, holds no
8163
+ lock or build and is not pinned, and nothing under it, nor the worktree's
8164
+ git index, HEAD or Stim state, changed within the short window. A dirty or
8165
+ unpushed worktree can still be stopped and have these directories cleared;
8166
+ only its removal is refused. Caches of other tools (pnpm and npm stores,
8167
+ CocoaPods, SwiftPM, the shared Gradle caches) are never deleted.
8168
+ status shows the level and, at level 3, the shortfall in the last pass;
8169
+ every command prints one stderr line while the last check, at most 10
8170
+ minutes old, measured level 3.
8171
+
7597
8172
  stim status last checks, plan and running pass
7598
8173
  stim status --json maintenance observations and recent records
7599
8174
  stim logs --source maintenance this workspace's maintenance records
@@ -7608,6 +8183,9 @@ In report mode actions and explaining skips are logged only when newly
7608
8183
  planned; in on mode every action taken, failure and explaining skip is logged
7609
8184
  with its bytes, and a pass record carries the bytes freed. Removal of a
7610
8185
  worktree or orphaned directory is logged to the machine log only.
8186
+ An iOS build that fails on a corrupt compilation cache logs the emptying,
8187
+ or why Stim left the cache alone, with trigger "ios build", in every mode;
8188
+ see \`stim guide errors STIM_COMPILATION_CACHE_CORRUPT\`.
7611
8189
  maintenance.logChecks enables debug observations; default false.
7612
8190
  status and doctor report invalid settings and unresolved claims with a removal
7613
8191
  command to run only after confirming the holder is gone. Invalid cache caps
@@ -7615,6 +8193,10 @@ fall back to their own defaults; invalid maintenance settings disable passes.
7615
8193
  Pin a workspace with \`stim settings set maintenance.keep true\` in its project.
7616
8194
  The run claim serializes passes with gc --delete; gc refuses a held claim
7617
8195
  with the holder and recovery guidance instead of waiting.
8196
+ Each pass keeps a 1 MiB file, maintenance/disk-reserve. When taking the run
8197
+ claim fails with ENOSPC or EDQUOT, the pass deletes it and retries once, so a
8198
+ pass starts at zero free disk as long as an earlier pass left the reserve; the
8199
+ pass writes it again at its end.
7618
8200
 
7619
8201
  CLEANUP AND DISK
7620
8202
 
@@ -7630,15 +8212,22 @@ See stim guide logs for AGENT_DEVICE_STATE_DIR examples.
7630
8212
  WHAT RECLAIMS AN OWNED DEVICE
7631
8213
  stim worktree remove parks eligible owned simulators and emulators
7632
8214
  (\`guide lifecycle pool\`); deletes them when
7633
- parking is disabled or their setup cannot be verified
8215
+ parking is disabled, their setup cannot be
8216
+ verified, or free disk is at pressure level 2 or 3
7634
8217
  stim gc --delete sweeps devices this Stim home created that no project
7635
8218
  references (\`guide cleanup gc\`), clears
7636
8219
  verified parked simulators and emulators, and
7637
- runs \`stim worktree remove\` on every clean,
7638
- Stim-managed linked worktree whose branch is merged,
8220
+ runs \`stim worktree remove\` on every clean
8221
+ linked worktree of a registered repository whose
8222
+ branch is merged,
7639
8223
  or whose pull request was merged or closed, and
7640
8224
  that shows no activity within
7641
- gc.worktreeGraceMinutes
8225
+ gc.worktreeGraceMinutes, and deletes the owned
8226
+ devices of a finished worktree it keeps (dirty,
8227
+ unpushed, locked) when its workspace is idle for
8228
+ that grace and not pinned by maintenance.keep
8229
+ automatic maintenance deletes the owned devices of idle workspaces
8230
+ (MAINTENANCE above)
7642
8231
  stim gc --delete --older-than <days>
7643
8232
  also reaps the device of a workspace no Stim
7644
8233
  command has used in that long, even though the
@@ -7716,7 +8305,7 @@ to stats.json.corrupt-<unix ms> and starts a new one.`,
7716
8305
  Archived log subscriptions send retained records, then logs-ended.
7717
8306
  Archived frame subscriptions need at and cannot be physical or go live.
7718
8307
  Replay ranges report recording disabled.
7719
- Reads stay on this Mac.
8308
+ Reads stay on this machine.
7720
8309
  An updated server is required; older servers refuse with bad-request when
7721
8310
  workspace is omitted. There is no CLI archive log flag.
7722
8311
  archive.enabled defaults true and accepts every settings layer. STIM_HOME
@@ -7789,6 +8378,69 @@ caches, never changes actionable, and --delete and --older-than never select
7789
8378
  it. Any --cache scope, including all, omits measurement and reports
7790
8379
  sections.swiftpmCache: null. Only stats and unscoped gc measure it.
7791
8380
 
8381
+ GRADLE VERSIONS
8382
+ \`stim gc --cache gradle-versions\` lists, and with --delete removes, the
8383
+ caches/<version> and wrapper/dists/gradle-<version>-* directories under
8384
+ GRADLE_USER_HOME of each Gradle version no registered project pins, read from
8385
+ the wrapper of each project's Android Gradle root and root. Nothing is deleted
8386
+ while a process that may be Gradle using this home runs: a Gradle main
8387
+ class, a Gradle wrapper or distribution jar, or a java process naming a path
8388
+ under the home, unless it names no such path and every gradle.user.home or
8389
+ -g it names is an existing Gradle home elsewhere, while ps fails, while no Stim config
8390
+ exists, while a registered project is missing or its pin cannot be read,
8391
+ while a Gradle directory cannot be listed, or while caches, wrapper or
8392
+ wrapper/dists is a symlink or resolves outside the home;
8393
+ sections.gradleVersions.blocked names the reason. A version is also kept
8394
+ when one of its paths is not a plain directory inside the home, when an
8395
+ entry under it cannot be listed or stat'd, when it holds more than 500000
8396
+ entries, or, with --older-than <days>, when anything under it changed within
8397
+ that many days.
8398
+ Right before removal gc lists processes and reads pins again, then renames
8399
+ each directory to a .stim-delete-* sibling and deletes it. A later gc
8400
+ --delete --cache gradle-versions finishes any .stim-delete-* left by a failed
8401
+ removal, even while the versions are blocked (not while the home is outside
8402
+ a scoped STIM_HOME or its layout is unsafe), since nothing uses those
8403
+ directories, and reports it again when that fails; plain gc --delete and
8404
+ automatic maintenance never touch them. modules-2, build-cache-1, jdks and
8405
+ other shared Gradle caches are never deleted, and --cache all never includes these versions.
8406
+ With STIM_HOME set, gc deletes nothing in a Gradle home outside it. At disk
8407
+ pressure level 3 automatic maintenance first stops the idle Gradle and Kotlin
8408
+ daemons gc --delete --cache gradle-daemons would stop, only when every Gradle
8409
+ process that keeps a version is such a daemon, then does the same for
8410
+ versions unmodified for maintenance.gradleVersionIdleDays days (7; 0 turns
8411
+ both off). A manual gc --delete --cache gradle-versions never stops daemons.
8412
+
8413
+ OTHER TOOLS' MACHINE-WIDE CACHES (REPORT ONLY)
8414
+ Unscoped gc also reports large caches other tools keep for the whole machine,
8415
+ each with its size and the owning tool's own remedy:
8416
+ - Gradle version caches and wrapper dists under GRADLE_USER_HOME (default
8417
+ ~/.gradle) for versions no registered project pins in the
8418
+ gradle/wrapper/gradle-wrapper.properties of its Android Gradle root
8419
+ (android.gradleRoot, default android, relative to the app, inside the
8420
+ repository; a custom root must hold settings.gradle and the module's build
8421
+ script) or of the project root, and caches/modules-2. A registered project
8422
+ that is missing, whose settings cannot be resolved, or whose wrapper cannot
8423
+ be read or names no Gradle version is named, since it may still pin a
8424
+ listed version; every version a readable wrapper of it pins still counts as
8425
+ pinned. A directory that cannot be listed adds a notice and marks the
8426
+ section incomplete.
8427
+ Gradle removes version caches and modules-2 entries unused for 30 days on
8428
+ its own. The remedy for a version is stim gc --delete --cache
8429
+ gradle-versions (see GRADLE VERSIONS); modules-2 stays report only.
8430
+ - iOS simulator runtimes with their simulator count. Only a deletable
8431
+ runtime no simulator uses gets the remedy xcrun simctl runtime delete <id>.
8432
+ - Xcode device support entries under ~/Library/Developer/Xcode/*DeviceSupport.
8433
+ Xcode copies an entry again when that device next connects.
8434
+ - agent-device runner builds grouped by agent-device and Xcode build version,
8435
+ compared with the installed agent-device and Xcode. agent-device has no
8436
+ prune command; remove an entry not in use by hand.
8437
+ $STIM_HOME/machine-cache-usage.json caches the sizes, simulator runtimes and
8438
+ tool versions for 10 minutes and measures again when the measured
8439
+ directories change. Stim never deletes any of these. They are never in
8440
+ caches, never change actionable, and --delete and --older-than never select
8441
+ them. Any --cache scope omits measurement and reports sections.machineCaches:
8442
+ null.
8443
+
7792
8444
  LINKED WORKTREES
7793
8445
  \`stim worktree remove\` works with any linked worktree, warmed or not.
7794
8446
  Git registration identifies the worktree; a Stim registry entry is not
@@ -7819,20 +8471,43 @@ IN USE
7819
8471
  existence cannot be read (a permission error) is never treated as deleted.
7820
8472
 
7821
8473
  SWEEPING FINISHED WORKTREES
7822
- Every \`gc\` without --cache looks at every registered project root and
7823
- every workspace.json root, grouped by git worktree, and reports each linked
8474
+ Every \`gc\` without --cache looks at every registered project root,
8475
+ every workspace.json root, and every linked worktree \`git worktree list\`
8476
+ shows for a registered project's repository, whether or not a stim command
8477
+ ever ran in it. It groups them by git worktree and reports each linked
7824
8478
  worktree with the reason it is removed or kept. A worktree is finished when
7825
8479
  its branch is merged into the default branch, or its pull request was
7826
8480
  merged or closed (PULL REQUESTS below). \`--worktrees\` also removes
7827
8481
  one that is idle: no recorded use for --older-than days, 7 without it; a
7828
- worktree whose last use is unknown is kept. Both need the same clean state;
7829
- a worktree is kept when it is the source checkout, bare, locked, in use,
8482
+ worktree whose last use is unknown is kept, unless it has no commits of its
8483
+ own beyond the default branch (NO COMMITS OF ITS OWN below). Both need the
8484
+ same clean state;
8485
+ a worktree is kept when it is the source checkout, bare, locked, holds
8486
+ another worktree (worktree remove refuses it too), in use,
7830
8487
  dirty (untracked files count; pod install churn and
7831
8488
  watchman's untracked \`.watchman-cookie-*\` files alone do not), unpushed
7832
8489
  (commits no remote-tracking ref or other local branch reaches), or has
7833
8490
  initialized submodules. Without --worktrees, the report leaves out the
7834
8491
  source checkout and roots outside git.
7835
8492
 
8493
+ Each worktree line also shows its in-tree build state: the size of the
8494
+ build and dependency directories maintenance clears (DISK PRESSURE LEVELS
8495
+ above) and a root .pnpm-store, largest first (APFS clones count in full).
8496
+ A size comes from the disk-usage cache while it is under an hour old and
8497
+ no entry was added to or removed from its directory since (a build that
8498
+ only grows files deeper inside shows within the hour); otherwise gc runs
8499
+ one du per directory, 4 at once, 10 seconds each, and a directory du did
8500
+ not finish shows "size unknown". du counts hard links shared between two
8501
+ directories in both. gc never deletes them on their own;
8502
+ the JSON field is buildState. Only directories of this worktree count,
8503
+ not those of a worktree nested inside it.
8504
+
8505
+ A directory directly under a registered repository's .worktrees/ that
8506
+ \`git worktree list\` does not show and that no git knows (no .git, or a
8507
+ .git file whose gitdir is gone, as after a prune) is listed under "Directories under .worktrees that git does
8508
+ not list as worktrees" with its size and the rm -rf command, and never
8509
+ removed: gc cannot check it for uncommitted work. Look through it first.
8510
+
7836
8511
  IN USE means its dev server supervisor runs, a stim ios, android or stop
7837
8512
  run holds its native-run lock, a live build lock or slot names it, a
7838
8513
  managed tunnel or remote lock is held, an owned simulator or emulator of
@@ -7854,9 +8529,32 @@ SWEEPING FINISHED WORKTREES
7854
8529
  activity cannot be read, the worktree is kept (reason activity-unknown).
7855
8530
  0 turns the grace period off.
7856
8531
 
8532
+ FRESH STATE: \`gc --delete\` and the maintenance worktree check fetch the
8533
+ default branch and ask gh as MERGED and PULL REQUESTS below describe. The
8534
+ worktree check runs in the background when a stim command starts a
8535
+ maintenance pass that has it due, every maintenance.worktreeCheckMinutes;
8536
+ plain gc never starts one. It writes each pull request lookup into the
8537
+ cache \`status --watch\` also writes. A report without --delete runs no
8538
+ fetch and no gh: it judges merges from the existing origin/<default> ref and
8539
+ takes a pull request from that cache when it records the worktree's current
8540
+ branch and HEAD. Both count as fresh while they are younger than twice the
8541
+ worktree check interval, and never less than 10 minutes. A merge found
8542
+ against the existing ref stays true as the default branch moves on, so a
8543
+ report calls a worktree removable only on facts fresh state keeps, with
8544
+ three exceptions: a force-push that dropped the merge from the default
8545
+ branch, a pull request reopened since the lookup, and the grace period of a
8546
+ pull request merged or closed after its merge commit was made. A kept worktree whose
8547
+ verdict fresher state could change says "merge state not refreshed", and
8548
+ its reason not-merged becomes merge-unknown. With maintenance.mode off or
8549
+ maintenance.removeFinishedWorktrees false the check never runs, and only
8550
+ \`gc --delete\` refreshes. \`gc --delete\` always collects fresh state
8551
+ before it removes anything; \`stim worktree remove\` run from a report
8552
+ checks only for uncommitted and unpushed work.
8553
+
7857
8554
  MERGED means, with the default branch taken from origin/HEAD, after a
7858
8555
  \`git fetch origin <default>\` per repository (30s timeout, no credential
7859
- prompt; skipped when that checkout fetched in the last 10 minutes):
8556
+ prompt; skipped when that checkout fetched in the last 10 minutes; FRESH
8557
+ STATE above for a report):
7860
8558
  - HEAD is reachable from origin/<default> through a merge commit, and the
7861
8559
  branch's reflog shows a commit made on it that HEAD contains. A branch
7862
8560
  with no commit of its own -- fresh, cut from another branch, or reset
@@ -7877,8 +8575,23 @@ SWEEPING FINISHED WORKTREES
7877
8575
  not block removal, because their change is on the default branch, and the
7878
8576
  branch is kept.
7879
8577
 
7880
- PULL REQUESTS: gc runs one \`gh api graphql\` query per repository that
7881
- asks for the 20 newest pull requests of each linked worktree's branch, as
8578
+ NO COMMITS OF ITS OWN: a worktree whose HEAD the default branch contains
8579
+ and whose branch has no commit made on it (fresh, cut from another
8580
+ branch, or reset onto one; the MERGED rule above) holds nothing git would
8581
+ lose, but looks the same as one just made by git worktree add. Plain gc keeps
8582
+ it (reason not-merged, "no commits of its own beyond origin/<default>").
8583
+ \`--worktrees\` removes it when idle: by its recorded use when there is
8584
+ one, else by the RECENT ACTIVITY sources above being older than
8585
+ --older-than days (reason recently-used while they are newer, and
8586
+ last-use-unknown when they cannot be read). Automatic maintenance removes it
8587
+ only at disk pressure level 3, once that activity is older than
8588
+ maintenance.pressureProtectMinutes and at least 5 minutes old, whatever
8589
+ the setting; unreadable activity keeps it. Removal re-checks, under the removal
8590
+ locks, that HEAD did not move and the worktree is still idle, and
8591
+ \`stim worktree remove\` still refuses dirty or unpushed work.
8592
+
8593
+ PULL REQUESTS: with fresh state, gc runs one \`gh api graphql\` query per
8594
+ repository that asks for the 20 newest pull requests of each linked worktree's branch, as
7882
8595
  \`gh pr list --head <branch> --state all\` would, and for each worktree
7883
8596
  takes the pull request whose head is HEAD, else one whose head contains
7884
8597
  HEAD. A pull request whose head HEAD is past ("PR #12 merged, and HEAD has
@@ -7896,14 +8609,15 @@ SWEEPING FINISHED WORKTREES
7896
8609
  not looked up. \`stim worktree remove\` makes the same check when
7897
8610
  local-only commits alone would refuse the removal, and accepts a merged
7898
8611
  pull request whose head is or contains HEAD.
7899
- stim gc # report merged worktrees
7900
- stim gc --delete # remove them
8612
+ stim gc # report merged worktrees from local state
8613
+ stim gc --delete # refresh, then remove them
7901
8614
  stim gc --delete --worktrees --older-than 3 # also the clean idle ones
7902
8615
  --cache with --worktrees is refused with STIM_BAD_ARG; run them separately.
7903
8616
  With --delete it runs the \`stim worktree remove\` pipeline, never --force,
7904
8617
  on each removable worktree. That pipeline re-inspects the worktree and
7905
8618
  re-checks use and recent activity under the removal locks, and idleness
7906
- for an idle worktree or an unchanged HEAD for a merged one, then parks
8619
+ for an idle worktree, an unchanged HEAD for a merged one, or both for one
8620
+ with no commits of its own, then parks
7907
8621
  devices through the same teardown and handles the
7908
8622
  branch exactly as a manual \`stim worktree remove\`, which keeps the
7909
8623
  checkout when its HEAD moves while devices are reclaimed. A worktree that
@@ -7935,9 +8649,27 @@ ORPHANED WORKSPACE DIRECTORIES
7935
8649
  it. A directory with a missing or unparseable workspace.json, or a root on
7936
8650
  an unmounted volume, is reported under Skipped and never deleted.
7937
8651
 
8652
+ TMPDIR LEFTOVERS
8653
+ \`gc\` without --cache reports recognized entries directly in the user's TMPDIR
8654
+ under "TMPDIR leftovers", with their count, size and a remedy: Stim temp
8655
+ directories (stim-claim-*, stim-host-*, stim-fakehome-*,
8656
+ stim-hosted-macos-*, stim-server-*, stim-ws-*), xctrace recordings
8657
+ (instruments*.ktrace) and Jest's jest_<uid in base 36> cache. \`gc --delete\` checks
8658
+ them again and deletes a Stim temp directory or recording only when nothing
8659
+ in it changed for 24 hours. It keeps a Stim temp directory that contains or
8660
+ sits inside a registered project, holds a live or unverifiable ownership
8661
+ claim or a .lock directory that is not a claim set, or cannot be listed. It
8662
+ keeps a recording that cannot be read, and every recording while an xctrace
8663
+ or Instruments process runs or the process list cannot be read. Jest's cache and every other entry
8664
+ are never deleted. gc never follows a symlink and never acts outside TMPDIR.
8665
+ With STIM_HOME set it neither reports nor deletes them, because TMPDIR is
8666
+ shared by every Stim home. Automatic maintenance runs the same deletion at disk pressure
8667
+ level 2.
8668
+
7938
8669
  STALE STATUS CACHE ENTRIES
7939
8670
  \`stim status\` caches each folder's size under $STIM_HOME/disk-usage and
7940
- each worktree's pull request under $STIM_HOME/pull-requests. \`worktree
8671
+ each worktree's pull request under $STIM_HOME/pull-requests, which the
8672
+ maintenance worktree check and \`gc --delete\` also write. \`worktree
7941
8673
  remove\` drops the entries of the worktree it removed (its checkout,
7942
8674
  node_modules and workspace folder, and its pull request), not the size
7943
8675
  entries of its owned devices' data folders. \`gc\` reports the
@@ -8037,6 +8769,12 @@ run drops the dead config ENTRIES those projects left behind and frees their
8037
8769
  Metro ports. \`stim gc --json\` prints the same report as one payload
8038
8770
  (\`guide facts gc\`); show the user its sections before \`gc --delete\`.
8039
8771
 
8772
+ Ctrl-C stops pending GC actions and drains verified owned children before exiting
8773
+ 130. Completed actions remain applied. Unverifiable child cleanup keeps its
8774
+ ownership claims and prints STIM_CLAIM_UNAVAILABLE with a recovery remedy.
8775
+ With --json, cancellation prints one STIM_CANCELLED payload, or the cleanup
8776
+ refusal when cleanup cannot be verified.
8777
+
8040
8778
  REMOTE EAS SESSIONS
8041
8779
  Plain \`stim gc\` is a dry run. \`gc --delete\` can stop active stim-* EAS
8042
8780
  sessions after workspace state is missing. The stop needs verified
@@ -8303,8 +9041,11 @@ WORKSPACE BUILD OUTPUTS
8303
9041
  and worktree warm record in state.json, lastBuild.startedAt,
8304
9042
  supervisor.startedAt and the mtimes under logs/, so a dev server that keeps
8305
9043
  logging keeps its workspace in use.
8306
- The same time decides \`--older-than\` device reaping, and a workspace with
8307
- no evidence of use keeps its device.
9044
+ A workspace with none of those was last used when its workspace.json was
9045
+ written, which happens once, when the workspace is created; only a
9046
+ workspace with no readable workspace.json has an unknown last use. The
9047
+ same time decides \`--older-than\` device reaping and automatic device
9048
+ deletion, and a workspace whose last use is unknown keeps its device.
8308
9049
  The next build of an unchanged app installs from the shared build cache.
8309
9050
  After a native change the Xcode compilation cache speeds the rebuild, but on
8310
9051
  React Native 0.86 Swift does not use it (explicit modules are off), so that
@@ -8492,9 +9233,10 @@ KEYS STIM READS
8492
9233
  flag overrides this per invocation. Unset means Debug.
8493
9234
  ios.remote "proxy", "eas", or a named approved Mac from
8494
9235
  remote.machines, with the same meaning as --remote.
8495
- "auto" places on an approved Mac when this Mac is full or
9236
+ "auto" places on an approved Mac when this machine is full or
8496
9237
  busy. Unset runs here; "local" runs here even when a
8497
- lower layer says otherwise. See lifecycle hosted-ios.
9238
+ lower layer says otherwise. A --device run ignores
9239
+ it. See lifecycle hosted-ios.
8498
9240
  ios.projectPath directory under the app holding a bare React Native
8499
9241
  app's Xcode project and Podfile; default "ios". "."
8500
9242
  is the app directory itself (RNTester's layout).
@@ -8535,6 +9277,19 @@ KEYS STIM READS
8535
9277
  name: Stim is non-interactive, so it refuses an
8536
9278
  ambiguous identity rather than picking one. It wins
8537
9279
  over ios.signingIdentity.
9280
+ ios.appStoreConnectKeyPath, ios.appStoreConnectKeyId, ios.appStoreConnectIssuerId
9281
+ machine scope only; STIM_ASC_KEY_PATH, STIM_ASC_KEY_ID
9282
+ and STIM_ASC_ISSUER_ID each override their own
9283
+ setting when set and non-empty. The absolute
9284
+ path of an App Store Connect API key (.p8), its key
9285
+ ID and its issuer ID. Only \`stim device provision
9286
+ ios\` reads them, passing them to xcodebuild as
9287
+ -authenticationKeyPath/-authenticationKeyID/
9288
+ -authenticationKeyIssuerID instead of the Xcode
9289
+ account, so provisioning works on a Mac where nobody
9290
+ signs in to Xcode. Set all three or none; Stim never
9291
+ reads the key file or prints its path. Builds never
9292
+ use them.
8538
9293
  ios.lanHost e.g. "192.168.1.42" -- the address a phone uses to
8539
9294
  reach this workspace's Metro on an \`ios --device\`
8540
9295
  Debug run, pinning the interface on a multi-NIC Mac
@@ -8653,6 +9408,18 @@ ${ANDROID_AVD_CONFIG_HELP.map((line) => ` ${line}`).joi
8653
9408
  build: embedded JS, no Metro, cache keyed on the
8654
9409
  variant, and an APK re-pack on cache hits. See
8655
9410
  \`guide lifecycle release\`.
9411
+ android.artifactInputs a native Gradle APK cache declaration:
9412
+ complete: true, ignored: [exact repository-relative files],
9413
+ outputs: [generated repository-relative directories],
9414
+ localFiles: [exact project-relative or absolute files],
9415
+ environment: [variable names]. complete promises all
9416
+ inputs are repeatable and dependencies pinned. Include
9417
+ signing keys and every external or environment input.
9418
+ Missing local files and environment values are hashed
9419
+ as absent. Configured files providers can share the APK
9420
+ and verification receipt. Input values stay local;
9421
+ this is not build-worker transfer authorization.
9422
+ See the native Android guide.
8656
9423
  android.offloadInputs a native Gradle build-worker declaration:
8657
9424
  {"complete":true,"ignored":[],"outputs":["build","app/build"]}.
8658
9425
  complete confirms Git-visible source plus the exact
@@ -8660,7 +9427,7 @@ ${ANDROID_AVD_CONFIG_HELP.map((line) => ` ${line}`).joi
8660
9427
  build. outputs names generated directories reported
8661
9428
  by AGP; recorded outputs can survive worker sync.
8662
9429
  Omitted optional inputs can change build results.
8663
- Stim artifact caching remains unavailable. See
9430
+ Worker APK caching also requires artifactInputs. See
8664
9431
  \`stim guide lifecycle native-android\` for input,
8665
9432
  privacy and worker requirements.
8666
9433
  android.keystore the keystore a RE-PACKED release APK is signed with,
@@ -8685,16 +9452,17 @@ ${ANDROID_AVD_CONFIG_HELP.map((line) => ` ${line}`).joi
8685
9452
  the debug keystore's fixed "android".
8686
9453
  android.remote "proxy", "eas", or a named approved Mac in
8687
9454
  remote.machines; "auto" places on an approved Mac when
8688
- this Mac is full or busy. Unset runs here; "local"
9455
+ this machine is full or busy. Unset runs here; "local"
8689
9456
  runs here even when a lower layer says otherwise.
8690
- See lifecycle hosted-android.
9457
+ A --device run ignores it. See lifecycle
9458
+ hosted-android.
8691
9459
  remote.easFallback true lets "auto" (ios.remote, android.remote or
8692
9460
  --remote auto) run the simulator or emulator on a
8693
- billed EAS Simulator when this Mac is at its
9461
+ billed EAS Simulator when this machine is at its
8694
9462
  concurrency.maxDevices cap (or has runs queued) and no
8695
- remote.machines Mac takes the run, instead of waiting
9463
+ remote.machines entry takes the run, instead of waiting
8696
9464
  or refusing with STIM_AT_CAPACITY. Default false. A
8697
- busy Mac with a free slot never uses it. Stim checks
9465
+ busy machine with a free slot never uses it. Stim checks
8698
9466
  first that the run could use --remote eas (eas-cli
8699
9467
  with simulator commands, eas simulator:availability,
8700
9468
  agent-device, the default slot, a reachable Metro);
@@ -8802,6 +9570,8 @@ ${ANDROID_AVD_CONFIG_HELP.map((line) => ` ${line}`).joi
8802
9570
  Debug by stim macos
8803
9571
  macos.infoPlist development Info.plist relative to Package.swift,
8804
9572
  without shared URL schemes or an update feed
9573
+ macos.artifactInputs complete pinned, repeatable inputs producing a relocatable
9574
+ app for completed build caching. See stim guide macos.
8805
9575
  macos.arguments string array passed directly to the executable;
8806
9576
  see stim guide macos for the prototype's limits
8807
9577
  macos.assetCatalog optional .xcassets directory relative to Package.swift,
@@ -9052,51 +9822,61 @@ test suites and the end-to-end harness use one -- and a scoped config must not
9052
9822
  leave simulators on the machine it cannot account for. A redirected home that
9053
9823
  wants a pool says so with the variable.
9054
9824
 
9055
- DESKTOP ACCESS TICKETS
9056
- Stim Desktop's Add wizard sets STIM_ACCESS_TICKET for one \`doctor --fix\`
9057
- run. Agents never set it, never run \`stim-server setup\`, never edit
9825
+ SETUP TICKETS
9826
+ A remote machine approves this one when a person there runs
9827
+ \`stim-server setup\` with this machine's setup ticket (32 random bytes, 43
9828
+ base64url characters) in STIM_SETUP_TICKET. Stim Desktop's Add wizard makes the
9829
+ ticket and sets STIM_ACCESS_TICKET for one \`doctor --fix\` run; without it,
9830
+ \`doctor --fix\` makes one and prints the setup command to run on the worker.
9831
+ Agents never set STIM_ACCESS_TICKET, never run \`stim-server setup\`, never edit
9058
9832
  \`remote.*\`, \`server.*\` or \`hosting.*\` for the user, and never approve requests.
9059
- Stim sends the ticket (43 base64url characters) only in build and device-host access
9060
- requests and stores only its SHA-256 ticketHash. With --fix, a still-pending
9061
- request is replaced when its ticketHash is missing or differs from the current
9062
- ticket; matching hashes and approved credentials are preserved.
9833
+ Until setup approves, the worker answers unknown-secret and the entry stays
9834
+ pending; every doctor run tries again. The first connection after approval pins
9835
+ the worker's stim-server key in build-machines.json or device-host-machines.json,
9836
+ and every later connection must prove that key; the tailnet node and address
9837
+ never decide who the worker is. With --fix, a pending entry is replaced when
9838
+ STIM_ACCESS_TICKET names another ticket.
9063
9839
  Approved doctor entries under remoteMachines and deviceHosts include host
9064
9840
  { name, screenRecording, accessibility } when the worker reports it.
9065
9841
 
9066
- REMOTE MACS AND PHONE APP
9067
- When the user mentions slow builds, a busy Mac, a full disk or device limit, or
9842
+ REMOTE MACHINES AND PHONE APP
9843
+ When the user mentions slow builds, a busy machine, a full disk or device limit, or
9068
9844
  wanting updates away from the desk, tell them about Stim Desktop > Settings >
9069
- Remote Macs > Add (a Mac on the tailnet that builds or hosts simulators for
9070
- this one) or Phones > Pair a Phone (the Stim phone app). Stim Desktop also
9071
- suggests these itself, at most once a day and never during a build. Agents only
9072
- tell the user: they never run \`stim-server setup\`, never edit \`remote.*\`,
9073
- \`server.*\` or \`hosting.*\` for the user, and never approve requests.
9074
-
9075
- REMOTE MACS ARE MACHINE-LEVEL
9076
- \`remote.machines\` names the remote Macs this Mac may build on and host owned
9845
+ Remote Machines > Add (another machine on the tailnet that builds for this one,
9846
+ or a Mac that also hosts simulators) or Phones > Pair a Phone (the Stim phone
9847
+ app). Stim Desktop also suggests these itself, at most once a day and never
9848
+ during a build. Agents only tell the user: they never run \`stim-server setup\`,
9849
+ never edit \`remote.*\`, \`server.*\` or \`hosting.*\` for the user, and never
9850
+ approve requests.
9851
+
9852
+ REMOTE MACHINES ARE MACHINE-LEVEL
9853
+ \`remote.machines\` names the remote machines this machine may build on and host owned
9077
9854
  simulator sessions on, by MagicDNS name with an optional serve port (default
9078
9855
  7443). Name each node and port once:
9079
9856
 
9080
9857
  stim settings set remote.machines '["janics-mac-mini"]'
9081
9858
  stim doctor --fix
9082
9859
 
9083
- Automatic membership is separate from approval. In Desktop Settings > Remote Macs,
9084
- use Builds enabled and Simulators enabled for this Mac or a configured remote.
9085
- The equivalent machine settings list the excluded members; both default to []:
9860
+ Automatic membership is separate from approval. In Desktop Settings > Remote
9861
+ Machines, use Builds enabled and Simulators enabled for this machine or a
9862
+ configured remote. The equivalent machine settings list the excluded members;
9863
+ both default to []:
9086
9864
 
9087
9865
  stim settings set remote.buildPoolDisabled '["local"]'
9088
9866
  stim settings set remote.devicePoolDisabled '["janics-mac-mini"]'
9089
9867
  stim settings unset remote.buildPoolDisabled
9090
9868
 
9091
- Use local for this Mac and exact remote.machines entries for remotes, including
9869
+ Use local for this machine and exact remote.machines entries for remotes, including
9092
9870
  case and port. Names are not trimmed; unmatched entries exclude nothing. Copy the
9093
9871
  configured name or use Desktop's switches. Each pool
9094
9872
  must retain local or at least one configured remote already approved for that role.
9095
9873
  Offline approved members count as configured members, but placement still requires
9096
- an available compatible host. Settings refuses removing the last member, including
9097
- removing it from remote.machines. Disabling does not unpair a machine or stop a run.
9874
+ an available compatible host. A remote that tailscale status reports as running an
9875
+ OS other than macOS is never a device-pool member or hosting candidate, since it
9876
+ cannot host; an unknown OS counts as able to host. Settings refuses removing the
9877
+ last member, including removing it from remote.machines. Disabling does not unpair a machine or stop a run.
9098
9878
 
9099
- These settings apply only to new automatic work requested by this Mac. They do not
9879
+ These settings apply only to new automatic work requested by this machine. They do not
9100
9880
  change which work other requesters send to a host. Named placement and --remote-build
9101
9881
  local bypass membership; the default device placement without --remote auto remains
9102
9882
  local. Existing local and hosted device sessions keep their owner. Cache hits remain
@@ -9106,16 +9886,33 @@ local alone does not trigger billed EAS fallback; an existing explicit EAS opt-i
9106
9886
  requires the physical device cap or queue condition. Restore membership before retrying or choose
9107
9887
  an explicit placement. Build and simulator memberships are independent.
9108
9888
 
9109
- A remote Mac is used for a capability only after it grants that approval. Build
9110
- and device-host approvals are separate: a Mac in the list that never granted
9889
+ A remote machine is used for a capability only after it grants that approval. Build
9890
+ and device-host approvals are separate: a machine in the list that never granted
9111
9891
  one is not an error, it is not used for that capability, and doctor reports
9112
9892
  it plainly.
9113
9893
 
9894
+ PAUSE INCOMING WORK
9895
+ On the worker machine, an operator can temporarily refuse new incoming builds or
9896
+ hosted sessions without revoking any clients:
9897
+
9898
+ stim settings set server.buildsPaused true
9899
+ stim settings set server.devicesPaused true
9900
+
9901
+ Both machine settings default to false. The running server reads changes without
9902
+ restarting and rechecks admission after an earlier offer. Running builds and
9903
+ existing iOS, Android and macOS hosted sessions continue; attach, cached artifact
9904
+ retrieval and stop remain available. Local CLI builds and devices are unaffected.
9905
+ These settings apply to every requesting machine, including explicit placement.
9906
+ Desktop's pool switches instead affect only that requester's automatic placement.
9907
+ Set either setting to false to resume that capability. A pause does not make the
9908
+ machine idle: wait for existing work to finish or stop the relevant sessions before
9909
+ benchmarking. Agents change these settings only when the user asks them to.
9910
+
9114
9911
  DEVICE HOSTING
9115
- Only \`doctor --fix\` asks for device-host access. A person on that Mac
9116
- approves the printed id with \`stim-server devices grant <id> --device-host\`.
9117
- A person can also run \`stim-server setup\` on the worker: one node, one ticket,
9118
- one expiry, at most one request per capability, with a per-grant Y/n question (Enter approves) in a
9912
+ Only \`doctor --fix\` asks for device-host access: it makes a setup ticket and
9913
+ prints the command. A person approves by running \`stim-server setup\` on the
9914
+ worker with that ticket: one ticket, one expiry, one record per capability for
9915
+ the key that holds the ticket, with a per-grant Y/n question (Enter approves) in a
9119
9916
  terminal or \`--yes\` otherwise. Agents never run \`stim-server setup\` or
9120
9917
  approve requests. Setup never changes TCC or enables Funnel; an SSH-driven
9121
9918
  run is not offered. Without a terminal or --yes, setup refuses before installing
@@ -9123,10 +9920,10 @@ unless every chosen capability already has a matching approval. Desktop reuse
9123
9920
  requires an existing tailnet route. Ctrl-C or SIGTERM completes the journal,
9124
9921
  releases the setup claim and exits 1; a typed N also exits 1. Hosting grants
9125
9922
  include no read, control or build capability.
9126
- On a remote Mac that hosts, Stim Desktop > Settings > Remote Macs > Running here lists the
9127
- simulators, emulators and apps approved Macs run there, below Macs using this
9128
- Mac. Stop asks for confirmation, ends the session and deletes or parks
9129
- its device on that Mac. Parked sessions remain listed without Stop. The list
9923
+ On a remote Mac that hosts, Stim Desktop > Settings > Remote Machines > Running
9924
+ here lists the simulators, emulators and apps approved machines run there, below
9925
+ Macs Using This Mac. Stop asks for confirmation, ends the session and deletes
9926
+ or parks its device on that Mac. Parked sessions remain listed without Stop. The list
9130
9927
  refreshes every five seconds and stays hidden when the local server does not
9131
9928
  support it.
9132
9929
  $STIM_HOME/device-host-machines.json stores a private token and pinned tailnet
@@ -9136,7 +9933,7 @@ stim settings set ios.remote auto --scope machine (or android.remote; workspace,
9136
9933
  repo and committed scopes override the machine default),
9137
9934
  or per run stim ios --remote auto / stim ios --remote <machine>.
9138
9935
  New Desktop runs pass no --remote flag; recorded hosted sessions keep their
9139
- machine until stim stop. Only devices outside this Mac show "on <machine>"
9936
+ machine until stim stop. Only devices outside this machine show "on <machine>"
9140
9937
  on tiles, workspace pages, viewer toolbars and sidebar rows, with the reason
9141
9938
  as hover text.
9142
9939
  Stim sends tokens only to the pinned node's own tailnet address,
@@ -9162,30 +9959,29 @@ shared daemon only when that agent-device can lease a single app (its
9162
9959
  notice, and no client is handed the Mac's desktop. STIM_AGENT_DEVICE_BIN in
9163
9960
  stim-server's environment names an agent-device binary to use instead of
9164
9961
  ~/.local/bin/agent-device. Hosted iOS uses one daemon per simulator, pinned by
9165
- its UDID policy; both Macs need agent-device 0.21.20 or later. Hosted Android
9962
+ its UDID policy; both machines need agent-device 0.21.20 or later. Hosted Android
9166
9963
  uses one daemon per emulator, pinned by its serial policy, with agent-device
9167
- 0.21.22 or later on both Macs. See guide lifecycle
9964
+ 0.21.22 or later on both machines. See guide lifecycle
9168
9965
  hosted-ios. \`doctor\` on that Mac notes an installed hosted app that runs while
9169
9966
  the setting is \`none\`.
9170
9967
 
9171
9968
  REMOTE BUILDS
9172
- The same \`remote.machines\` list names the Macs that may build for this one:
9969
+ The same \`remote.machines\` list names the machines that may build for this one:
9173
9970
 
9174
9971
  {
9175
9972
  "remote": { "machines": ["janics-mac-mini"] }
9176
9973
  }
9177
9974
 
9178
- \`doctor --fix\`, run in any app directory, asks each named Mac for build access and pins its tailnet
9179
- node in $STIM_HOME/build-machines.json. A person approves the request on that
9180
- Mac with \`stim-server devices grant <id> --build\`; doctor prints the id.
9181
- A person can instead run \`stim-server setup\` on the worker with a node,
9182
- ticket and expiry, under the approval rule above. A build grant is full access
9975
+ \`doctor --fix\`, run in any app directory, makes a setup ticket for each named
9976
+ machine and prints the \`stim-server setup\` command a person runs on that
9977
+ machine to approve this one, under the approval rule above. The next doctor run
9978
+ pins the worker's stim-server key in $STIM_HOME/build-machines.json. A build grant is full access
9183
9979
  to the account stim-server runs as: project code runs there unsandboxed, so it
9184
9980
  can grant itself read and control. Revoking it does not undo what that code
9185
- already did. Grant it only to Macs you trust.
9186
- Stim connects to a named Mac only while its name still belongs to the pinned
9981
+ already did. Grant it only to machines you trust.
9982
+ Stim connects to a named machine only while its name still belongs to the pinned
9187
9983
  node and never sends the token to another node. \`doctor\` reports each
9188
- remote Mac's build pairing state and, for an approved one, asks it for one build
9984
+ remote machine's build pairing state and, for an approved one, asks it for one build
9189
9985
  offer and lists every reason it would not take this app's iOS build: no
9190
9986
  answer, another Stim build, CPU, Xcode, simulator SDK or CocoaPods, no
9191
9987
  Bundler for an app whose Gemfile.lock pins CocoaPods, no iPhone simulator on
@@ -9200,12 +9996,12 @@ configured names case-insensitively, with port 7443 when omitted; reports use
9200
9996
  the configured entry.
9201
9997
 
9202
9998
  auto follows remote.buildMode, considering only enabled automatic pool members
9203
- local builds only on this Mac for this invocation
9999
+ local builds only on this machine for this invocation
9204
10000
  name requires a matching entry in remote.machines, already paired and
9205
- approved for builds; ignores remote.buildMode and this Mac's load/slot gating
10001
+ approved for builds; ignores remote.buildMode and this machine's load/slot gating
9206
10002
 
9207
- A name never prompts for pairing, tries another remote Mac or falls back here.
9208
- Any refusal or failure is STIM_OFFLOAD_REFUSED with the remote Mac and reason.
10003
+ A name never prompts for pairing, tries another remote machine or falls back here.
10004
+ Any refusal or failure is STIM_OFFLOAD_REFUSED with the remote machine and reason.
9209
10005
  Device, Release/non-Debug, --remote, Android CAS compiler builds, builds with
9210
10006
  the build cache off, and an unknown simulator runtime cannot build there and
9211
10007
  refuse on a cache miss. Invalid values, unlisted names and unpaired names refuse
@@ -9213,12 +10009,11 @@ at setup before consulting the cache; they create no build record or failed-run
9213
10009
  stats. A listed paired name with a cache hit compiles nothing, contacts no worker
9214
10010
  and records buildMachine with builtOn absent. Build failures retain the selected
9215
10011
  value; builtOn is here or the configured machine entry when compilation ran.
9216
- Prebuild and pod install still run on this Mac before a named offload. A refusal
10012
+ Prebuild and pod install still run on this machine before a named offload. A refusal
9217
10013
  starts no local xcodebuild, Gradle or SwiftPM compile.
9218
10014
  Run stim doctor --fix to ask for build access if not paired. A person on the
9219
- worker finds the id with stim-server devices and approves it with
9220
- stim-server devices grant <id> --build, or runs stim-server setup there with
9221
- the client node, ticket and expiry. Check stim settings get remote.machines,
10015
+ worker approves it by running the stim-server setup command doctor printed, with
10016
+ its ticket and expiry. Check stim settings get remote.machines,
9222
10017
  or rerun with --remote-build auto or --remote-build local. Ctrl-C cancels
9223
10018
  the remote build without a local xcodebuild, Gradle or SwiftPM compile.
9224
10019
 
@@ -9229,23 +10024,29 @@ Copyable agent prompt:
9229
10024
  With remote.build auto, \`remote.buildMode\` decides where an iOS simulator Debug build or an Android
9230
10025
  emulator debug build or a stim macos SwiftPM Debug build compiles:
9231
10026
 
9232
- auto (default) here while this Mac has capacity: a free
10027
+ auto (default) here while this machine has capacity: a free
9233
10028
  \`concurrency.maxBuilds\` slot (always, with no build limit) and a
9234
10029
  load per core under \`server.maxLoadPerCore\`. Otherwise on a
9235
- remote Mac that accepts the build and is expected to be faster:
9236
- while every slot here is busy, any remote Mac that accepts; while only
10030
+ remote machine that accepts the build and is expected to be faster:
10031
+ while every slot here is busy, any remote machine that accepts; while only
9237
10032
  the load is high, one whose load per core is lower than this
9238
- Mac's. A Mac too old to report its load counts only while every
10033
+ machine's. A machine too old to report its load counts only while every
9239
10034
  slot here is busy.
9240
- force on a remote Mac whenever one accepts it
10035
+ force on a remote machine whenever one accepts it
9241
10036
  off here when local remains enabled in the automatic build pool
9242
10037
 
9243
- Load per core is the 5-minute load average divided by the CPU count; a Mac's
9244
- native builds are its Stim runs in prebuild, pods or compile on that Mac, not
10038
+ For React Native and Expo, among machines that can take a new build, a matching dependency installation
10039
+ ranks before load. The worker checks the requested project, lockfile and install
10040
+ arguments, and verifies that its modules and install marker remain present.
10041
+ Older workers' dependency hints and mere checkout or build-folder presence do
10042
+ not influence this ranking. Exact completed-artifact hits still come first.
10043
+
10044
+ Load per core is the 5-minute load average divided by the CPU count; a machine's
10045
+ native builds are its Stim runs in prebuild, pods or compile on that machine, not
9245
10046
  the ones it offloaded.
9246
10047
  \`server.maxLoadPerCore\` (default 2) is also the busy threshold for
9247
- automatic iOS and Android device placement. It is the load per core at which a Mac
9248
- counts as saturated, both here and on a remote Mac.
10048
+ automatic iOS and Android device placement. It is the load per core at which a machine
10049
+ counts as saturated, both here and on a remote machine.
9249
10050
 
9250
10051
  STIM_REMOTE_BUILD_MODE overrides it for one command. Device, Release and
9251
10052
  \`--remote eas|proxy\` builds, Android builds with the Apple Clang CAS compiler cache,
@@ -9254,20 +10055,39 @@ Debug builds can use a separate --remote-build, targeting the hosting Mac's
9254
10055
  architecture and runtime.
9255
10056
  macOS uses matching Xcode and macOS SDK, with a per-client SwiftPM dependency
9256
10057
  cache on the worker. It skips JavaScript dependencies, prebuild and pods; see
9257
- stim guide macos. macOS artifacts are not cached.
10058
+ stim guide macos. Completed caching requires macos.artifactInputs and a locally
10059
+ verified compile; remote output is not published under an unverified client key.
9258
10060
  An offloaded iOS/Android build first runs prebuild (and \`pod install\` for iOS) here,
9259
10061
  then asks every paired machine what it can build. It takes one whose Stim
9260
- build matches this Mac and CPU matches the target simulator, with enough disk, that does not decline,
10062
+ build matches this Mac and, for iOS and macOS, CPU matches the target simulator, with enough disk, that does not decline,
9261
10063
  preferring the one that already holds this repository, then the least loaded,
9262
10064
  and moves to the next one in that order when a machine that offered fails the
9263
10065
  sync or refuses to start the build.
10066
+ After a local and configured-provider miss, native Xcode simulator builds can
10067
+ reuse an exact completed archive from an enabled paired worker. Its full key
10068
+ includes source, Xcode, SDK, architecture and target options. A hit skips sync
10069
+ and compilation even while the worker is busy or this machine has room. Missing or
10070
+ corrupt archives follow normal placement. Off mode and an explicit local build
10071
+ do not query peer caches. Older workers continue normal placement. This peer
10072
+ lookup also covers declared native Gradle worker APKs when the worker supports
10073
+ native-gradle-peer-cache. It rechecks current private inputs against the saved
10074
+ receipt at offer and fetch for the requested source, resolved variant, ABI and
10075
+ build options. Caller fingerprints stay null; worker keys stay worker-scoped.
10076
+ React Native and Expo development builds also query enabled paired workers
10077
+ with react-native-peer-cache after local and configured-provider misses. Their
10078
+ post-mutation fingerprint, native build choices and compatible toolchain must
10079
+ match. Completed archives survive worker checkout changes or removal; hits
10080
+ need no source upload or build slot. Disabled remote artifact reads skip this
10081
+ lookup. Release and physical-device builds keep their existing paths.
10082
+ SwiftPM, compiler and dependency caches use their existing paths.
9264
10083
  For iOS the machine's Xcode and simulator SDK must match, and it needs an
9265
10084
  iPhone simulator on the target runtime. Its project-selected CocoaPods must match too, unless
9266
- the app's Gemfile.lock pins CocoaPods: both Macs then run that version through
10085
+ the app's Gemfile.lock pins CocoaPods: both machines then run that version through
9267
10086
  bundler, so the machine needs only Bundler on its stim-server PATH and
9268
10087
  installs the pinned gems itself on the first build. The comparison selects
9269
- 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.
9270
- For Android its JDK major
10088
+ the app's .ruby-version when installed, otherwise rvm's or rbenv's default Ruby, and only when neither exists the GEM_HOME, GEM_PATH and Ruby-related PATH entries of the machine'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.
10089
+ For Android its CPU may differ, since the APK's ABIs come from the NDK, so an
10090
+ x86_64 Linux or Windows machine running stim-server can build for an Apple silicon Mac. Its JDK major
9271
10091
  version must match, and its Android SDK must hold the NDK, build-tools and
9272
10092
  compile platform that the project's React Native version names in
9273
10093
  gradle/libs.versions.toml; Gradle and AGP come from the synced project. When
@@ -9277,7 +10097,7 @@ reason, such as \`janics-mac-mini: busy (load at or above 2/core; load
9277
10097
  \`git ls-files -co --exclude-standard\` lists (the machine keeps only files it
9278
10098
  lacks, so an unignored secret such as \`.env\` is sent too), builds with
9279
10099
  Stim's own code there, refuses unless the fingerprint there equals this one,
9280
- and brings the \`.app\` or APK back. An Android build there uses this Mac's
10100
+ and brings the \`.app\` or APK back. An Android build there uses this machine's
9281
10101
  variant, target ABI, Gradle build cache, PCH and compiler cache choices, so
9282
10102
  the APK matches the ABI-narrowed cache key. Stim checks the archive's sha256
9283
10103
  and fingerprints this checkout again before it stores the app under the
@@ -9295,11 +10115,11 @@ interrupted while it reconnects, leaves it until those 5 minutes pass. With auto
9295
10115
  \`--json\` facts record the reason as offloadFallback. Offloading holds no
9296
10116
  local build slot; that fallback takes one. \`stim stats\` keeps where each
9297
10117
  compiling build ran and why (\`guide facts stats\`).
9298
- An offloaded app lands only in this Mac's build cache, not in a remote cache
10118
+ An offloaded app lands only in this machine's build cache, not in a remote cache
9299
10119
  provider. A project whose xcodebuild changes its own fingerprinted inputs
9300
10120
  fails the offload fingerprint check: auto builds here; a named placement refuses.
9301
10121
 
9302
- On a remote Mac that builds for others, stim-server keeps each client's checkouts, dependencies,
10122
+ On a remote machine that builds for others, stim-server keeps each client's checkouts, dependencies,
9303
10123
  DerivedData, compilation cache, ccache and Gradle home under
9304
10124
  \`server.workerRoot\` (absolute; default $STIM_HOME/build-worker), one area
9305
10125
  and Stim home per client and repository, and one Gradle home per client. It
@@ -9308,21 +10128,23 @@ less than 10 GB free, while its own native builds and the offloaded one fill
9308
10128
  its \`concurrency.maxBuilds\`, or while its load per core is at or above its
9309
10129
  \`server.maxLoadPerCore\`, and reports the reason in its offer. It boots and
9310
10130
  installs nothing. While an offloaded build runs there, it holds one of that
9311
- Mac's \`concurrency.maxBuilds\` slots, so a local Stim build there waits for it
10131
+ machine's \`concurrency.maxBuilds\` slots, so a local Stim build there waits for it
9312
10132
  and counts it as busy; the slot is freed when the build ends, is cancelled or
9313
10133
  its process is gone. Android builds there run on the JDK in stim-server's
9314
10134
  JAVA_HOME (else the macOS default JDK) with the SDK in its ANDROID_HOME (else
9315
- ~/Library/Android/sdk). The client's Gradle daemon stays warm there for
10135
+ ~/Library/Android/sdk, or ~/Android/Sdk on Linux). The client's Gradle daemon stays warm there for
9316
10136
  \`server.gradleDaemonIdleMinutes\` (default 30; 0 stops it when each build
9317
10137
  ends; a new value applies from the next daemon) and never holds a build
9318
10138
  slot. While none of the client's builds runs, stim-server stops it sooner
9319
10139
  when a build is cancelled, the client is revoked, or the machine has less
9320
10140
  than 2 GB of memory available, and deleting the client's directory under the
9321
10141
  worker root stops it too.
9322
- \`stim-server service install [--serve]\` on the remote Mac keeps stim-server
9323
- running as a login LaunchAgent; \`--path-prepend <dir>\` and \`--env KEY=VALUE\`
9324
- pin a PATH entry or variable such as a private CocoaPods that stim-server's
9325
- login-shell environment would otherwise replace.
10142
+ \`stim-server service install [--serve]\` on a remote machine keeps stim-server
10143
+ running as a login LaunchAgent on macOS, a systemd user service on Linux
10144
+ (\`sudo loginctl enable-linger <user>\` keeps it running after logout; install
10145
+ prints that command and never runs it), or a logon scheduled task on Windows; \`--path-prepend <dir>\` and \`--env KEY=VALUE\`
10146
+ pin a PATH entry or variable, such as a private CocoaPods, that stim-server's
10147
+ toolchain discovery does not find.
9326
10148
  \`stim-server service update --release <version>\` there moves the service to
9327
10149
  that exact stim-server release from the public npm registry once npm verifies
9328
10150
  its integrity and registry signatures; \`--from <dir>\` installs the packed
@@ -9330,15 +10152,15 @@ packages of a checkout instead, with the same signature check on each npm
9330
10152
  dependency but none for Stim's own packages. It waits for offloaded builds and hosted
9331
10153
  sessions to finish, restarts the job, and switches back when the new server
9332
10154
  exits or does not answer within 90 seconds; \`stim-server service rollback\` returns
9333
- to the previous server. A client Mac approved for builds or device hosting
10155
+ to the previous server. A client machine approved for builds or device hosting
9334
10156
  there can ask for the same update over its tailnet connection: an npm release
9335
10157
  always, and its own packed build only while \`server.acceptClientBuilds\` is
9336
10158
  true on that machine (default false).
9337
10159
  Replacing a loaded service waits up to 45 seconds for its existing listeners to
9338
10160
  release the port. Uninstall does not wait for the port. If stopping the old job
9339
- or starting the new one fails, install restores the previous plist and attempts
9340
- to restart a previously loaded LaunchAgent; if it cannot, it prints the
9341
- launchctl bootout/bootstrap remedy. A running LaunchAgent does not prove readiness; check \`stim-server service status\`
10161
+ or starting the new one fails, install restores the previous plist or unit and attempts
10162
+ to restart a previously running service; if it cannot, it prints the
10163
+ launchctl bootout/bootstrap or systemctl --user daemon-reload/restart remedy. A running service does not prove readiness; check \`stim-server service status\`
9342
10164
  and its reported log. The server listens before it touches the Stim home; a
9343
10165
  read-only child process reads the Stim home, server and recording directories,
9344
10166
  and until it returns \`/health\` answers 503 with \`startup.state\` pending.
@@ -9370,7 +10192,7 @@ stim-server records the screens of owned simulators, emulators and the
9370
10192
  Stim-owned Chrome page while an automation tool drives them or a paired client
9371
10193
  watches them, so the phone app can scrub back through them. It keeps the last
9372
10194
  15 minutes of footage per device under $STIM_HOME/workspaces/<id>/recordings/,
9373
- on this Mac only, and serves it only to paired clients. \`recording.enabled\`
10195
+ on this machine only, and serves it only to paired clients. \`recording.enabled\`
9374
10196
  turns it off:
9375
10197
 
9376
10198
  stim settings set recording.enabled false --scope machine
@@ -9463,8 +10285,11 @@ The example shows the defaults. Full setting names and behavior:
9463
10285
  optimizations.remoteBuildCache
9464
10286
  false skips both optional remote artifact providers, including loading
9465
10287
  their modules and authentication. Local artifact caching stays enabled.
9466
- Stim ships no network provider or hosted cache. Remote artifact reuse
9467
- requires a provider configured through cache.provider or Expo.
10288
+ Install @stim-cli/cache-s3 and select it through cache.provider for S3-compatible storage,
10289
+ including Cloudflare R2, or configure another provider through Expo.
10290
+ Stim does not run a hosted cache service; credentials use the AWS SDK chain.
10291
+ S3 file uploads retry connection resets up to three PUT attempts, reopening
10292
+ the completed file each time. Cancellation also stops the retry delays.
9468
10293
  optimizations.releaseBundleSwap
9469
10294
  false always builds Release from source, including the current JS;
9470
10295
  it never installs an old embedded bundle. Fresh artifacts can still store.
@@ -9534,7 +10359,9 @@ PCH mode matches; disable artifact caching too to force native compilation. Lega
9534
10359
  cannot key these compiler profiles, so Stim skips that tier for custom profiles
9535
10360
  and Android CAS. Providers implementing the Stim key contract remain usable.
9536
10361
  Android compiler/PCH profiles also get separate CMake staging directories under
9537
- <module>/.cxx/stim-<profile>, or under the project's custom staging root. Switching
10362
+ <module>/.cxx/stim-<profile> (<module>/.cxx/<first 4 profile characters> on
10363
+ Windows, where every character counts against MAX_PATH), or under the project's
10364
+ custom staging root. Switching
9538
10365
  backends in Stim selects the matching directory without deleting previous builds.
9539
10366
  These directories accumulate across profile changes and shim upgrades. Stim
9540
10367
  does not prune them; remove an obsolete generated profile only with all native
@@ -9926,24 +10753,65 @@ changing focus: it sets STIM_BACKGROUND_LAUNCH=1 in the app's environment, which
9926
10753
  Stim Desktop honors, including for reopen events from open -g. An app that
9927
10754
  activates itself at launch or on reopen still takes focus.
9928
10755
  Hosted launches (macos --remote) do not set it.
9929
- macOS artifacts are not cached. This prototype has no --slot or reload command.
10756
+ This prototype has no --slot or reload command.
10757
+ Completed app caching is opt-in with macos.artifactInputs:
10758
+
10759
+ { "macos": { "artifactInputs": { "complete": true,
10760
+ "ignored": [], "outputs": [], "localFiles": [], "environment": [] } } }
10761
+
10762
+ complete declares pinned, repeatable build inputs and an app that runs after its
10763
+ original checkout and scratch directory are gone. Do not enable it for mutable
10764
+ dependencies, build-time network data, undeclared environment or local inputs,
10765
+ or code that needs its original absolute source paths (including #filePath).
10766
+ Stim hashes Git-visible repository source, Package.resolved even when ignored,
10767
+ the development plist, declared resources, Swift/Xcode/SDK identity and CPU
10768
+ architecture. ignored names exact Git-ignored repository-relative input files;
10769
+ outputs names generated repository-relative directories already ignored by Git.
10770
+ localFiles names exact additional private files, relative to the package or
10771
+ absolute; environment names additional build-time variables. Values and private
10772
+ file contents are hashed locally and are not included in the shared receipt.
10773
+ Symbolic links in source inventories are currently ineligible for caching.
10774
+ Without a valid declaration Stim compiles normally and keeps SwiftPM incremental
10775
+ reuse. optimizations.buildCache and optimizations.remoteBuildCache also apply.
10776
+ The completed bundle uses the ordinary shared build cache and an optional
10777
+ cache.provider's files capability, including S3. A hit avoids compiler slots and
10778
+ placement, then gets this workspace's identifier and name on a re-signed copy.
10779
+ Corrupt or incompatible entries fall back to compilation. Inputs changed during
10780
+ compilation prevent publication. Compiler and package scratch state is not shared.
10781
+ An enabled paired worker with swiftpm-peer-cache can retain a completed archive
10782
+ after verifying its actual source, private inputs and toolchain before and after
10783
+ compilation. After local/provider misses, an exact peer hit skips source upload
10784
+ and compiler admission, even while that worker is busy. Private values stay on
10785
+ the worker; its opaque identity is not used as this Mac's local cache key.
10786
+ Ignored bundle files must be declared in ignored or localFiles for peer reuse.
10787
+ The worker rechecks private inputs before offer and fetch; deleting its checkout
10788
+ does not discard an archive whose private inputs remain valid. Toolchain versions and command selection are
10789
+ checked from the package directory. After checkout removal, only verified direct
10790
+ Apple Swift, xcrun and xcodebuild selections can be reproduced; custom launchers
10791
+ require the checkout and otherwise miss safely. SWIFT_EXEC and SWIFT_EXEC_MANIFEST
10792
+ compiler overrides are resolved and versioned separately and require the checkout.
10793
+ The SDK selected by SDKROOT or SWIFTPM_SDKROOT_macosx is checked by real path and build version. Explicit local placement,
10794
+ remote build mode off, disabled pool members and remoteBuildCache
10795
+ false skip peer lookup. Older workers keep their normal compilation path.
9930
10796
  --plan validates the Swift Package directory, macos settings, development plist
9931
- and declared resources without building, signing, staging, stopping or launching
10797
+ and declared resources without building, signing, stopping or launching
9932
10798
  an app, writing workspace state or claiming a build slot. It does not execute
9933
10799
  Package.swift, so it cannot validate the executable product or package dependencies.
9934
10800
  --remote-build is respected, including named-machine setup refusals; no worker
9935
10801
  is contacted and live worker availability is unknown. --remote is launch-only
9936
10802
  and refuses with --plan. The JSON plan has platform "macos", product and
9937
- buildMachine; fingerprint, cacheKey, provider, prebuild, outcome and expectedMs
9938
- are null, cacheHit and cacheSkipped are false, and basis is 0. SwiftPM decides
9939
- incremental compile work when a build runs; the plan predicts no cache outcome
9940
- or duration. Desktop checks automatically while build details are visible,
10803
+ buildMachine. An eligible cache identity supplies fingerprint and cacheKey;
10804
+ cacheHit reports a verified local completed artifact, checked in a disposable
10805
+ temporary copy. Providers and workers are not contacted. cacheSkipped is true
10806
+ when artifact caching is disabled or ineligible. provider, prebuild, outcome
10807
+ and expectedMs remain null, and basis is 0. SwiftPM decides incremental compile
10808
+ work when a build runs; the plan predicts no duration. Desktop checks automatically while build details are visible,
9941
10809
  reusing a completed build or check for 60 seconds and skipping running builds.
9942
10810
  A failed build records its error and compiler output without launching an app.
9943
10811
  While it runs, stim status --json reports it as environments[].build with
9944
10812
  platform "macos": phase prepare, compile, install, then launch, and during
9945
10813
  compile detail.unit "steps" with SwiftPM's [done / total] counts (fetching and
9946
- planning are detail.step "configure"). It has no cache lookup, so outcome and
10814
+ planning are detail.step "configure"). outcome and
9947
10815
  plannedPhases are null; finished runs and their phase times are in
9948
10816
  environments[].builds.macos, where phases includes launch once the launch step finishes
9949
10817
  and compileSteps is SwiftPM's step total. An offloaded build reports the worker's
@@ -10144,11 +11012,11 @@ used.
10144
11012
  ON ANOTHER MAC
10145
11013
 
10146
11014
  stim macos --remote <machine> builds the Debug app on this Mac and runs it on an
10147
- approved remote Mac over the tailnet, without SSH. The machine must be listed
10148
- in remote.machines and approved: stim doctor --fix asks it, a person on that
10149
- Mac runs stim-server devices grant <id> --device-host or stim-server setup
10150
- with that client node, ticket and expiry (guide settings), and stim doctor then
10151
- records the approval. Stim connects only to the machine's pinned tailnet node.
11015
+ approved remote Mac over the tailnet, without SSH. The Mac must be listed
11016
+ in remote.machines and approved: stim doctor --fix makes a setup ticket, a
11017
+ person on that Mac runs stim-server setup with that ticket and expiry (guide
11018
+ settings), and stim doctor then pins that Mac's stim-server key. Stim goes ahead
11019
+ only with the stim-server that proves it holds that key.
10152
11020
  A refusal or an unreachable host fails the command; it never launches here
10153
11021
  instead.
10154
11022
 
@@ -10207,7 +11075,7 @@ approved host that lacks them. A server started by Stim Desktop uses Desktop's
10207
11075
  grants.
10208
11076
 
10209
11077
  Hosted delivery carries the staged bundle with declared resources and compiled assets.
10210
- When offload built it on the host's own tailnet node, the host copies the files
11078
+ When offload built it on the host's own stim-server, the host copies the files
10211
11079
  from the build it kept for this Mac instead of receiving them again, admitting
10212
11080
  only bytes that match the bundle's digests here. That needs both the build and
10213
11081
  the device-host approval for this Mac. Files the host cannot take, an older
@@ -10313,7 +11181,7 @@ outside the user's own projects.`;
10313
11181
  const local = `The tutorial runs on this Mac. Every time you run the tutorial app on iOS
10314
11182
  from a worktree of {base}, use stim ios --remote local --remote-build local,
10315
11183
  even when the user's settings would place the device or the build on another
10316
- Mac. Only an explicit request to build on another Mac changes the build, with
11184
+ machine. Only an explicit request to build on another machine changes the build, with
10317
11185
  stim ios --remote local --remote-build "<machine>"; the device stays here.`;
10318
11186
  const screenshots = `Before and after screenshots, for a pull request: in each change's worktree,
10319
11187
  run the app on iOS before you edit anything, open it with agent-device and save
@@ -10636,7 +11504,7 @@ function renderIndex(version) {
10636
11504
  return lines.join("\n");
10637
11505
  }
10638
11506
  function guideCommand(program, version, status = (running) => guideStatus({
10639
- projectRoot: findProjectRoot(process.cwd()),
11507
+ projectRoot: findProjectRoot$1(process.cwd()),
10640
11508
  running
10641
11509
  })) {
10642
11510
  program.command("guide [topic] [section]").description("Print reference documentation for THIS version of Stim (topics: " + topicNames().join(", ") + "). A topic with sections prints its section index; name a section to print only it, e.g. `stim guide errors STIM_NO_METRO` or `stim guide lifecycle builds`. Generated by the binary, so it cannot drift from the installed CLI.").action(async (topic, section) => {