stim 1.26.0 → 1.27.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 (171) hide show
  1. package/dist/{acquire-artifact-DVsvSwf7.mjs → acquire-artifact-BKN2Yuhg.mjs} +2 -2
  2. package/dist/{act-C-BqJEpu.mjs → act-ROviTF_t.mjs} +297 -122
  3. package/dist/{activity-Lnb_EoRY.mjs → activity-Cd-ysFes.mjs} +6 -6
  4. package/dist/{agent-device-usage-output-DbXy5tg8.mjs → agent-device-usage-output-C5nCKWp9.mjs} +5 -5
  5. package/dist/{android-CSbLRD53.mjs → android--HBgpvpW.mjs} +5 -5
  6. package/dist/{android-CiNQw3kq.mjs → android-Cd6w7_qw.mjs} +96 -43
  7. package/dist/{android-B5P81yI-.mjs → android-Deb9X3_Y.mjs} +37 -15
  8. package/dist/{android-DTAcRwBC.mjs → android-DriNoAVy.mjs} +23 -18
  9. package/dist/{android-cas-BTVhZ0Qs.mjs → android-cas-D5e6ecUg.mjs} +4 -4
  10. package/dist/android-cas-compiler.mjs +1 -1
  11. package/dist/api.d.mts +13 -3
  12. package/dist/api.mjs +39 -29
  13. package/dist/{apk-signed-tail-B46U9hXc.mjs → apk-signed-tail-BHQHx3Rm.mjs} +2 -2
  14. package/dist/{artifact-BxREhAaV.mjs → artifact-0AGJYB9O.mjs} +18 -23
  15. package/dist/{artifact-CsAP6AGK.mjs → artifact-faemIZv5.mjs} +19 -24
  16. package/dist/{artifact-platform-CMRc0M_r.mjs → artifact-platform-BO2Uvk_A.mjs} +1 -1
  17. package/dist/auto-placement-z-H5pJza.mjs +350 -0
  18. package/dist/{browser-web-rYPyqhTf.mjs → browser-web-slD8AvLp.mjs} +16 -16
  19. package/dist/{build-cache-DrXNCy3T.mjs → build-cache-gBJ-WKzA.mjs} +43 -21
  20. package/dist/{build-DPhApsjV.mjs → build-hS0QyFlQ.mjs} +35 -31
  21. package/dist/{build-plan-DrMMdayj.mjs → build-plan-CBqYbzYM.mjs} +7 -6
  22. package/dist/{cache-manifest-BEZ9Bpl1.mjs → cache-manifest-DEjqGGat.mjs} +1 -1
  23. package/dist/cache-manifest.mjs +1 -1
  24. package/dist/{budget-k2mZGvvk.mjs → ccache-BWTze1Ls.mjs} +560 -1603
  25. package/dist/{cdp-BG3FJCCx.mjs → cdp-C3svvXiE.mjs} +1 -1
  26. package/dist/{chrome-BvV8ssI3.mjs → chrome-DyDWurWG.mjs} +1 -1
  27. package/dist/{cli-BBKFGOFl.mjs → cli-ftXDLto1.mjs} +24 -23
  28. package/dist/cli.mjs +1 -1
  29. package/dist/{client-D9CaRks-.mjs → client-xsdxrhWA.mjs} +48 -62
  30. package/dist/collector-run.d.mts +10 -9
  31. package/dist/collector-run.mjs +42 -27
  32. package/dist/{command-output-BF5AR2dL.mjs → command-output-Dj-WT4BP.mjs} +1 -1
  33. package/dist/{config-BktkY_zC.mjs → config-Cn94Jc0g.mjs} +23 -4
  34. package/dist/{dependencies-Dw6wfQdv.mjs → dependencies-BGokZ-Ng.mjs} +108 -60
  35. package/dist/{dependency-state-DFB41vfr.mjs → dependency-state-C3OtgG94.mjs} +14 -7
  36. package/dist/{detached-entry-KmieOKgN.mjs → detached-entry-BPlSaCwC.mjs} +1 -1
  37. package/dist/{dev-client-DXR_tPVY.mjs → dev-client-BMca82xo.mjs} +2 -2
  38. package/dist/{device-BcYeHSFu.mjs → device-BJm0irrq.mjs} +16 -16
  39. package/dist/{device-capacity-DCSvUF0l.mjs → device-capacity-6LP7QsaA.mjs} +101 -16
  40. package/dist/device-host-worker.mjs +19 -19
  41. package/dist/{device-ios-BLGfXQeK.mjs → device-ios-534c14fz.mjs} +251 -23
  42. package/dist/{device-lease-run-BmvIl7bI.mjs → device-lease-run-DwsGfjUN.mjs} +5 -5
  43. package/dist/{device-pool-Dluix4H3.mjs → device-pool-BnQqvs6o.mjs} +3 -3
  44. package/dist/{device-BK-zwyd_.mjs → device-tM41-TUq.mjs} +25 -24
  45. package/dist/{doctor-Cp-BhzTS.mjs → doctor-Dbx1ZpD2.mjs} +1 -1
  46. package/dist/{doctor-Bhg1ii70.mjs → doctor-DyqE9n1J.mjs} +28 -16
  47. package/dist/{doctor-OnwP94ne.mjs → doctor-F-3VUUyN.mjs} +76 -32
  48. package/dist/{doctor-fingerprint-D3a04Mhu.mjs → doctor-fingerprint-o7ZI9YLn.mjs} +1 -1
  49. package/dist/doctor-fingerprint.mjs +1 -1
  50. package/dist/{doctor-watchman-BStK05nt.mjs → doctor-watchman-CxDbyrrt.mjs} +5 -5
  51. package/dist/{eas-build-Dsj93iM9.mjs → eas-build-CjNSWmjo.mjs} +4 -4
  52. package/dist/eas-fallback-9fqb46kW.mjs +87 -0
  53. package/dist/{eas-session-ledger-B5vygTiD.mjs → eas-session-ledger-DrP4OuuM.mjs} +6 -6
  54. package/dist/{error-diagnostics-BOOm9Db7.mjs → error-diagnostics-snej9k4j.mjs} +51 -25
  55. package/dist/{exec-VFRpu1iA.mjs → exec-aULwtNly.mjs} +57 -6
  56. package/dist/{gc-C_79zvz4.mjs → gc-BRwAY3qT.mjs} +2 -2
  57. package/dist/{gc-ldgr-hXU.mjs → gc-CGZ1Yg88.mjs} +160 -45
  58. package/dist/{guide-D7K0X6KG.mjs → guide-_rfEh_zM.mjs} +580 -183
  59. package/dist/{hosted-android-Bk54ej6n.mjs → hosted-android-BMKzUNnY.mjs} +4 -4
  60. package/dist/{hosted-client-fHO8yqzc.mjs → hosted-client-D7Pk_Oo1.mjs} +12 -8
  61. package/dist/{hosted-logs-BfbmDM4M.mjs → hosted-logs-B1gqQuva.mjs} +8 -8
  62. package/dist/{hosted-logs-sync-B0pvmOk-.mjs → hosted-logs-sync-Dg3qrZmV.mjs} +5 -5
  63. package/dist/{hosted-macos-CPl0OZ9h.mjs → hosted-macos-B9w4H8hc.mjs} +6 -6
  64. package/dist/{hosted-native-CpDB3-PO.mjs → hosted-native-CdgaiuyP.mjs} +17 -16
  65. package/dist/{idle-shutdown-FfxL1KdG.mjs → idle-shutdown-Cbvo5KEh.mjs} +7 -8
  66. package/dist/{ios-ftxdQbJS.mjs → ios-B5HlVMhQ.mjs} +92 -37
  67. package/dist/{ios-B7-lToDu.mjs → ios-Bk4mWbtS.mjs} +8 -8
  68. package/dist/{ios-ENC4sRPA.mjs → ios-CsRVik64.mjs} +7 -1
  69. package/dist/{ios-WxpDJ5T1.mjs → ios-E26qSpkb.mjs} +27 -11
  70. package/dist/{ios-device-CsyryvpT.mjs → ios-device-C_QkIf1Z.mjs} +3 -3
  71. package/dist/{ios-device-B6HK951C.mjs → ios-device-CiBA-er9.mjs} +1 -1
  72. package/dist/{ios-project-Ty5pHl9O.mjs → ios-project-D8Jcy_br.mjs} +22 -9
  73. package/dist/{ios-signing-4c_qXvQm.mjs → ios-signing-BJzvDPfU.mjs} +2 -2
  74. package/dist/{ios-state-Cw7-WQf2.mjs → ios-state-BZ03NFgF.mjs} +1 -1
  75. package/dist/{js-swap-BMhXYVDS.mjs → js-swap-Dg2a945i.mjs} +8 -7
  76. package/dist/{launch-DOp8RjDz.mjs → launch-CRyBeNsA.mjs} +42 -38
  77. package/dist/lightened-BCsdAGj_.mjs +171 -0
  78. package/dist/{logs-dQDki6Tt.mjs → logs-CC_-ZzEE.mjs} +7 -7
  79. package/dist/{logs-s1dRIG1E.mjs → logs-aJQb-RDg.mjs} +46 -21
  80. package/dist/{macos-B0VEV3Gf.mjs → macos-BGmTpnv9.mjs} +1 -1
  81. package/dist/{macos-DBBPNnIA.mjs → macos-DlCBhMw5.mjs} +73 -38
  82. package/dist/macos-run.mjs +1 -1
  83. package/dist/maintenance-run.mjs +7 -7
  84. package/dist/{metro-CjC10t6E.mjs → metro-CvyD-xwe.mjs} +4 -4
  85. package/dist/{metro-gateway-B6_PgD0L.mjs → metro-gateway-BnHdqMO_.mjs} +3 -3
  86. package/dist/{named-ports-BG0j1X4g.mjs → named-ports-BCyMWA6b.mjs} +3 -3
  87. package/dist/{native-android-CL2NGiik.mjs → native-android-DlpxCyOP.mjs} +17 -18
  88. package/dist/{native-gradle-artifact-inputs-btSUulme.mjs → native-gradle-artifact-inputs-C13qT7R_.mjs} +5 -5
  89. package/dist/{native-runtime-Dq3RBcON.mjs → native-runtime-DqaIBO5w.mjs} +9 -8
  90. package/dist/{native-source-D-4Dh4JU.mjs → native-source-D1AzWiuP.mjs} +1 -1
  91. package/dist/{native-xcode-inputs-BW7H8JGu.mjs → native-xcode-inputs-B1W8SrxB.mjs} +3 -3
  92. package/dist/{native-xcode-ios-CBXJAxfC.mjs → native-xcode-ios-QPP0Rm68.mjs} +17 -17
  93. package/dist/{ndjson-CgE_o3Dl.mjs → ndjson-Dy2W4vRa.mjs} +2 -2
  94. package/dist/offload-worker.mjs +23 -24
  95. package/dist/{operation-exec-CUZifp6Z.mjs → operation-exec-Dp4FuIGR.mjs} +1 -1
  96. package/dist/{ownership-BjYvMNX_.mjs → ownership-Bx-0cku-.mjs} +3 -3
  97. package/dist/{page-CzEA_-5_.mjs → page-CQJlcj3x.mjs} +2 -2
  98. package/dist/{phone-reverses-lmj6kcPm.mjs → phone-reverses-C9yAaKEv.mjs} +18 -4
  99. package/dist/{placement-log-DIFPQAXp.mjs → placement-log-BXUbiZM6.mjs} +3 -3
  100. package/dist/{plan-placement-IiiSQhtz.mjs → plan-placement-UxnQjv5Z.mjs} +36 -10
  101. package/dist/{ports-C2_w1Z0Z.mjs → ports-uUrghqWN.mjs} +3 -3
  102. package/dist/{prebuild-gmouvmJZ.mjs → prebuild-BDpToDgR.mjs} +16 -17
  103. package/dist/{project-BUu6Is4k.mjs → project-DYj-HVx6.mjs} +17 -17
  104. package/dist/{pull-request-CbNlkFok.mjs → pull-request-BzLmzAIQ.mjs} +2 -2
  105. package/dist/pull-requests.mjs +1 -1
  106. package/dist/{react-native-android-Cgg99REN.mjs → react-native-android-DDBccgJw.mjs} +32 -26
  107. package/dist/{react-native-build-oNwqF5Wb.mjs → react-native-build-DaK2IFyP.mjs} +9 -9
  108. package/dist/{react-native-doctor-BhA3KUte.mjs → react-native-doctor-B-Mw6DcR.mjs} +6 -6
  109. package/dist/{react-native-ios-B-nf4rjN.mjs → react-native-ios-DjYwUF9U.mjs} +23 -17
  110. package/dist/{recordings-BldzVKq0.mjs → recordings-C20iUZfp.mjs} +1 -1
  111. package/dist/{registration-CN6nlhcD.mjs → registration-BwCe64PH.mjs} +1 -1
  112. package/dist/{reload-C9OduiNc.mjs → reload-DJBeF_rC.mjs} +1 -1
  113. package/dist/{reload-CCR7KC_l.mjs → reload-fUdkH2_p.mjs} +16 -16
  114. package/dist/{remote-C8S7FlPF.mjs → remote-y6n99WMW.mjs} +22 -9
  115. package/dist/{result-7F5hEwQJ.mjs → result-CTqBw2Te.mjs} +32 -21
  116. package/dist/{run-BqB0v3pt.mjs → run-DaCeNKRm.mjs} +3 -3
  117. package/dist/{selection-BNPBZ6T5.mjs → selection-Bs8phwhN.mjs} +37 -29
  118. package/dist/{server-bare-A75rDIdD.mjs → server-bare-BOh-iZEj.mjs} +2 -2
  119. package/dist/{server-command-CssKFOD7.mjs → server-command-8CuA51Cv.mjs} +6 -6
  120. package/dist/{server-expo-eQfOdydb.mjs → server-expo-ABQs2XFA.mjs} +4 -4
  121. package/dist/server-expo-Ba_Y21vB.mjs +2 -0
  122. package/dist/{settings-CosW5X-S.mjs → settings-BDVI8Ivc.mjs} +331 -62
  123. package/dist/{settings-OaGQF-ri.mjs → settings-CMHhQNwI.mjs} +24 -3
  124. package/dist/settings-payload-cm60Zhib.mjs +257 -0
  125. package/dist/settings-write-BdNRLlBn.mjs +185 -0
  126. package/dist/settings.schema.json +51 -33
  127. package/dist/{simslim-Bax1qcxu.mjs → simslim-DCQwzoPy.mjs} +2 -2
  128. package/dist/{slot-launch-CYCEhfBY.mjs → slot-launch-0l7RqSpr.mjs} +6 -6
  129. package/dist/{stage-jKQ7CP_B.mjs → stage-F4cAfJGi.mjs} +2 -2
  130. package/dist/{start-C_wefNKB.mjs → start-DcqnF9p8.mjs} +1 -1
  131. package/dist/{start-_BdJ32cZ.mjs → start-DrPwER_2.mjs} +27 -22
  132. package/dist/{state-C1i2nMOP.mjs → state-BX1y5syV.mjs} +4 -4
  133. package/dist/{state-C4OdpOTF.mjs → state-CZxLyetD.mjs} +2 -2
  134. package/dist/{stats-tEpEEH-u.mjs → stats-GxuCbles.mjs} +5 -5
  135. package/dist/{stats-report-n2U5plhk.mjs → stats-report-B86Cv7Uc.mjs} +1 -1
  136. package/dist/{status-CRXz-Gtj.mjs → status-7ULDEJ1e.mjs} +1 -1
  137. package/dist/{status-DQrqodLj.mjs → status-BfnK9pUd.mjs} +25 -25
  138. package/dist/{status-B9M7x4CZ.mjs → status-Bj7ZYvvc.mjs} +1 -1
  139. package/dist/{status-watch-B5wDIH_o.mjs → status-watch-BfabQn03.mjs} +5 -5
  140. package/dist/status-worker.mjs +1 -1
  141. package/dist/{stim-desktop-6Dqp-gjX.mjs → stim-desktop-CbWo21kb.mjs} +2 -2
  142. package/dist/{stop-ySNQHXMG.mjs → stop-CH79S3IZ.mjs} +5 -5
  143. package/dist/{stop-DRqQVqDJ.mjs → stop-D-EAr25B.mjs} +21 -20
  144. package/dist/{stop-CmdNhGlC.mjs → stop-x1Yu3He4.mjs} +2 -2
  145. package/dist/supervisor-run.mjs +17 -17
  146. package/dist/{support-CVOCp0Ft.mjs → support-Be0UeVZU.mjs} +10 -10
  147. package/dist/{support-BGSCBoib.mjs → support-CNg_YsOk.mjs} +6 -6
  148. package/dist/{swiftpm-artifact-inputs-CfUzt_0Z.mjs → swiftpm-artifact-inputs-uPqYvGBv.mjs} +4 -4
  149. package/dist/{swiftpm-macos-x6HPpl_2.mjs → swiftpm-macos-Ch4YjY_A.mjs} +11 -12
  150. package/dist/{toolchain-J4J8MrAr.mjs → toolchain-CbCG6C5Z.mjs} +321 -15
  151. package/dist/{toolchain-DoavrvIA.mjs → toolchain-JYAOquPg.mjs} +1 -1
  152. package/dist/{trigger-KVaWWNyn.mjs → trigger-DMou-X7j.mjs} +2 -2
  153. package/dist/trigger-qp2ji5wE.mjs +2 -0
  154. package/dist/{warm-progress-DvhTX_eu.mjs → warm-progress-C0RsbngN.mjs} +731 -727
  155. package/dist/{web-Bihbf7-u.mjs → web-Cq_AQ4DK.mjs} +9 -9
  156. package/dist/web-Je6abcpz.mjs +2 -0
  157. package/dist/web-run.mjs +9 -9
  158. package/dist/windows-report.mjs +1 -1
  159. package/dist/{workspace-state-pUg3_RRA.mjs → workspace-state-BjqdjnJR.mjs} +3 -3
  160. package/dist/{workspaces-g2lhdBXF.mjs → workspaces-DFbj07jp.mjs} +1795 -70
  161. package/dist/{worktree-BKuklFh_.mjs → worktree-COAJ3-OD.mjs} +934 -514
  162. package/dist/{worktree-Bm4cb-nU.mjs → worktree-JGUd_53L.mjs} +1 -1
  163. package/package.json +7 -7
  164. package/dist/ccache-Bx5TbxwG.mjs +0 -143
  165. package/dist/eas-fallback-FwkeqqGi.mjs +0 -381
  166. package/dist/hosted-ios-D55QhYaD.mjs +0 -15
  167. package/dist/native-run-BfIDFKLH.mjs +0 -275
  168. package/dist/server-expo-wvcj5tiZ.mjs +0 -2
  169. package/dist/settings-write-QjYfSrjF.mjs +0 -360
  170. package/dist/trigger-DzJu208Q.mjs +0 -2
  171. package/dist/web-BFLueSh-.mjs +0 -2
@@ -1,8 +1,8 @@
1
- import { t as ANDROID_AVD_CONFIG_HELP } from "./settings-CosW5X-S.mjs";
2
- import { i as findProjectRoot$1 } from "./project-BUu6Is4k.mjs";
3
- import { h as RECENT_LAUNCH_MS } from "./eas-session-ledger-B5vygTiD.mjs";
4
- import { o as guideStatus, t as WATCHMAN_NESTED_WORKTREES } from "./doctor-watchman-BStK05nt.mjs";
5
- import "./status-watch-B5wDIH_o.mjs";
1
+ import { t as ANDROID_AVD_CONFIG_HELP } from "./settings-BDVI8Ivc.mjs";
2
+ import { i as findProjectRoot$1 } from "./project-DYj-HVx6.mjs";
3
+ import { h as RECENT_LAUNCH_MS } from "./eas-session-ledger-DrP4OuuM.mjs";
4
+ import { o as guideStatus, t as WATCHMAN_NESTED_WORKTREES } from "./doctor-watchman-CxDbyrrt.mjs";
5
+ import "./status-watch-BfabQn03.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
@@ -297,16 +297,20 @@ reserved named ports, even outside the workspace. Read guide ports before use.
297
297
 
298
298
  Ask the user before these actions:
299
299
 
300
- - worktree remove, because it deletes the worktree and gives up its owned
301
- device. It works with any linked worktree, warmed or not, without requiring
300
+ - worktree remove of a worktree you did not create, because it deletes the
301
+ worktree and gives up its owned device. It works with any linked worktree, warmed or not, without requiring
302
302
  a Stim registry entry. Git-created branches are kept; a branch with an
303
303
  existing Stim ownership record is deleted only when it has no unique commits.
304
304
  - worktree remove --force, because it also discards uncommitted and untracked
305
305
  files.
306
306
  - gc --delete, because it deletes orphaned resources, removes clean linked
307
307
  worktrees whose branch or pull request is merged (or whose pull request
308
- was closed), and clears the build outputs of every
309
- workspace not in use. Run stim gc --json first and show
308
+ was closed), and lightens every workspace that is not building and ran
309
+ no ios, android or macos command in the last 15 minutes, a running dev
310
+ server included, by clearing its build outputs and its git-ignored
311
+ in-tree build directories, and deletes node_modules and ios/Pods of idle
312
+ linked worktrees, which the next start, ios or android restores with
313
+ worktree warm. Run stim gc --json first and show
310
314
  the user the entries its sections list (guide facts gc). gc --delete
311
315
  --cache all empties the shared build caches and those outputs instead, and
312
316
  gc --delete --cache workspaces clears only the outputs; both inspect
@@ -327,8 +331,10 @@ Ask the user before these actions:
327
331
 
328
332
  LEAVE THE MACHINE CLEAN
329
333
 
330
- Run stim stop when you pause or finish a task, and stim worktree remove once
331
- it is merged or abandoned. Never create a simulator or emulator by hand;
334
+ Run stim stop when you pause or finish a task, and stim worktree remove on
335
+ the worktree you created once its work is merged or abandoned. Keep scratch
336
+ files, test output and downloads outside the worktree: a stray untracked file
337
+ blocks that removal, and automatic cleanup then has to archive it. Never create a simulator or emulator by hand;
332
338
  every device comes from Stim. Before starting a new workspace, check stim
333
339
  status for an idle workspace to reuse and confirm capacity headroom instead of
334
340
  adding another. Never quit Device Hub or Simulator.app: on Xcode 27
@@ -1335,7 +1341,8 @@ ON FAILURE
1335
1341
  An \`ios\` or \`android\` run that \`stim stop\` or Ctrl-C interrupted
1336
1342
  reports STIM_CANCELLED and exits 130. A
1337
1343
  run that exits on the interrupt at once (a second Ctrl-C, or Ctrl-C with no
1338
- build tool or SimSlim to stop) exits 130 with no payload.
1344
+ build tool, SimSlim, launch readiness wait or hosted app delivery to stop)
1345
+ exits 130 with no payload.
1339
1346
 
1340
1347
  \`stop --json\` prints { root, ok, supervisor, collectors, metro, device,
1341
1348
  port, metroTunnel, releasedLeases, macos? }. macos is { status: stopped | failed,
@@ -1532,7 +1539,8 @@ RULES
1532
1539
  why) or "failed" (detail says why and what to retry).
1533
1540
  kind: device, parkedDevice, parkedHostedDevice, idleDevice,
1534
1541
  deviceRecord,
1535
- workspaceOutputs, recording, workspaceDirectory, project,
1542
+ workspaceOutputs, worktreeDependencies, recording,
1543
+ workspaceDirectory, project,
1536
1544
  buildLock, buildSlot, deviceLease, easSession, worktree,
1537
1545
  cache, gradleVersion, tempLeftover (one "done" labelled
1538
1546
  with the TMPDIR it cleared, plus a "kept" or "failed" per
@@ -1600,9 +1608,9 @@ RULES
1600
1608
  else null. buildState is { bytes, dirs:
1601
1609
  { path, bytes } } for the in-tree build and
1602
1610
  dependency directories automatic maintenance
1603
- clears (ios/build, android/app/build,
1604
- android/build, android/.gradle, .build,
1605
- node_modules, ios/Pods, at the root, a
1611
+ clears (android/app/build, android/build,
1612
+ android/.gradle, .build, node_modules,
1613
+ ios/Pods, at the root, a
1606
1614
  project root, or apps/* and packages/* for
1607
1615
  build directories) and a root .pnpm-store,
1608
1616
  paths relative to the worktree, largest
@@ -1701,18 +1709,29 @@ RULES
1701
1709
  Metro, client and device logs over twice the
1702
1710
  8 MiB cap; willTrim marks the ones it would
1703
1711
  trim
1712
+ worktreeDependencies { path, willDelete, detail } node_modules and
1713
+ ios/Pods of linked worktrees; willDelete marks
1714
+ the ones --delete would lighten, and detail
1715
+ says why the others stay. Sizes are not
1716
+ measured: APFS clones make them misleading
1704
1717
  recordings { dir, projectRoot, bytes, deleteBytes,
1705
1718
  willDelete, withWorkspace, reason, detail }
1706
1719
  recordings/ of each workspace; deleteBytes is
1707
1720
  what --delete would remove; withWorkspace
1708
1721
  marks those of a gone workspace, removed with
1709
1722
  its workspace directory
1710
- workspaceBuildOutputs { dir, projectRoot, bytes, idleDays, willClear,
1711
- reason, detail } derived-data, gradle-build,
1712
- android-cas, cache-provider, macos/build,
1713
- macos/<Product>.app and macos/staging-* of each
1714
- workspace; willClear marks the ones --delete
1715
- would clear; --cache workspaces omits the
1723
+ workspaceBuildOutputs { dir, projectRoot, inTreeDirs, bytes, idleDays,
1724
+ willClear, reason, detail } derived-data,
1725
+ gradle-build, android-cas, cache-provider,
1726
+ macos/build, macos/<Product>.app and
1727
+ macos/staging-* of each workspace; inTreeDirs
1728
+ lists the project's android/app/build,
1729
+ android/build,
1730
+ android/.gradle and .build that --delete
1731
+ clears with them, every file in them
1732
+ ignored by git and none tracked, and
1733
+ bytes includes them; willClear marks the ones
1734
+ --delete would lighten; --cache workspaces omits the
1716
1735
  directories that --delete removes whole (dead
1717
1736
  projects and orphanedWorkspaces), and --cache
1718
1737
  all does not
@@ -1771,11 +1790,13 @@ RULES
1771
1790
  bytes, willDelete, kept }] } | null ignored
1772
1791
  directories worktree remove moved out of a
1773
1792
  worktree into $STIM_HOME/trash before git
1774
- removed it. bytes is null while a removal is
1793
+ removed it, and paths worktree warm moved out
1794
+ of a copy. bytes is null while a removal is
1775
1795
  deleting the entry. kept names the live or
1776
1796
  unresolved ownership claim, the missing or
1777
1797
  invalid trash.json, or the worktree that
1778
- still exists, that keeps a directory;
1798
+ still exists (not for a warm entry), that
1799
+ keeps a directory;
1779
1800
  --delete deletes the rest, only remove-*
1780
1801
  entries with a valid trash.json. null when the
1781
1802
  trash is empty or under any --cache scope
@@ -2273,6 +2294,10 @@ RULES
2273
2294
  build: ios compile on janics-mac-mini (build, 2m10s), 3m05s elapsed
2274
2295
  build: ios launch (bundling JS 45%, 12s), 58s elapsed
2275
2296
 
2297
+ The machine keeps every native build run, not only the latest, in
2298
+ $STIM_HOME/builds/builds.ndjson (newest 500), read with
2299
+ \`stim logs --source builds --json\` (\`guide logs\`, BUILD LEDGER).
2300
+
2276
2301
  An environment with a recorded run also carries lastBuilds, each
2277
2302
  platform's most recent ios or android run, finished or failed:
2278
2303
 
@@ -2550,9 +2575,10 @@ RULES
2550
2575
  plan does not, so changes compare the fingerprint before that
2551
2576
  prebuild; changeCount 0 then means those inputs match the
2552
2577
  baseline. rekeyedBy is empty.
2553
- placement with ios.remote or android.remote set to auto or a Mac,
2554
- where the plan assumes the device runs: "on <machine>" or
2555
- "this machine; auto may use <machines>"; absent otherwise
2578
+ placement with ios.remote or android.remote set to auto, remote,
2579
+ remote-only or a Mac, where the plan assumes the device runs:
2580
+ "on <machine>" or "this machine; <word> may use <machines>";
2581
+ absent otherwise
2556
2582
  refusal { code, message, remedy } when the run would refuse:
2557
2583
  STIM_PREBUILD_FAILED for a tracked native dir the fingerprint
2558
2584
  leaves out, STIM_EAS_BUILD_MISSING for an EAS miss
@@ -2707,9 +2733,9 @@ BUILD PLACEMENT (\`offload\`)
2707
2733
  decision is "here", "offloaded", or "fell-back" (it tried a remote machine
2708
2734
  and built here). reason is why: the run's \`placement:\` reason, such as
2709
2735
  "load 0.6/core, 1 of 3 build slots busy here" (auto keeps the build here
2710
- while this machine has room) or "remote.buildMode is off" (which prints no line),
2736
+ while this machine has room) or "--remote-build local" (which prints no line),
2711
2737
  or for a fallback the cause, prefixed by the machine it concerns, such as
2712
- "mini: no less loaded (load 1.2/core there, 0.6/core here)". buildMs is the compile here, or the offloaded build's
2738
+ "mini: saturated: load 2.4/core there, at or above 2/core". buildMs is the compile here, or the offloaded build's
2713
2739
  total (offer, sync, build and fetch); localEstimateMs is this project's
2714
2740
  last cold build here before the run. A run that hits the cache or waits
2715
2741
  for another workspace's build compiles nothing and records none.
@@ -3199,7 +3225,8 @@ FLAGS
3199
3225
  shared untagged Metro and app client records (not the web
3200
3226
  page's); default also includes untagged legacy records
3201
3227
  --source <s...> metro, client, device, build, agent, maintenance, placement (one or more), or all.
3202
- gc alone reads the machine-wide gc log (GC LOG below).
3228
+ gc alone reads the machine-wide gc log (GC LOG below);
3229
+ builds alone reads the machine-wide build ledger (BUILD LEDGER below).
3203
3230
  An unknown value is REJECTED rather than quietly matching
3204
3231
  nothing.
3205
3232
  --level <l> minimum level: debug, info, warn, error, fatal
@@ -3252,7 +3279,8 @@ serve bounded log queries.
3252
3279
  SecTrust, WebKit, CoreUI) that have nothing to do with your app. The proven
3253
3280
  ones are demoted to info by the collector, and iOS main-thread
3254
3281
  "Synchronous URL loading" performance diagnostics (Expo icons served by
3255
- Metro raise them) to warn, so a launch does not count them as app errors;
3282
+ Metro raise them) and SwiftUI "Invalid Configuration" faults (the Expo dev
3283
+ launcher raises them for a duplicated server entry) to warn, so a launch does not count them as app errors;
3256
3284
  the scope rule covers the rest.
3257
3285
  The metro stream carries exactly one demotion of its own, and it is Stim's
3258
3286
  doing: the dev-client deep link \`ios\`/\`android\` open to wire the app to
@@ -3370,6 +3398,35 @@ THE RECORD
3370
3398
  there are none. Stim adds it at query time; it is not in the
3371
3399
  log files.
3372
3400
 
3401
+ IOS BOOT RECORDS
3402
+ Booting an owned iOS simulator writes records to the workspace's device.ndjson:
3403
+ src device, platform ios, deviceId the simulator udid, slot when it is not
3404
+ default, level info unless noted. They show in a plain \`logs\` and in
3405
+ \`--source device\`, and never in \`--errors\`.
3406
+ event ios_boot, with phase:
3407
+ boot_requested just before simctl boot; boot is adopted (a parked
3408
+ simulator), created, existing (an owned simulator
3409
+ that was shut down) or restart
3410
+ bootstatus_done simctl bootstatus -b returned
3411
+ springboard_started SpringBoard finished starting; pid is its pid
3412
+ viewer_opened the simulator viewer was asked to open it
3413
+ simslim_reboot SimSlim changed the simulator and SpringBoard's pid
3414
+ changed; pidBefore and pidAfter are null when the pid
3415
+ could not be read
3416
+ restart warn: SpringBoard is not running after boot and the
3417
+ simulator was shut down; Stim boots it once more
3418
+ event springboard_crash_reports, written before that restart and again if
3419
+ SpringBoard is still missing after it. files lists the SpringBoard-*.ips
3420
+ reports that name this simulator and were captured in the last ten minutes,
3421
+ copied from ~/Library/Logs/DiagnosticReports and its Retired folder into
3422
+ the workspace logs directory; level is warn, or info with an empty files
3423
+ when none matched. dropped counts matching reports that exceeded the limits
3424
+ below and were not copied; failure is the message of a write that failed,
3425
+ with the reports copied before it in files. macOS deletes the originals
3426
+ after about two days; Stim only reads that folder. It looks at the newest
3427
+ 100 reports of at most 1 MiB each, copies at most 8 MiB per run, and keeps
3428
+ at most 40 in the directory.
3429
+
3373
3430
  Maintenance records use src: maintenance and appear in the plain timeline.
3374
3431
  stim logs --source maintenance
3375
3432
  Add --errors to --source maintenance to show only maintenance_failure events;
@@ -3397,6 +3454,43 @@ Each record has src: gc, level (info, or error for a failed item), msg and
3397
3454
  and the reason a kept or failed item was left
3398
3455
  Records of one run share runId. See stim guide cleanup, WHAT WAS CLEARED.
3399
3456
 
3457
+ BUILD LEDGER
3458
+ stim logs --source builds reads $STIM_HOME/builds/builds.ndjson from any
3459
+ directory: one record per native build run (ios, android or macos; compiled
3460
+ here, offloaded or fetched from a cache; succeeded, failed or cancelled). It
3461
+ keeps the newest 500 records, outlives the workspace that ran the build, and
3462
+ is never removed by gc. It cannot be combined with other sources, and --source
3463
+ all does not include it. --errors shows the failed runs. A record has
3464
+ src: builds, event: build, level (info, warn for cancelled, error for failed),
3465
+ msg and
3466
+ ts, startedAt epoch milliseconds when the run finished and started
3467
+ id the run
3468
+ project, workspace, branch
3469
+ project root, workspace id and the branch checked out
3470
+ platform ios, android or macos
3471
+ scheme, variant, configuration
3472
+ the iOS scheme when one was selected, the Android variant,
3473
+ the build configuration
3474
+ placement { where: local | offloaded, machine?, fellBack?, reason? };
3475
+ machine is the build machine that compiled the app, and
3476
+ fellBack with reason marks a build that considered
3477
+ offloading and compiled here
3478
+ cache { result: hit | miss | bypassed, source?: local | remote,
3479
+ key, fingerprint, missReason? }
3480
+ durationMs, phases
3481
+ wall time and milliseconds in each local phase (prepare,
3482
+ cache-lookup, wait, prebuild, pods, compile, device,
3483
+ install, launch)
3484
+ offloadPhases milliseconds on the build machine: sync, deps, prebuild,
3485
+ pods, build, fetch
3486
+ outcome succeeded, failed or cancelled
3487
+ failure failed or cancelled runs: { code, message, remedy, phase }
3488
+ device { name, runtime? } of the simulator, emulator or device;
3489
+ never its serial or UDID
3490
+ stimVersion the stim that ran the build
3491
+ A run that stopped before it had an app to build (a refused flag, a missing
3492
+ project) writes no record. Writing the record never changes the run's result.
3493
+
3400
3494
  REMOTE REQUEST RECORDS
3401
3495
  A request to another machine that fails is a warn record with src: build in the
3402
3496
  run's build log: remote_connect_failed { host, capability, ms, timeoutMs, msg,
@@ -3426,16 +3520,45 @@ Events: run_start, run_end { exit, ms }, exec { program, ms, ok, exit? }, and
3426
3520
  remote_connect and remote_request { ms, ok, code? } for requests to another
3427
3521
  machine. They carry no arguments, tokens or tickets.
3428
3522
 
3523
+ TIMING TRACES
3524
+ STIM_TRACE=<path> makes a stim command write a timeline of where it spent its
3525
+ time, as Chrome trace events. Open the file in Perfetto (ui.perfetto.dev) or
3526
+ chrome://tracing. Unset, nothing is written and no file is opened. There is no
3527
+ setting for it, and the trace goes only to that file, never to stdout or stderr.
3528
+ Use a new path for each command: the command you start truncates the file, and
3529
+ the processes it starts (a Metro supervisor, collectors, a nested stim) append
3530
+ to it.
3531
+ STIM_TRACE=/tmp/ios.json stim ios
3532
+ The file is a JSON array with one event per line and no closing bracket, so
3533
+ read it with JSON.parse(text.trimEnd().replace(/,$/, '') + ']'). Every event
3534
+ has pid; spans have ts and dur in microseconds since the epoch. Events:
3535
+ command one span named "stim <command>" with the exit code in args.exit
3536
+ exec one span per child process. args.argv (or args.command for a shell
3537
+ command) has the values of secret-looking flags, credentials in
3538
+ URLs and token shapes replaced by [redacted], and the credentials
3539
+ of curl -u and security import -P; other short flags such as -p
3540
+ and -u are kept. args also has cwd, ok, exit and signal
3541
+ phase the build phases of an ios, android or macos run on a
3542
+ "phases: <platform> build" track, named as in stats.json and the
3543
+ build ledger (prepare, cache-lookup, wait, prebuild, pods, compile,
3544
+ device, install, launch), and the steps of worktree warm
3545
+ span fingerprint, wait for metro and carry ignored entries
3546
+ mark instant events such as a fingerprint memo hit
3547
+ Children running at the same time are drawn on separate tracks. A span still
3548
+ open when its process exits is written with unfinished: true; a process ended
3549
+ by a signal writes none. A trace holds
3550
+ full paths and arguments, so treat it like a log.
3551
+
3429
3552
  Placement records use src: placement and sit in the run's build log
3430
3553
  (build-*.ndjson), so a new run replaces the previous run's. A run writes one
3431
3554
  record per decision it makes (a build that compiles and has a remote machine to
3432
- consider, or ios/android --remote auto), with event build_placement,
3555
+ consider, or ios/android --remote auto|remote|remote-only), with event build_placement,
3433
3556
  device_placement or placement_fallback. A cache hit, a project with no paired
3434
3557
  remote machine and a named --remote target write none:
3435
3558
  stim logs --source placement
3436
3559
  stim logs --source placement --json
3437
3560
  Fields: kind (build or device), platform, settings [{ key, value, from }] with
3438
- from flag, env, setting or default (remote.build and remote.buildMode for a
3561
+ from flag, env, setting or default (remote.build for a
3439
3562
  build, ios.remote or android.remote, plus remote.easFallback when it is on, for
3440
3563
  a device), candidates [{ machine, code,
3441
3564
  msg, detail? }], choice { machine, code, msg } with machine local when the run
@@ -3458,6 +3581,8 @@ WHAT WRITES WHAT
3458
3581
  maintenance.ndjson workspace maintenance actions, failures and explaining skips
3459
3582
  $STIM_HOME/gc/gc.ndjson machine-wide, not in a workspace: what gc, maintenance
3460
3583
  and the budget gate cleared (stim logs --source gc)
3584
+ $STIM_HOME/builds/builds.ndjson machine-wide: the last 500 native builds
3585
+ (stim logs --source builds)
3461
3586
  metro.ndjson the bundler, in every supervisor mode. Stim's
3462
3587
  middleware adds one record per bundle request
3463
3588
  (bundle_response_started, _progress, _finished,
@@ -3480,7 +3605,8 @@ WHAT WRITES WHAT
3480
3605
  -- and, on iOS, where every Apple framework running in
3481
3606
  the app's process also logs. The proven noise sources
3482
3607
  are recorded at info rather than error (Synchronous
3483
- URL loading diagnostics at warn); the rest is why
3608
+ URL loading diagnostics and SwiftUI Invalid Configuration
3609
+ faults at warn); the rest is why
3484
3610
  --errors leaves this source out unless asked. A VERIFIED
3485
3611
  LAUNCH counts these records and prints one line:
3486
3612
 
@@ -3673,7 +3799,12 @@ Branch on the code, never on the message.`,
3673
3799
  hosted session. Auto skips declined offers before building, then uses the
3674
3800
  local queue if none admits. A reserve refusal after an accepted offer fails
3675
3801
  with this code and asks to retry; it does not re-place the built architecture.
3676
- Check stim-server and Tailscale there,
3802
+ remote-only never runs here and never joins the local queue: it refuses
3803
+ with this code when no host could ever take the run, and waits for a host that
3804
+ is only full or loaded (see STIM_AT_CAPACITY).
3805
+ An unreachable host's message and remedy are the diagnosis stim doctor gives
3806
+ (Tailscale off, Mac offline with last seen, serve route, stim-server), without
3807
+ tailscale ping. Check stim-server and Tailscale there,
3677
3808
  then run stim doctor. Correct --device-type / --runtime for iOS or
3678
3809
  --system-image / --device-profile for Android when the offer names
3679
3810
  an unavailable choice. A session that exists stays recorded even if delivery
@@ -3795,9 +3926,14 @@ Branch on the code, never on the message.`,
3795
3926
  note naming which of the two set it; Gemfile.lock is never edited either way.
3796
3927
  \`worktree warm --refresh\` reports the same code for the install it runs in
3797
3928
  the SOURCE CHECKOUT -- the lockfile's own command (\`pnpm install\`,
3798
- \`yarn install\`, \`bun install\`, \`npm ci\`) or that same pod ladder. The
3929
+ \`yarn install\`, \`bun install\`, \`npm ci\`), the \`npm run postinstall\`
3930
+ it runs when npm's config there sets ignore-scripts, or that same pod ladder. The
3799
3931
  message names the command, quotes its last lines, and nothing is copied: fix
3800
- the source checkout, then warm again.`
3932
+ the source checkout, then warm again.
3933
+ start, ios and android also refuse with this code when \`gc --delete\`
3934
+ lightened the worktree and the \`stim worktree warm\` they ran to restore
3935
+ its node_modules and ios/Pods failed. The warm's own lines say why; fix
3936
+ that, then run \`stim worktree warm\` or retry the command.`
3801
3937
  },
3802
3938
  STIM_BUILD_FAILED: {
3803
3939
  summary: "xcodebuild or gradle failed; the Android missing-SDK and APK refusals",
@@ -4035,6 +4171,11 @@ and no enabled remote can finish this build, or when the build requires a local
4035
4171
  compiler. Enable local or choose an explicit --remote-build placement. Existing
4036
4172
  builds are not interrupted and a cache hit needs no compiler.
4037
4173
 
4174
+ remote-only never builds here: it refuses when no remote machine in remote.machines
4175
+ can take the build, naming each machine and why it was skipped, and for a build that
4176
+ cannot leave this machine. Rerun with --remote-build remote to allow a local build
4177
+ when no remote machine can take it.
4178
+
4038
4179
  A named --remote-build selection is strict. The message names the machine
4039
4180
  and why it cannot take or finish the build: not configured or paired, approval
4040
4181
  pending or denied, unreachable, changed pinned server key, a stim-server
@@ -4063,7 +4204,7 @@ Run stim doctor --fix to ask for build access if not paired. Check
4063
4204
  stim settings get remote.machines. A person on the worker approves this machine
4064
4205
  by running the stim-server setup command doctor --fix printed, with its ticket
4065
4206
  and expiry (guide settings). Agents never run setup or approve requests.
4066
- Rerun with --remote-build auto for normal placement and local fallback, or
4207
+ Rerun with --remote-build auto or remote for placement with local fallback, or
4067
4208
  --remote-build local to keep the build here.
4068
4209
  `
4069
4210
  },
@@ -4158,6 +4299,13 @@ Rerun with --remote-build auto for normal placement and local fallback, or
4158
4299
  Trust, then run the command again. It is a per-developer-certificate tap, not
4159
4300
  a per-build one -- but an uninstall clears it, including the one Stim's own
4160
4301
  signer-conflict retry performs.
4302
+ On a local iOS simulator, a cache-hit app that dyld kills at launch because
4303
+ one part of the app cannot bind a symbol from another part of the same app
4304
+ (for example React.framework against hermesvm.framework) is a damaged build,
4305
+ not a launch problem. Stim removes that cache entry, and the remedy names
4306
+ it. Run the command again to build the app fresh. A cache provider's copy is
4307
+ not removed; add \`--no-build-cache\` to skip it. If the fresh app crashes
4308
+ the same way, the project's own native code is the cause.
4161
4309
  With \`--remote eas\` the remedy names the EAS Simulator session that is
4162
4310
  still running, as for STIM_INSTALL_FAILED.`
4163
4311
  },
@@ -4439,6 +4587,16 @@ so a Debug run on one is wired to a LAN origin instead of localhost.`,
4439
4587
  installation. If it fails, the refusal includes observed host memory pressure
4440
4588
  when available. Free memory before retrying under pressure; see
4441
4589
  \`stim guide lifecycle simslim\`. Booted alone does not prove readiness.
4590
+ The same probe lists the simulator's launchd jobs and requires a running
4591
+ SpringBoard. A simulator that reports Booted while SpringBoard has died
4592
+ (\`simctl launch\` then says "The system shell probably crashed") is shut
4593
+ down through teardown and booted once more; if SpringBoard is still not
4594
+ running, the refusal names the udid and the remedy: \`stim stop\`, then
4595
+ \`stim ios\` again. Repeated SpringBoard crashes leave reports in
4596
+ ~/Library/Logs/DiagnosticReports, and macOS deletes them after about two
4597
+ days; before the restart Stim copies the simulator's recent ones into the
4598
+ workspace logs directory and records the boot steps by udid
4599
+ (\`stim logs --source device\`, \`stim guide logs\`, IOS BOOT RECORDS).
4442
4600
  On Android the emulator's own stdio is captured to
4443
4601
  the global workspace logs/emulator.log (truncated per boot), and when it printed a
4444
4602
  \`FATAL |\` / \`ERROR |\` / \`PANIC:\` line THAT is the message and the remedy
@@ -4541,6 +4699,11 @@ so a Debug run on one is wired to a LAN origin instead of localhost.`,
4541
4699
  locks, builds, viewers and recent activity. Reclaim excludes this workspace,
4542
4700
  physical, hosted, remote, parked and other homes' devices and never deletes.
4543
4701
  A failed reclaim is logged and skipped; the run keeps waiting.
4702
+ --remote remote-only (and macos.remote remote-only) has no local queue: when every
4703
+ approved host is full, loaded or short of memory it polls the hosts every 5 seconds
4704
+ for the same wait (--wait, default 600 seconds) and then refuses with this code;
4705
+ --no-wait and --wait 0 refuse at once. Builds under remote-only refuse with
4706
+ STIM_OFFLOAD_REFUSED instead.
4544
4707
  Stop an environment (\`stim stop\`), pass a longer \`--wait <seconds>\`,
4545
4708
  or raise concurrency.maxDevices. With --remote auto, remote.machines and
4546
4709
  the opt-in remote.easFallback (billed EAS Simulator) are tried before this
@@ -4980,8 +5143,8 @@ Inspect what remains with gc() before retrying a mutation.
4980
5143
  `
4981
5144
  },
4982
5145
  STIM_CANCELLED: {
4983
- summary: "an ios or android run was interrupted by `stim stop` or Ctrl-C before it finished",
4984
- body: () => `STIM_CANCELLED (ios, android; exit 130)
5146
+ summary: "an ios, android or macos run was interrupted by `stim stop` or Ctrl-C before it finished",
5147
+ body: () => `STIM_CANCELLED (ios, android, macos; exit 130)
4985
5148
  "The ios run was cancelled by \`stim stop\` (pid <n>) before it finished."
4986
5149
  The run was interrupted: by \`stim stop\`, which cancels a build it leaves
4987
5150
  with nothing to deploy to, or by Ctrl-C. It forwarded the interrupt
@@ -4990,8 +5153,11 @@ Inspect what remains with gc() before retrying a mutation.
4990
5153
  stopped at. An interrupted build stores no artifact, so the next run looks
4991
5154
  up the cache as usual. Stopped between build tools, a run that \`stop\`
4992
5155
  cancelled starts no further build tool and stops before it installs. A
4993
- second Ctrl-C exits at once, and so does a Ctrl-C with no build tool or
4994
- SimSlim to stop; those exits print no payload. Run the same command again when you want the
5156
+ Ctrl-C while the run delivers the app to a hosting Mac, or waits for the app
5157
+ to load its bundle or report readiness, ends that work and reports
5158
+ STIM_CANCELLED. A second Ctrl-C exits at once, and so does a Ctrl-C with no
5159
+ build tool, SimSlim, hosted app delivery or launch readiness wait to stop;
5160
+ those exits print no payload. Run the same command again when you want the
4995
5161
  app on a device.`
4996
5162
  },
4997
5163
  STIM_STOP_BLOCKED: {
@@ -5204,8 +5370,9 @@ not on any remote" (worktree remove)
5204
5370
  lockfile says nothing about the one on disk now, whose dependencies may well
5205
5371
  have been installed since; and a repository with no record at all -- every
5206
5372
  repository before its first \`--refresh\` -- copies as it always has.
5207
- In a monorepo the record is keyed on the directory that owns the lockfile,
5208
- which is usually the repository root, so every app of it reads the same one.`
5373
+ Each directory that owns a lockfile the refresh installs has its own record:
5374
+ the repository root and, when the app has a lockfile of its own, the app. A
5375
+ plain warm reads both.`
5209
5376
  },
5210
5377
  warm: {
5211
5378
  summary: "worktree warm claim storage refusals and recovery",
@@ -5901,9 +6068,9 @@ If Stim is not installed globally, replace stim with npx stim.
5901
6068
 
5902
6069
  A person on the host approves device-host access, separately from build, read
5903
6070
  and control. The setup in hosted-ios also serves Android. android.remote sets
5904
- a workspace default. eas and proxy remain backends. --remote auto or
5905
- android.remote = auto places automatically; see hosted-ios for the shared
5906
- policy. No flag or setting runs on this machine.
6071
+ a workspace default. eas and proxy remain backends. --remote auto, remote or
6072
+ remote-only (or the same android.remote value) places automatically; see
6073
+ hosted-ios for the shared policy. No flag or setting runs on this machine.
5907
6074
 
5908
6075
  A named Mac is strict: refusal, unreachable, declined or failed preparation
5909
6076
  returns STIM_HOSTING_REFUSED without fallback. --device with --remote
@@ -6030,8 +6197,8 @@ Logcat retains a finite buffer; records already evicted or persisted beyond the
6030
6197
  overlap may be unavailable. An oversized query retries the recent tail and writes
6031
6198
  a device warning naming any dropped interval. Followers share a child-aware log
6032
6199
  claim; install and stop cancel and settle the worker before native operations.
6033
- Stop copies logs before and after the host's final collection and teardown, with
6034
- each client drain bounded to 30 seconds and progress on stderr. Collected logs
6200
+ Stop copies logs once, after the host's final collection and teardown, bounded to
6201
+ 30 seconds with progress on stderr; Ctrl-C cancels the copy and keeps the placement. Collected logs
6035
6202
  remain readable after stop. The host advertises hosted-android-data for Android
6036
6203
  handoff and native logs. Older hosts get a newer stim-server note, use upload
6037
6204
  and show records already copied here.`
@@ -6051,28 +6218,44 @@ If Stim is not installed globally, replace stim with npx stim.
6051
6218
  stim reload ios
6052
6219
  stim stop
6053
6220
 
6054
- The same setup serves macos --remote. Approval is separate from build, read
6221
+ The same setup serves macos --remote and macos.remote. Approval is separate from build, read
6055
6222
  and phone control. Set ios.remote to the machine name for a default (machine scope for every app on this machine);
6056
6223
  no flag or setting runs here. eas and proxy keep their remote backend meanings.
6057
6224
  --remote auto (or ios.remote = auto / android.remote = auto) stays here when
6058
6225
  there is a free concurrency.maxDevices slot (0 means unlimited), no device
6059
6226
  waiter ahead, normal host memory pressure, no budget shortfall,
6060
6227
  and 5-minute load per core below server.maxLoadPerCore. Without remote.machines,
6061
- auto is local. An unknown local device count also stays local; boot admission
6228
+ auto and remote are local and remote-only refuses with STIM_HOSTING_REFUSED. An unknown local device count also stays local; boot admission
6062
6229
  still decides. The default without a flag or setting stays local. remote.devicePoolDisabled excludes
6063
6230
  members from new automatic placement only; an excluded local member is never a
6064
6231
  fallback. Existing sessions keep their owner and named placement bypasses the pool.
6065
6232
  See stim guide settings for separate build/device membership controls.
6066
6233
 
6234
+ --remote remote (or ios.remote = remote) skips that local check and asks the hosts
6235
+ first, in remote.machines order; the simulator runs here only when none admits
6236
+ it, with the same wait and EAS fallback as auto. --remote remote-only never runs
6237
+ here and never joins the local queue or uses EAS. When every host that answered is
6238
+ only full, loaded or short of memory, it waits for one to have room, polling every
6239
+ 5 seconds for --wait seconds (default 600) and printing one placement line; --no-wait,
6240
+ --wait 0 or the end of the wait refuse with STIM_AT_CAPACITY. When no host can ever take
6241
+ the run (none configured, unreachable, declined for another reason such as paused),
6242
+ it refuses at once with STIM_HOSTING_REFUSED, naming each skipped host and its reason.
6243
+ --plan never waits. It prints the same STIM_HOSTING_REFUSED, or a STIM_BAD_ARG naming
6244
+ an invalid remote.build or STIM_REMOTE_BUILD.
6245
+ Excluding local in remote.devicePoolDisabled has the same effect on auto and remote.
6246
+
6067
6247
  Otherwise Stim asks every approved remote.machines host in parallel, skipping
6068
6248
  one that tailscale status reports as running an OS other than macOS, with a
6069
6249
  3-second probe timeout and this run's model/runtime or image/profile selectors.
6070
- A compatible choice, no declined reason, normal memory pressure and available
6071
- capacity are required. With a free local slot, a host must report a lower load;
6072
- with a full cap or a queue ahead, an admitted host with unknown load is allowed.
6073
- Hosts rank by the build preference (flag > STIM_REMOTE_BUILD > remote.build)
6074
- when it names a machine, then lowest load, most free memory and remote.machines order. Automatic build
6075
- offload resolves later, after the device architecture is known.
6250
+ A compatible choice, no declined reason, normal memory pressure, available
6251
+ capacity and a load per core under the host's own server.maxLoadPerCore are
6252
+ required (a host that reports no limit uses this machine's). With a free local
6253
+ slot, a host with unknown load is skipped under auto (remote admits it); with a full cap or a queue ahead, an
6254
+ admitted host with unknown load is allowed. Admitted hosts are tried in order:
6255
+ the host named by the build preference (flag > STIM_REMOTE_BUILD > remote.build)
6256
+ when it names a machine, then remote.machines order, so the first listed host with
6257
+ room fills before the next gets a run. Load and free memory do not rank hosts.
6258
+ Automatic build offload resolves later, after the device architecture is known.
6076
6259
 
6077
6260
  When no host admits and this machine is at its concurrency.maxDevices cap (or runs
6078
6261
  are queued ahead), remote.easFallback (default false; EAS Simulator is billed)
@@ -6089,7 +6272,7 @@ metro.tunnel off; on an Expo tunnel, run stim start --remote first). A recorded
6089
6272
  EAS session is not sticky: once this machine has room, auto runs here and the
6090
6273
  session bills until stim stop or remote.easMaxDurationMinutes. If any check fails, or the
6091
6274
  setting is off, auto runs here and may wait in the existing FIFO device
6092
- slot queue; --no-wait and --wait 0 refuse with STIM_AT_CAPACITY. The placement
6275
+ slot queue; --no-wait and --wait 0 refuse with STIM_AT_CAPACITY (remote-only has no local queue and waits for a remote host instead, as described above). The placement
6093
6276
  line explains the decision, the skipped hosts and why EAS was not used
6094
6277
  (machine "eas"). JSON progress goes to stderr.
6095
6278
  The same decision, with a reason code per host, is a src: placement record in
@@ -6101,8 +6284,8 @@ A reserve refusal after a successful offer can race with another run. It fails
6101
6284
  with STIM_HOSTING_REFUSED naming the host and asking to retry. Stim does not
6102
6285
  try the next host or local placement after the build has targeted that host's
6103
6286
  architecture, and never falls back once a hosted session exists. A named machine remains strict. --device
6104
- cannot combine with --remote auto, and a --device run ignores ios.remote and
6105
- android.remote. eas/proxy and macOS placement are unchanged.
6287
+ cannot combine with --remote auto, remote or remote-only, and a --device run ignores ios.remote and
6288
+ android.remote. eas/proxy placement is unchanged; macOS placement follows macos.remote (stim guide macos).
6106
6289
  --simulator-app is honoured locally with auto and ignored with a note on a host.
6107
6290
  Placement measures budgets without reclaiming; only a new local run reclaims.
6108
6291
  Android --remote <machine> uses an owned emulator on that Mac; see hosted-android.
@@ -7444,8 +7627,10 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
7444
7627
  Waiting prints the count, holder workspace names
7445
7628
  and elapsed time at once and about every 10s, then
7446
7629
  the queue position, head workspace name and
7447
- $STIM_HOME/device-waits path. Timeout names the
7448
- same head and path. Stop cancels a queued run and
7630
+ $STIM_HOME/device-waits path. Workspaces that
7631
+ share a project name show the directory that tells
7632
+ them apart, as in mobile (qa-b-p2). Timeout names
7633
+ the same head and path. Stop cancels a queued run and
7449
7634
  releases its ticket without booting its device.
7450
7635
  Status JSON adds build.waitingFor independently
7451
7636
  of phase: { kind: "device-slot" | "build-slot",
@@ -7523,7 +7708,14 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
7523
7708
  (\`gc --delete\` semantics; the next build installs from the shared
7524
7709
  cache). A workspace whose last use is unknown is kept
7525
7710
  4. trim shared cache entries nothing has used for 14 days
7526
- (\`gc --delete --older-than 14\` for the caches)
7711
+ (\`gc --delete --older-than 14\` for the caches). Caches with nothing
7712
+ to trim print one line, not one each. A trim that removed nothing is
7713
+ recorded in $STIM_HOME/budget-sweep.json and not repeated for 5
7714
+ minutes while the set of caches is the same and free disk has not
7715
+ fallen by more than 1 GB since; the run prints one budget line saying
7716
+ it skipped. \`gc\` and automatic maintenance never skip, and a dry
7717
+ run (\`doctor\`) still plans the step. The STIM_LOW_DISK message
7718
+ measures the largest uses of disk at the same time.
7527
7719
 
7528
7720
  IDLE SHUTDOWN (30 MINUTES BY DEFAULT): with devices.idleShutdownMinutes above
7529
7721
  0, a workspace's supervisor shuts down that workspace's owned simulators and
@@ -7592,8 +7784,8 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
7592
7784
  summary: "every flag per command, Android variants and flavors, the per-run simulator model, runtime and system image",
7593
7785
  body: () => `THE OPTION SURFACE, IN FULL
7594
7786
  start --json --wait <seconds> --remote --reset-cache
7595
- ios --remote-build <auto|local|name> --slot <name> --json --plan --no-metro-check --no-build-cache --scheme <name> --configuration <name> --device-type <name> --runtime <version> --simulator-app <xcode|siniulator|stim-desktop> --device [udid] --wait <seconds> --no-wait --remote <eas|proxy|auto|machine>
7596
- android --remote-build <auto|local|name> --slot <name> --json --plan --no-metro-check --no-build-cache --variant <name> --system-image <id> --device-profile <id> --device [serial] --wait <seconds> --no-wait --remote <proxy|eas>
7787
+ ios --remote-build <local|auto|remote|remote-only|name> --slot <name> --json --plan --no-metro-check --no-build-cache --scheme <name> --configuration <name> --device-type <name> --runtime <version> --simulator-app <xcode|siniulator|stim-desktop> --device [udid] --wait <seconds> --no-wait --remote <eas|proxy|local|auto|remote|remote-only|machine>
7788
+ android --remote-build <local|auto|remote|remote-only|name> --slot <name> --json --plan --no-metro-check --no-build-cache --variant <name> --system-image <id> --device-profile <id> --device [serial] --wait <seconds> --no-wait --remote <proxy|eas|local|auto|remote|remote-only|machine>
7597
7789
  ios/android --wait bounds owned-device slot waits (default 600s), or physical
7598
7790
  leases with --device (default 60s). Remote targets do not join this local
7599
7791
  slot queue. --no-wait refuses a full/queued owned
@@ -7701,12 +7893,24 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
7701
7893
  .stim.json, else \`git symbolic-ref --short refs/remotes/origin/HEAD\`;
7702
7894
  when neither answers it says so and makes no warning.
7703
7895
 
7704
- Dependencies install where the lockfile is, which in a monorepo is the
7705
- repository root, when the lockfile moved or nothing is installed. Pods run
7706
- for the app the command was invoked from, and only that app, when its
7707
- ios/Podfile.lock moved or ios/Pods/Manifest.lock does not match it. Each
7708
- completed or skipped deps and pods step names its source directory and
7709
- reason. A failed install refuses with STIM_DEPS_FAILED and nothing is copied.
7896
+ The app is the one warm prepares: the app the command was invoked from, or,
7897
+ from a monorepo root, the one app registered in the source checkout or in
7898
+ the worktree. With no such app, a refresh from the root updates only the
7899
+ repository root and says so on stderr.
7900
+ Dependencies install where the repository root's lockfile is and, when the
7901
+ app has its own lockfile (a nested package outside the root workspaces),
7902
+ there too; a root that has neither installed dependencies nor an install
7903
+ record is left alone when the app has its own lockfile. Each installs when
7904
+ its lockfile moved, when its patches/ directory moved (npm projects only),
7905
+ when nothing is installed, or when npm reports a tree that differs from the
7906
+ one it reported after the last completed install. An npm project whose
7907
+ npm config sets ignore-scripts gets no postinstall from \`npm ci\`, so when its
7908
+ package.json defines one the refresh runs \`npm run postinstall\` next
7909
+ (patch-package, usually); dependencies' own install scripts stay blocked.
7910
+ Pods run for that app, and only that app, when its ios/Podfile.lock moved or
7911
+ ios/Pods/Manifest.lock does not match it. Each completed or skipped deps and
7912
+ pods step names its source directory and reason. A failed install or
7913
+ postinstall refuses with STIM_DEPS_FAILED and nothing is copied.
7710
7914
 
7711
7915
  An install that did not finish is remembered, and a PLAIN warm reads that
7712
7916
  before it copies. The refresh records the completed install of the lockfile it
@@ -7771,7 +7975,11 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
7771
7975
  expo-modules-jsi/apple/.DerivedData Xcode build state, directly under
7772
7976
  node_modules or in the pnpm virtual store. An ignored directory that Git
7773
7977
  reports above those names (for example an app directory with no tracked
7774
- files) is copied as is. Warm also skips paths
7978
+ files) is copied as is. Warm renames those deleted paths into
7979
+ $STIM_HOME/trash and a detached, low-priority rm deletes them after warm
7980
+ returns (through the programmatic API, before the call resolves); when the
7981
+ rename is not possible (Windows, $STIM_HOME on another volume), warm deletes
7982
+ them before it returns. Warm also skips paths
7775
7983
  overlapping a nested destination worktree or below a symlink ancestor.
7776
7984
  Tracked .idea settings come from Git and stay untouched by warm.
7777
7985
 
@@ -8457,11 +8665,14 @@ In on mode a pass runs, cheapest to rebuild first:
8457
8665
  claim is unresolved; the emptying holds the exclusive claim
8458
8666
  5. linked worktrees whose branch or pull request finished (gc's finished
8459
8667
  worktree rules and gc.worktreeGraceMinutes; maintenance.removeFinishedWorktrees),
8460
- and, with maintenance.removeIdleWorktrees (off by default), in the age
8668
+ a dirty finished one after archiving its uncommitted files (ARCHIVED
8669
+ CHANGES in stim guide cleanup gc), clean ones with no commits of their
8670
+ own (NO COMMITS OF ITS OWN there), and, with
8671
+ maintenance.removeIdleWorktrees (off by default), in the age
8461
8672
  sweep clean, pushed ones unused for olderThanDays, as gc --delete
8462
8673
  --worktrees --older-than removes them, git-ignored files included; never
8463
- a dirty one, one with unpushed commits, the source checkout, or one whose
8464
- last use is unknown
8674
+ an unfinished dirty one, one with unpushed commits, the source checkout,
8675
+ a locked one, or one whose last use is unknown
8465
8676
  6. parked simulators and emulators unused for olderThanDays in the age
8466
8677
  sweep, through centralized teardown with ownership checked again
8467
8678
  7. owned simulators and emulators of a finished worktree that is kept, such
@@ -8497,14 +8708,21 @@ DISK PRESSURE LEVELS
8497
8708
  level 2, which runs until free disk is back above the floor; level 3 runs
8498
8709
  only while free disk stays under the level 3 floor. At levels 2 and 3 the
8499
8710
  protection window is maintenance.pressureProtectMinutes (10) instead of
8500
- protectRecentHours. Each step re-measures first and stops once free disk is
8501
- back above its level's floor, cheapest to rebuild first.
8711
+ protectRecentHours, except for the build outputs and build directories of
8712
+ level 2, which follow the lightening rule of gc --delete (WORKSPACE BUILD
8713
+ OUTPUTS): a running dev server, a booted or hosted device and a managed
8714
+ tunnel do not keep them, a build or a running macOS app does, and they stay
8715
+ for 15 minutes after the workspace's last ios, android or macos run.
8716
+ Each step re-measures first and stops once free disk is back above its
8717
+ level's floor, cheapest to rebuild first.
8502
8718
  Level 2:
8503
- 1. build outputs of workspaces unused for the short window
8719
+ 1. build outputs of workspaces, with the lightening rule above
8504
8720
  2. parked simulators and emulators, deleted through centralized teardown
8505
- 3. build directories of idle worktrees: ios/build, android/app/build,
8506
- android/build, android/.gradle and .build, at a project root, the
8507
- worktree root, or apps/* and packages/*
8721
+ 3. build directories of worktrees with a Stim workspace, by the same
8722
+ rule: android/app/build, android/build, android/.gradle and .build, at
8723
+ a project root, the worktree root, or apps/* and packages/*. ios/build
8724
+ stays: React Native's pod install writes the codegen output and
8725
+ podspec facades there that only pod install recreates
8508
8726
  4. owned simulators and emulators of workspaces idle for the short window
8509
8727
  5. Stim temp directories, xctrace recordings and SwiftPM temp directories
8510
8728
  in TMPDIR unmodified for 24 hours, with gc --delete's checks (see stim guide cleanup gc)
@@ -8514,12 +8732,14 @@ DISK PRESSURE LEVELS
8514
8732
  and the build
8515
8733
  2. finished worktrees, and clean, pushed worktrees with no commits of
8516
8734
  their own beyond the default branch, with the short window as grace
8517
- period (maintenance.removeFinishedWorktrees still applies)
8735
+ period and idle window (maintenance.removeFinishedWorktrees still applies)
8518
8736
  3. workspaces whose dev server runs but that are otherwise inactive for
8519
8737
  the short window: their dev server stops and their owned simulators
8520
8738
  and emulators are deleted
8521
8739
  4. node_modules and ios/Pods at the root or a project root of idle linked
8522
- worktrees, never the source checkout
8740
+ worktrees, never the source checkout, with the running-process checks
8741
+ of WORKTREE DEPENDENCIES; when worktree warm can restore them they are
8742
+ recorded for the next start, ios or android to restore
8523
8743
  5. idle Gradle and Kotlin daemons, stopped as gc --delete --cache
8524
8744
  gradle-daemons stops them (never during a Stim build, never a busy or
8525
8745
  unverified one, never with STIM_HOME set), only when every Gradle
@@ -8532,8 +8752,11 @@ DISK PRESSURE LEVELS
8532
8752
  still keeps them
8533
8753
  A directory is cleared only when it is a real directory inside its
8534
8754
  worktree, git tracks nothing in it, its worktree is not in use, holds no
8535
- lock or build and is not pinned, and nothing under it, nor the worktree's
8536
- git index, HEAD or Stim state, changed within the short window. A dirty or
8755
+ lock or build and is not pinned, and, for the node_modules and ios/Pods of
8756
+ level 3, nothing under it, nor the worktree's git index, HEAD or Stim
8757
+ state, changed within the short window. A build directory of level 2 also
8758
+ stays in a worktree where no Stim workspace has recorded any state; a build
8759
+ that Stim did not run is not detected. A dirty or
8537
8760
  unpushed worktree can still be stopped and have these directories cleared;
8538
8761
  only its removal is refused. Caches of other tools (pnpm and npm stores,
8539
8762
  CocoaPods, SwiftPM, the shared Gradle caches) are never deleted.
@@ -8664,6 +8887,11 @@ this cleanup. agent-device is optional: a missing binary skips this step, and a
8664
8887
  failed or timed-out list/close prints a device line on stderr while teardown
8665
8888
  continues. Each cleanup pass allows at most 15 seconds of agent-device calls
8666
8889
  per device; the pass \`stop\` makes after shutdown has its own budget.
8890
+ A daemon agent-device will not replace (daemon-busy: it is starting up or still
8891
+ has sessions, requests or leases) is not retried. Stim prints one line per
8892
+ device, naming each busy daemon, even though it lists sessions before and after
8893
+ shutdown: sessions on the device in them stay open until the daemon is idle or,
8894
+ if it is yours, stopped with agent-device daemon stop --state-dir <dir>.
8667
8895
  A local daemon that cannot use socket transport or a CLI without the target guard
8668
8896
  skips cleanup with a warning; no remote daemon is used.
8669
8897
 
@@ -8854,7 +9082,10 @@ LINKED WORKTREES
8854
9082
  still exists, as after a removal killed between the rename and git, stays
8855
9083
  with the reason; if that worktree is the one the entry came from, run
8856
9084
  \`stim worktree remove\` on it to finish the removal.
8857
- gc never moves the directories back.
9085
+ gc never moves the directories back. Warm uses the same trash for the
9086
+ known paths it deletes from a copy (such as a copied .pnpm-store); gc
9087
+ --delete deletes such an entry (trash.json kind warm) once its claim is free,
9088
+ whatever the state of the worktree.
8858
9089
  When git still refuses the removal, the kept ownership record no longer
8859
9090
  names the devices that were parked or deleted. A workspace that is in use
8860
9091
  (see IN USE below) keeps its directory, devices and registry entry, and the
@@ -8863,11 +9094,14 @@ LINKED WORKTREES
8863
9094
  only when it has no unique commits.
8864
9095
 
8865
9096
  IN USE
8866
- gc and worktree remove never delete a workspace's directory, build outputs
8867
- or checkout while it is in use: its dev server supervisor is running or
9097
+ gc and worktree remove never delete a workspace's directory or checkout
9098
+ while it is in use: its dev server supervisor is running or
8868
9099
  cannot be verified, a \`stim ios\`, \`stim android\` or \`stim stop\` run
8869
9100
  holds its native-run.lock, a live or unresolvable build lock or slot names it,
8870
- or its managed tunnel or managed remote lock is held. The deletion holds
9101
+ or its managed tunnel or managed remote lock is held. Build outputs follow
9102
+ the narrower rule in WORKSPACE BUILD OUTPUTS (\`guide cleanup disk\`): only
9103
+ a build, a running macOS app, or an ios, android or macos run in the last
9104
+ 15 minutes keeps them. The deletion holds
8871
9105
  native-run.lock itself, so no native run can start partway through. A lock
8872
9106
  that names no workspace blocks nothing, because every build also holds its
8873
9107
  own workspace's native-run.lock; gc lists it with the command that removes
@@ -8884,14 +9118,17 @@ SWEEPING FINISHED WORKTREES
8884
9118
  ever ran in it. It groups them by git worktree and reports each linked
8885
9119
  worktree with the reason it is removed or kept. A worktree is finished when
8886
9120
  its branch is merged into the default branch, or its pull request was
8887
- merged or closed (PULL REQUESTS below). \`--worktrees\` also removes
9121
+ merged or closed (PULL REQUESTS below). A clean worktree with no commits
9122
+ of its own counts as finished once idle (NO COMMITS OF ITS OWN below).
9123
+ \`--worktrees\` also removes
8888
9124
  one that is idle: no recorded use for --older-than days, 7 without it; a
8889
9125
  worktree whose last use is unknown is kept, unless it has no commits of its
8890
- own beyond the default branch (NO COMMITS OF ITS OWN below). Both need the
8891
- same clean state;
9126
+ own beyond the default branch (NO COMMITS OF ITS OWN below). Each needs a
9127
+ clean state, except a finished one whose changes are archived;
8892
9128
  a worktree is kept when it is the source checkout, bare, locked, holds
8893
9129
  another worktree (worktree remove refuses it too), in use,
8894
- dirty (untracked files count; pod install churn and
9130
+ dirty and not finished, or dirty and finished when its changes cannot be
9131
+ archived (ARCHIVED CHANGES below; untracked files count; pod install churn and
8895
9132
  watchman's untracked \`.watchman-cookie-*\` files alone do not), unpushed
8896
9133
  (commits no remote-tracking ref or other local branch reaches), or has
8897
9134
  initialized submodules. Without --worktrees, the report leaves out the
@@ -8922,14 +9159,17 @@ SWEEPING FINISHED WORKTREES
8922
9159
  an unexpired device lease. When the simulator or emulator list cannot be
8923
9160
  read, the worktree counts as in use. A device left booted keeps the
8924
9161
  worktree until \`stim stop\` in it, or \`gc --idle <duration>\`, shuts
8925
- the device down.
9162
+ the device down. A worktree that holds the running stim or the realpath of
9163
+ the \`stim\` on PATH is in use too, and \`stim worktree remove\` refuses it
9164
+ even with --force, as it refuses an installed stim-server service's checkout.
8926
9165
 
8927
9166
  RECENT ACTIVITY keeps a worktree gc would otherwise remove until
8928
9167
  gc.worktreeGraceMinutes (120 by default, \`guide settings\`) have passed
8929
9168
  since the latest of: a write to its git index, HEAD or HEAD reflog, a write
8930
9169
  to its Stim workspace state or log files, and, for a merged branch, the
8931
9170
  committer date of the commit that brought it into the default branch, or
8932
- the time its pull request was merged or closed. That
9171
+ the time its pull request was merged or closed, and, for a dirty finished
9172
+ worktree, the newest change to its uncommitted or untracked files. That
8933
9173
  window is when an agent that just merged runs \`stim stop\` and \`stim
8934
9174
  worktree remove\` itself. The reason is recent-activity, and the report
8935
9175
  and the JSON eligibleAt field say when it becomes removable. When that
@@ -8984,20 +9224,56 @@ SWEEPING FINISHED WORKTREES
8984
9224
 
8985
9225
  NO COMMITS OF ITS OWN: a worktree whose HEAD the default branch contains
8986
9226
  and whose branch has no commit made on it (fresh, cut from another
8987
- branch, or reset onto one; the MERGED rule above) holds nothing git would
8988
- lose, but looks the same as one just made by git worktree add. Plain gc keeps
8989
- it (reason not-merged, "no commits of its own beyond origin/<default>").
8990
- \`--worktrees\` removes it when idle: by its recorded use when there is
8991
- one, else by the RECENT ACTIVITY sources above being older than
8992
- --older-than days (reason recently-used while they are newer, and
8993
- last-use-unknown when they cannot be read). Automatic maintenance removes it
8994
- the same way in its daily age sweep, after maintenance.olderThanDays, only
8995
- with maintenance.removeIdleWorktrees (off by default), and at disk pressure
8996
- level 3 once that activity is older than
8997
- maintenance.pressureProtectMinutes and at least 5 minutes old, whatever
8998
- the setting; unreadable activity keeps it. Removal re-checks, under the removal
8999
- locks, that HEAD did not move and the worktree is still idle, and
9000
- \`stim worktree remove\` still refuses dirty or unpushed work.
9227
+ branch, reset onto one, or a detached HEAD; the MERGED rule above) holds
9228
+ nothing git would lose, but looks the same as one just made by git
9229
+ worktree add. gc --delete and the maintenance worktree check remove a clean
9230
+ one without an open pull request once the RECENT ACTIVITY sources above
9231
+ are older than gc.worktreeGraceMinutes and maintenance.protectRecentHours
9232
+ (2 hours by default), and never younger than 5 minutes (reason
9233
+ recent-activity until then, activity-unknown when they cannot be read). At
9234
+ disk pressure level 3 the window is maintenance.pressureProtectMinutes,
9235
+ still at least 5 minutes. Removal re-checks, under the removal locks, that
9236
+ HEAD did not move and the worktree is still idle, and \`stim worktree
9237
+ remove\` still refuses dirty or unpushed work. A process Stim does not
9238
+ track, such as a benchmark run from a detached build, does not keep it.
9239
+
9240
+ ARCHIVED CHANGES: a dirty worktree that is otherwise finished, idle past
9241
+ RECENT ACTIVITY and not in use is archived, then removed, by gc --delete
9242
+ and the maintenance worktree check. Under the removal locks Stim writes
9243
+ \`git diff --binary HEAD\` (staged and unstaged changes together) to
9244
+ tracked.patch, the untracked files git does not ignore to untracked.tar,
9245
+ HEAD's commits the default branch lacks to head.bundle,
9246
+ with a manifest.json naming the branch, HEAD, pull request and files, in
9247
+ $STIM_HOME/archive/worktrees/<repository>/<branch-or-dir>-<timestamp>/.
9248
+ Ignored files (node_modules, build output) are not saved. It verifies the
9249
+ patch with \`git apply --check -R\`, the tarball's file list, and that no
9250
+ archived file changed, again just before git removes the worktree; any
9251
+ failure keeps the worktree and deletes the archive. It also keeps a
9252
+ worktree whose archive.enabled is false, or false for a project registered
9253
+ inside it, with archive.maxAgeDays or
9254
+ archive.maxTotalGb 0, whose untracked files or patch exceed
9255
+ archive.maxTotalGb, whose archive would leave less free disk than
9256
+ budget.hardFloorDiskGb, whose staged version of a file differs from its
9257
+ working tree, or that holds an untracked nested repository; disk
9258
+ pressure level 3 never archives. Archives are 0700 and 0600 with no
9259
+ redaction, so an untracked secret such as a .env file that git does not
9260
+ ignore is saved; use archive.enabled false for a sensitive repository. A
9261
+ \`git status\` or editor refresh that rewrites the index restarts RECENT
9262
+ ACTIVITY. The removal line and the gc
9263
+ and maintenance logs name the archive and the restore commands; head.bundle
9264
+ exists only when the default branch lacks HEAD, and lets the restore work
9265
+ after the branch is deleted:
9266
+ git -C <repository> bundle unbundle <archive>/head.bundle (with a bundle)
9267
+ git -C <repository> worktree add --detach <path> <HEAD>
9268
+ git -C <path> apply <archive>/tracked.patch
9269
+ tar -xf <archive>/untracked.tar -C <path>
9270
+ Archive retention, which runs before and after each new worktree archive
9271
+ and after each removal that archives a workspace, deletes worktree archives
9272
+ older than archive.maxAgeDays. A new archive that would not fit beside the
9273
+ others in archive.maxTotalGb keeps its worktree instead, so the oldest are
9274
+ deleted for size only after that cap is lowered. An archive whose removal
9275
+ is still running holds an ownership claim and is never deleted. Plain \`stim worktree remove\` never archives; it
9276
+ refuses dirty work as before.
9001
9277
 
9002
9278
  PULL REQUESTS: with fresh state, gc runs one \`gh api graphql\` query per
9003
9279
  repository that asks for the 20 newest pull requests of each linked worktree's branch, as
@@ -9389,9 +9665,10 @@ THE ONE CASE GC WILL NOT REAP
9389
9665
  summary: "disk usage, workspace build outputs, logs and device recordings, AVD and build-log sizes, the data partition, trimming the shared caches",
9390
9666
  body: () => `DISK
9391
9667
  Logs, state, pidfiles and Xcode DerivedData are under the global workspace
9392
- directory, and \`worktree remove\` reclaims them. \`gc --delete\` clears the
9393
- build outputs of workspaces nobody is using (WORKSPACE BUILD OUTPUTS
9394
- below) and keeps the rest. Gradle retains its normal
9668
+ directory, and \`worktree remove\` reclaims them. \`gc --delete\` lightens
9669
+ every workspace that is not building and did not run a native command in
9670
+ the last 15 minutes, clearing its build outputs (WORKSPACE BUILD OUTPUTS
9671
+ below), and keeps the rest. Gradle retains its normal
9395
9672
  project build directories while sharing task outputs through its build cache.
9396
9673
 
9397
9674
  Android AVDs normally live under ~/.android/avd, and a booted owned AVD can
@@ -9442,18 +9719,35 @@ a changed value.
9442
9719
  WORKSPACE BUILD OUTPUTS
9443
9720
  Each workspace directory holds derived-data/, gradle-build/, android-cas/,
9444
9721
  cache-provider/, macos/build, the staged macos/<Product>.app and macos/staging-*
9445
- leftovers. \`gc\` reports them as one detected cache, "Workspace build
9446
- outputs", with a per-workspace size, last use and verdict. Plain
9447
- \`gc --delete\` clears them for every workspace not in use (see \`guide
9448
- cleanup gc\`), before anything else. \`--older-than <days>\` limits that to
9722
+ leftovers. The project's own android/app/build, android/build,
9723
+ android/.gradle and .build count with them when git ignores every file in
9724
+ the directory and tracks none; one that fails that check, or is not in a
9725
+ git checkout, stays, and \`gc --delete\` prints why when the check fails
9726
+ between the report and the deletion. ios/build stays: React Native's pod
9727
+ install writes podspec facades and codegen output there that only pod
9728
+ install recreates. \`gc\` reports them as one
9729
+ detected cache, "Workspace build outputs", with a per-workspace size, last
9730
+ use and verdict. Plain \`gc --delete\` lightens every workspace, before
9731
+ anything else, by clearing them, and prints the bytes freed per workspace.
9732
+ A running dev server, a booted or hosted simulator or emulator and a managed
9733
+ tunnel no longer keep them. They stay while the workspace is building (its
9734
+ native-run.lock is held, a live or unresolvable build lock or slot names
9735
+ it, or its build claim is live), while its macOS app or build runs or cannot
9736
+ be verified, and for 15 minutes after its last ios, android or macos run,
9737
+ cache hits included, as recorded in state.json's build records. Dev server
9738
+ starts, reloads and logs do not count as a native run. The shared build
9739
+ cache, the Xcode compilation cache, the Gradle caches and the Metro cache
9740
+ are never touched. \`--older-than <days>\` limits that to
9449
9741
  workspaces idle at least that long, and keeps one whose last use is
9450
9742
  unknown. \`--cache workspaces\` acts on them alone, except in the workspace
9451
9743
  directories that plain \`gc --delete\` removes whole (dead projects and
9452
9744
  orphaned directories); \`--cache all\` includes them. macos/ itself,
9453
9745
  macos/runtime.lock and its claim set, workspace.json, state.json, logs/,
9454
- other macOS files, locks and device records stay, as does the project's
9455
- own .build. The workspace keeps its devices and ports. A running, building,
9456
- unverified or hosted macOS app keeps its workspace untouched.
9746
+ other macOS files, locks and device records stay. The workspace keeps its
9747
+ devices, ports and running dev server. Automatic maintenance at disk
9748
+ pressure level 2 clears them by the same rule (DISK PRESSURE LEVELS); level
9749
+ 1 keeps its own rules. A running, building, unverified or hosted macOS app
9750
+ keeps its workspace untouched.
9457
9751
  stim gc --delete --cache workspaces --older-than 7
9458
9752
  Last use is the newest of the lastUsedAt that start, ios, android, reload
9459
9753
  and worktree warm record in state.json, lastBuild.startedAt,
@@ -9470,6 +9764,30 @@ WORKSPACE BUILD OUTPUTS
9470
9764
  build recompiles Swift. The next \`stim macos\` rebuilds its scratch from
9471
9765
  nothing (a full Swift build) and restages the app.
9472
9766
 
9767
+ WORKTREE DEPENDENCIES
9768
+ Plain \`gc --delete\` also lightens linked worktrees: it deletes the
9769
+ node_modules and ios/Pods at the root or a project root of a linked
9770
+ worktree that has a Stim workspace, never the source checkout. A worktree
9771
+ keeps them while it is in use (its dev server runs, a native run or build
9772
+ holds it), while it was used in the last 15 minutes (a git index, HEAD or
9773
+ reflog write, a Stim state or log write, or a write at the top of the
9774
+ directory), while git tracks a file in them, while a stim-server service
9775
+ or the stim on PATH runs from it, while a live process has its command
9776
+ line or working directory in it, and when the running processes cannot be
9777
+ listed (always on Windows). It also keeps them when \`stim worktree warm\`
9778
+ could not put the same thing back: warm would not copy that directory from
9779
+ the source checkout (it is missing there, excluded by .worktreeexclude or
9780
+ worktree.exclude, or inside a larger ignored directory such as a wholly
9781
+ ignored ios/), the worktree's lockfile (or ios/Podfile.lock for Pods)
9782
+ differs from the source checkout's, or the source checkout's own copy was
9783
+ not installed from its lockfile. gc records what it deleted in the
9784
+ workspace's lightened.json; the next \`stim start\`, \`stim ios\` or
9785
+ \`stim android\` there runs \`stim worktree warm\` first to restore it, and
9786
+ refuses with STIM_DEPS_FAILED, keeping the record, when those checks no
9787
+ longer pass, when warm fails, or when it leaves a directory missing. A
9788
+ deletion that stopped part-way is finished before the warm. Other commands, such as a test run, find the directory missing
9789
+ until then. .pnpm-store stays: it is all clones and frees nothing.
9790
+
9473
9791
  WORKSPACE LOGS
9474
9792
  \`gc\` reports the size of each workspace's logs/ (the workspaceLogs
9475
9793
  section of \`gc --json\`). metro.ndjson, client.ndjson and device.ndjson and
@@ -9529,6 +9847,12 @@ SHARED BUILD CACHES
9529
9847
  report-only because every Gradle build shares it. Stim reports its size
9530
9848
  but never prunes or empties it, including with --older-than or --cache all.
9531
9849
 
9850
+ A cache directory that \`gc --delete --older-than\` cannot list (EACCES,
9851
+ EPERM or ELOOP, for example) is a failed trim, not an empty one: the run
9852
+ prints "N entries in <dir> could not be read or removed", records the cache
9853
+ as failed and exits 1. Fix the directory's permissions and run it again. A
9854
+ directory that is missing or is not a directory is not a failure.
9855
+
9532
9856
  Trim rather than empty. Emptying costs the next build in every project the
9533
9857
  time the cache was saving.`
9534
9858
  }
@@ -9649,12 +9973,16 @@ KEYS STIM READS
9649
9973
  embedded JS, no Metro, cache keyed -release-sim, and
9650
9974
  a JS-bundle swap on cache hits. The \`--configuration\`
9651
9975
  flag overrides this per invocation. Unset means Debug.
9652
- ios.remote "proxy", "eas", or a named approved Mac from
9653
- remote.machines, with the same meaning as --remote.
9654
- "auto" places on an approved Mac when this machine is full or
9655
- busy. Unset runs here; "local" runs here even when a
9656
- lower layer says otherwise. A --device run ignores
9657
- it. See lifecycle hosted-ios.
9976
+ ios.remote where the simulator runs, with the same meaning as
9977
+ --remote: "local" here, even when a lower layer says
9978
+ otherwise; "auto" here, and on an approved Mac from
9979
+ remote.machines when this machine is full or busy;
9980
+ "remote" on the first approved Mac in remote.machines
9981
+ order with room, here only when none takes it;
9982
+ "remote-only" the same but never here; a named
9983
+ approved Mac exactly that Mac; "proxy" or "eas" a
9984
+ remote device backend. Unset runs here. A --device run
9985
+ ignores it. See lifecycle hosted-ios.
9658
9986
  ios.projectPath directory under the app holding a bare React Native
9659
9987
  app's Xcode project and Podfile; default "ios". "."
9660
9988
  is the app directory itself (RNTester's layout).
@@ -9875,14 +10203,13 @@ ${ANDROID_AVD_CONFIG_HELP.map((line) => ` ${line}`).joi
9875
10203
  password, handed to apksigner through its
9876
10204
  environment, never its command line. Unset means
9877
10205
  the debug keystore's fixed "android".
9878
- android.remote "proxy", "eas", or a named approved Mac in
9879
- remote.machines; "auto" places on an approved Mac when
9880
- this machine is full or busy. Unset runs here; "local"
9881
- runs here even when a lower layer says otherwise.
9882
- A --device run ignores it. See lifecycle
9883
- hosted-android.
9884
- remote.easFallback true lets "auto" (ios.remote, android.remote or
9885
- --remote auto) run the simulator or emulator on a
10206
+ android.remote where the emulator runs, with the same words as
10207
+ ios.remote: "local", "auto", "remote", "remote-only",
10208
+ a named approved Mac in remote.machines, "proxy" or
10209
+ "eas". Unset runs here. A --device run ignores it. See
10210
+ lifecycle hosted-android.
10211
+ remote.easFallback true lets "auto" or "remote" (ios.remote, android.remote
10212
+ or --remote) run the simulator or emulator on a
9886
10213
  billed EAS Simulator when this machine is at its
9887
10214
  concurrency.maxDevices cap (or has runs queued) and no
9888
10215
  remote.machines entry takes the run, instead of waiting
@@ -10016,6 +10343,16 @@ ${ANDROID_AVD_CONFIG_HELP.map((line) => ` ${line}`).joi
10016
10343
  compiled into Contents/Resources before signing
10017
10344
  macos.resources map of Contents/Resources destinations to source paths
10018
10345
  relative to Package.swift; see stim guide macos
10346
+ macos.remote where stim macos launches the app, with the words of
10347
+ ios.remote except "eas" and "proxy": "local" here;
10348
+ "auto" here unless this machine is busy, then an
10349
+ approved Mac in remote.machines; "remote" on the
10350
+ first approved Mac in order with room, here only when
10351
+ none takes it; "remote-only" the same but never here,
10352
+ which keeps the app from taking focus on this Mac; a
10353
+ named Mac exactly that Mac. The --remote flag
10354
+ overrides it. A recorded hosted app keeps its Mac
10355
+ until stim stop. Unset runs here. See stim guide macos.
10019
10356
  web.url the page \`stim web\` opens in the owned Chrome, an
10020
10357
  http:// or https:// URL. {port:<label>} becomes the
10021
10358
  workspace's named port (allocated like \`stim ports
@@ -10073,8 +10410,9 @@ ${ANDROID_AVD_CONFIG_HELP.map((line) => ` ${line}`).joi
10073
10410
  them from the environment or the machine layers.
10074
10411
 
10075
10412
  Each setting takes its documented type: string, array of strings, number,
10076
- boolean, or object. ios.remote and android.remote accept backend names or
10077
- tailnet machine names. metro.tunnel, web.viewport,
10413
+ boolean, or object. ios.remote, android.remote, macos.remote and remote.build
10414
+ accept placement words (local, auto, remote, remote-only) or tailnet machine
10415
+ names, and ios.remote and android.remote also the backend names. metro.tunnel, web.viewport,
10078
10416
  optimizations.android.compilerCache and optimizations.android.pch take only
10079
10417
  their listed choices. A value of the wrong
10080
10418
  type or outside those choices is refused by name on every command that resolves
@@ -10300,6 +10638,21 @@ simulator sessions on, by MagicDNS name with an optional serve port (default
10300
10638
  stim settings set remote.machines '["janics-mac-mini"]'
10301
10639
  stim doctor --fix
10302
10640
 
10641
+ The order of \`remote.machines\` is the priority order for every workload: the
10642
+ build, simulator, emulator and macOS app each go to the first listed machine that
10643
+ has approval, a free slot and a 5-minute load per core under that machine's own
10644
+ \`server.maxLoadPerCore\`, and the next one in order only when it does not. A
10645
+ machine at its caps, over its load limit, unreachable or lacking approval is
10646
+ skipped; load does not rank the machines that qualify, so the first one fills
10647
+ before the next is used. Put the fastest machine first and set the order with
10648
+ one command; \`stim doctor\` prints it as \`placement order\` and gives each
10649
+ machine a \`priority\` in its JSON:
10650
+
10651
+ stim settings set remote.machines '["fast-mini","slow-mini"]'
10652
+
10653
+ A host reports its own load limit in its device offer; a stim-server older than
10654
+ that does not, and this machine's \`server.maxLoadPerCore\` stands in for it.
10655
+
10303
10656
  Automatic membership is separate from approval. In Desktop Settings > Remote
10304
10657
  Machines, use Builds enabled and Simulators enabled for this machine or a
10305
10658
  configured remote. The equivalent machine settings list the excluded members;
@@ -10387,9 +10740,9 @@ An invalid remote.machines setting reports an error and preserves every saved cr
10387
10740
  replaced machine, run it, then re-add the name to request a new approval.
10388
10741
  A definite revoked or lapsed request can be requested again with --fix.
10389
10742
  An in-progress approval inspection reports busy rather than replacing its
10390
- pending token. \`stim macos --remote <machine>\` runs a macOS app on an
10391
- approved remote Mac (stim guide macos); \`ios\` and \`android\` do not yet
10392
- place sessions on these Macs. To view or control a hosted macOS app, a person
10743
+ pending token. \`stim macos --remote <placement>\` or \`macos.remote\` runs a macOS
10744
+ app on an approved remote Mac (stim guide macos), and \`ios.remote\` and
10745
+ \`android.remote\` place simulators and emulators on them. To view or control a hosted macOS app, a person
10393
10746
  on that Mac approves Screen & System Audio Recording and Device Control and Data
10394
10747
  Access (Accessibility on macOS 26 and earlier) for Stim Host, the app
10395
10748
  \`stim-server service install\` runs the server under.
@@ -10436,18 +10789,35 @@ builds, or the build messages change; their Stim builds can differ. A
10436
10789
  stim-server too old to report one needs the same Stim build as this machine.
10437
10790
 
10438
10791
  \`remote.build\` (machine scope, default auto) selects placement for
10439
- \`ios\`, \`android\` and \`macos\`. \`--remote-build <auto|local|name>\`
10792
+ \`ios\`, \`android\` and \`macos\`. \`--remote-build <local|auto|remote|remote-only|name>\`
10440
10793
  overrides STIM_REMOTE_BUILD, which overrides the setting. A blank environment
10441
- value is unset. Trimmed auto/local are case-insensitive. Machine names match
10794
+ value is unset. \`doctor\` flags an invalid value in the machine config or in
10795
+ STIM_REMOTE_BUILD before a build refuses on it. Trimmed placement words are case-insensitive. Machine names match
10442
10796
  configured names case-insensitively, with port 7443 when omitted; reports use
10443
10797
  the configured entry.
10444
10798
 
10445
- auto follows remote.buildMode, considering only enabled automatic pool members
10446
- local builds only on this machine for this invocation
10447
- name requires a matching entry in remote.machines, already paired and
10448
- approved for builds; ignores remote.buildMode and this machine's load/slot gating
10799
+ local builds only on this machine for this invocation
10800
+ auto (default) here while this machine has capacity, a remote
10801
+ machine from remote.machines when it does not; only enabled
10802
+ automatic pool members count
10803
+ remote on the first remote machine in remote.machines order that
10804
+ accepts the build, here only when none does
10805
+ remote-only the same, never here: when no remote machine accepts the build
10806
+ it refuses with STIM_OFFLOAD_REFUSED
10807
+ name requires a matching entry in remote.machines, already paired and
10808
+ approved for builds; ignores this machine's load/slot gating
10809
+
10810
+ \`remote.buildMode\` and STIM_REMOTE_BUILD_MODE were removed and are no longer read.
10811
+ \`doctor\` and \`stim settings\` report any left in a config file or the environment,
10812
+ \`stim settings set remote.buildMode\` refuses with the \`remote.build\` command that
10813
+ replaces it (auto stays auto, force becomes remote, off becomes local), and
10814
+ \`stim settings unset remote.buildMode\` removes the leftover key from every layer
10815
+ that has it (--scope limits it to one).
10449
10816
 
10450
10817
  A name never prompts for pairing, tries another remote machine or falls back here.
10818
+ remote-only refuses instead of building here for a build that cannot leave this
10819
+ machine (device, Release, an unknown simulator runtime, the build cache off), and
10820
+ when no pairing or approval exists.
10451
10821
  Any refusal or failure is STIM_OFFLOAD_REFUSED with the remote machine and reason.
10452
10822
  Device, Release/non-Debug, --remote, Android CAS compiler builds, builds with
10453
10823
  the build cache off, and an unknown simulator runtime cannot build there and
@@ -10474,26 +10844,29 @@ Copyable agent prompt:
10474
10844
  Run stim ios --remote-build janics-mac-mini. If it refuses, report the
10475
10845
  STIM_OFFLOAD_REFUSED reason and remedy; do not retry with another placement.
10476
10846
 
10477
- With remote.build auto, \`remote.buildMode\` decides where an iOS simulator Debug build or an Android
10478
- emulator debug build or a stim macos SwiftPM Debug build compiles:
10847
+ Which remote machine takes a build that leaves this machine follows the order of
10848
+ \`remote.machines\`: the first machine that accepts it, meaning it has a free slot
10849
+ (\`concurrency.maxBuilds\`), enough disk, a matching toolchain, and a load per core
10850
+ under its own \`server.maxLoadPerCore\`. A machine past its limit is skipped like a
10851
+ full one and the next in order is tried; the first machine fills to its caps before
10852
+ the next one gets a build. A machine that already holds the exact completed
10853
+ artifact goes ahead of the order, since that needs no build. With \`auto\` the
10854
+ build leaves this machine only when it is busy:
10479
10855
 
10480
10856
  auto (default) here while this machine has capacity: a free
10481
10857
  \`concurrency.maxBuilds\` slot (always, with no build limit) and a
10482
- load per core under \`server.maxLoadPerCore\`. Otherwise on a
10483
- remote machine that accepts the build and is expected to be faster:
10484
- while every slot here is busy, any remote machine that accepts; while only
10485
- the load is high, one whose load per core is lower than this
10486
- machine's. A machine too old to report its load counts only while every
10487
- slot here is busy.
10488
- force on a remote machine whenever one accepts it
10489
- off here when local remains enabled in the automatic build pool
10490
-
10491
- For React Native and Expo, among machines that can take a new build, a matching dependency installation
10492
- ranks before load. The worker checks the requested project, lockfile, install
10493
- arguments and the major of the Node the app pins, and verifies that its modules
10494
- and install marker remain present.
10495
- Older workers' dependency hints and mere checkout or build-folder presence do
10496
- not influence this ranking. Exact completed-artifact hits still come first.
10858
+ load per core under \`server.maxLoadPerCore\`. Otherwise on the first
10859
+ remote machine in order that accepts the build. A machine too old to
10860
+ report its load counts only while every slot here is busy.
10861
+ remote on the first remote machine in order that accepts the build, whatever
10862
+ load this machine is under; here only when none does
10863
+ remote-only as remote, but when none does the build refuses
10864
+
10865
+ A worker that holds a matching dependency installation does not rank ahead of an
10866
+ earlier machine in the order; it only saves the install on the machine that gets
10867
+ the build. The worker checks the requested project, lockfile, install arguments
10868
+ and the major of the Node the app pins, and verifies that its modules and install
10869
+ marker remain present.
10497
10870
 
10498
10871
  The worker installs dependencies with the lockfile nearest the app: the first
10499
10872
  pnpm-lock.yaml, yarn.lock or package-lock.json in its directory or above it in
@@ -10518,10 +10891,11 @@ Load per core is the 5-minute load average divided by the CPU count; a machine's
10518
10891
  native builds are its Stim runs in prebuild, pods or compile on that machine, not
10519
10892
  the ones it offloaded.
10520
10893
  \`server.maxLoadPerCore\` (default 2) is also the busy threshold for
10521
- automatic iOS and Android device placement. It is the load per core at which a machine
10522
- counts as saturated, both here and on a remote machine.
10894
+ automatic iOS, Android and macOS placement. It is the load per core at which a
10895
+ machine counts as saturated: each machine applies its own value, and a remote
10896
+ machine past it is skipped like a full one.
10523
10897
 
10524
- STIM_REMOTE_BUILD_MODE overrides it for one command. Device, Release and
10898
+ Device, Release and
10525
10899
  \`--remote eas|proxy\` builds, Android builds with the Apple Clang CAS compiler cache,
10526
10900
  and iOS/Android runs with the build cache off, always build here. Hosted iOS
10527
10901
  Debug builds can use a separate --remote-build, targeting the hosting Mac's
@@ -10534,7 +10908,7 @@ verified compile; remote output is not published under an unverified client key.
10534
10908
  An offloaded iOS/Android build first runs prebuild (and \`pod install\` for iOS) here,
10535
10909
  then asks every paired machine what it can build. It takes one whose Stim
10536
10910
  build matches this Mac and, for iOS and macOS, CPU matches the target simulator, with enough disk, that does not decline,
10537
- preferring the one that already holds this repository, then the least loaded,
10911
+ taking the first in remote.machines order that qualifies,
10538
10912
  and moves to the next one in that order when a machine that offered fails the
10539
10913
  sync or refuses to start the build.
10540
10914
  After a local and configured-provider miss, native Xcode simulator builds can
@@ -10566,7 +10940,7 @@ xcodebuild -downloadPlatform iOS -buildVersion <version>, or a pointer to the
10566
10940
  Xcode problem when xcode-select is the cause. A stim-server that reports no
10567
10941
  developerDir keeps the prose fix. It also needs an
10568
10942
  iPhone simulator on the target runtime. A missing or different global CocoaPods
10569
- keeps automatic placement from choosing the machine for a project that does not
10943
+ keeps auto, remote and remote-only placement from choosing the machine for a project that does not
10570
10944
  pin it, unless the machine already holds the exact artifact; the placement line
10571
10945
  says why and doctor shows the install command. A named --remote-build still
10572
10946
  sends the build, and a pod install that fails there reports "pod install failed"
@@ -10730,6 +11104,9 @@ be setup steps are supplied by Stim on the command lines it composes itself:
10730
11104
  SWIFT_OTHER_PREFIX_MAPPINGS on a Swift 6.4+ toolchain -- so no
10731
11105
  Podfile post_install block (Xcode 26+ only, and skipped when the
10732
11106
  project configured ccache, which defeats it)
11107
+ Debug builds for the iOS simulator also get
11108
+ DEBUG_INFORMATION_FORMAT=dwarf, which skips the dSYM bundle;
11109
+ Release and device builds keep the project's setting
10733
11110
  gradlew --build-cache -- so no org.gradle.caching=true in a committed
10734
11111
  gradle.properties. Debug builds add
10735
11112
  -PreactNativeArchitectures=<target ABI> when the owned
@@ -11188,7 +11565,7 @@ var macos_default = {
11188
11565
 
11189
11566
  If Stim is not installed globally, replace stim with npx stim.
11190
11567
 
11191
- ios --remote <machine> shares remote.machines and its separate device-host
11568
+ ios --remote <placement> shares remote.machines and its separate device-host
11192
11569
  approval. Read guide lifecycle hosted-ios for simulator placement and Metro.
11193
11570
 
11194
11571
  Run from the directory containing Package.swift. This prototype builds one
@@ -11292,7 +11669,7 @@ Apple Swift, xcrun and xcodebuild selections can be reproduced; custom launchers
11292
11669
  require the checkout and otherwise miss safely. SWIFT_EXEC and SWIFT_EXEC_MANIFEST
11293
11670
  compiler overrides are resolved and versioned separately and require the checkout.
11294
11671
  The SDK selected by SDKROOT or SWIFTPM_SDKROOT_macosx is checked by real path and build version. Explicit local placement,
11295
- remote build mode off, disabled pool members and remoteBuildCache
11672
+ disabled pool members and remoteBuildCache
11296
11673
  false skip peer lookup. Older workers keep their normal compilation path.
11297
11674
  --plan validates the Swift Package directory, macos settings, development plist
11298
11675
  and declared resources without building, signing, stopping or launching
@@ -11324,17 +11701,16 @@ line instead of when the app exits.
11324
11701
 
11325
11702
  BUILD OFFLOAD
11326
11703
 
11327
- --remote-build <auto|local|name> overrides STIM_REMOTE_BUILD and the
11704
+ --remote-build <local|auto|remote|remote-only|name> overrides STIM_REMOTE_BUILD and the
11328
11705
  machine setting remote.build (default auto). local builds here; a name
11329
- requires the matching configured and paired remote Mac, ignoring remote.buildMode
11330
- and this Mac's capacity. Any failure is STIM_OFFLOAD_REFUSED without a local xcodebuild, Gradle or
11706
+ requires the matching configured and paired remote Mac, ignoring this Mac's capacity. Any failure is STIM_OFFLOAD_REFUSED without a local xcodebuild, Gradle or
11331
11707
  SwiftPM compile or another machine. Invalid, unlisted or unpaired selections
11332
11708
  refuse before stopping the app or changing its build record. See stim guide errors STIM_OFFLOAD_REFUSED.
11333
11709
 
11334
- With remote.build auto, remote.buildMode places these SwiftPM Debug builds:
11335
- auto builds here while this Mac has capacity, force uses an approved build
11336
- machine when one accepts, and off builds here when local remains in the automatic
11337
- build pool. remote.buildPoolDisabled excludes members from automatic placement,
11710
+ remote.build places these SwiftPM Debug builds: auto builds here while this Mac has
11711
+ capacity, remote uses the first approved build machine in remote.machines order that
11712
+ accepts and otherwise builds here, remote-only never builds here and refuses when none
11713
+ accepts, and local builds here. remote.buildPoolDisabled excludes members from automatic placement,
11338
11714
  including its local fallback. Explicit local or named placement bypasses membership.
11339
11715
  Configure remote.machines
11340
11716
  and approve build access as described in stim guide settings. The worker needs
@@ -11356,8 +11732,8 @@ identity comes from the worker's toolchain. Any other combination keeps the matc
11356
11732
  The client validates the development plist and resource entries before asking a machine. It verifies
11357
11733
  the returned archive's sha256, bundle identifier, executable, declared resources
11358
11734
  and ad hoc signature
11359
- before replacing the owned bundle, then launches locally as usual. With auto, every offload
11360
- failure falls back to the local build, including force. Failed staging preserves
11735
+ before replacing the owned bundle, then launches locally as usual. With auto or remote, every offload
11736
+ failure falls back to the local build. Failed staging preserves
11361
11737
  the previous bundle. No failed artifact is promoted or cached. The build record
11362
11738
  is unchanged by invalid, unlisted or unpaired setup refusals. It
11363
11739
  carries buildMachine (selected) and builtOn (here or the machine, absent until
@@ -11519,19 +11895,40 @@ used.
11519
11895
 
11520
11896
  ON ANOTHER MAC
11521
11897
 
11522
- stim macos --remote <machine> builds the Debug app on this Mac and runs it on an
11898
+ stim macos --remote <machine> or macos.remote builds the Debug app on this Mac and runs it on an
11523
11899
  approved remote Mac over the tailnet, without SSH. The Mac must be listed
11524
11900
  in remote.machines and approved: stim doctor --fix makes a setup ticket, a
11525
11901
  person on that Mac runs stim-server setup with that ticket and expiry (guide
11526
11902
  settings), and stim doctor then pins that Mac's stim-server key. Stim goes ahead
11527
11903
  only with the stim-server that proves it holds that key.
11528
- A refusal or an unreachable host fails the command; it never launches here
11904
+ A refusal or an unreachable host fails the command; with a named Mac it never launches here
11529
11905
  instead.
11530
11906
 
11531
- The --remote value is a remote Mac name from remote.machines. macOS refuses
11532
- eas and proxy because it has neither backend. It also refuses auto until
11533
- automatic placement ships. These reserved names are case-insensitive and
11534
- trimmed; they refuse with STIM_BAD_ARG before state access or a connection.
11907
+ The --remote flag and the macos.remote setting take the placement words of
11908
+ ios.remote and a remote Mac name from remote.machines; the flag overrides the
11909
+ setting:
11910
+
11911
+ local launch here (the default when neither is set)
11912
+ auto launch here unless this Mac is busy (host memory pressure or load
11913
+ per core at or above server.maxLoadPerCore), then on the first
11914
+ approved Mac in remote.machines order that has room
11915
+ remote on the first approved Mac in remote.machines order that has room
11916
+ and a load per core under its own server.maxLoadPerCore, here only
11917
+ when none does
11918
+ remote-only the same, never here, so a macOS app never takes focus on this Mac:
11919
+ when every Mac that answered is only full or loaded it waits up to
11920
+ 600 seconds for one with room and then refuses with STIM_AT_CAPACITY;
11921
+ with no Mac that could ever take it (none configured, unreachable,
11922
+ declined for another reason) it refuses at once with
11923
+ STIM_HOSTING_REFUSED and the reason each Mac was skipped
11924
+ <machine> exactly that Mac, with no fallback
11925
+
11926
+ A placement word picks the Mac on a new launch and keeps the recorded one while an
11927
+ app runs there, until stim stop. The command prints a placement: line with the
11928
+ choice and the skipped Macs. macOS refuses eas and proxy because it has neither
11929
+ backend. Reserved names are case-insensitive and trimmed; they refuse with
11930
+ STIM_BAD_ARG before state access or a connection. The device pool switches
11931
+ (remote.devicePoolDisabled) also apply to automatic macOS placement.
11535
11932
 
11536
11933
  Logs: the host records the hosted app's stdout, stderr and exit like a local run.
11537
11934
  stim logs, including --errors, --json and --follow, asks the host over the same