stim 1.25.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 (182) hide show
  1. package/README.md +1 -1
  2. package/dist/{acquire-artifact-zA4S7RHE.mjs → acquire-artifact-BKN2Yuhg.mjs} +2 -2
  3. package/dist/{act-BbHS3oV8.mjs → act-ROviTF_t.mjs} +415 -118
  4. package/dist/{activity-BpjwrQXU.mjs → activity-Cd-ysFes.mjs} +6 -7
  5. package/dist/{agent-device-usage-output-Bnkerfod.mjs → agent-device-usage-output-C5nCKWp9.mjs} +5 -5
  6. package/dist/{android-CNZgRC-4.mjs → android--HBgpvpW.mjs} +5 -5
  7. package/dist/{android-BGwuK3Mc.mjs → android-Cd6w7_qw.mjs} +134 -57
  8. package/dist/{android-saBeB4lr.mjs → android-Deb9X3_Y.mjs} +37 -15
  9. package/dist/{android-Cjcj8MCw.mjs → android-DriNoAVy.mjs} +99 -47
  10. package/dist/{android-cas-BVqh2Zep.mjs → android-cas-D5e6ecUg.mjs} +4 -4
  11. package/dist/android-cas-compiler.mjs +1 -1
  12. package/dist/{android-project-CTYaETc0.d.mts → android-project-DUGMbq28.d.mts} +55 -1
  13. package/dist/api.d.mts +20 -7
  14. package/dist/api.mjs +42 -32
  15. package/dist/apk-signed-tail-BHQHx3Rm.mjs +142 -0
  16. package/dist/{artifact-Co8zixVx.mjs → artifact-0AGJYB9O.mjs} +28 -28
  17. package/dist/{artifact-DN8JNwqS.mjs → artifact-faemIZv5.mjs} +40 -30
  18. package/dist/{artifact-platform-CMRc0M_r.mjs → artifact-platform-BO2Uvk_A.mjs} +1 -1
  19. package/dist/{attempt-DW-mQqM7.mjs → attempt-ClyJlZPT.mjs} +4 -2
  20. package/dist/auto-placement-z-H5pJza.mjs +350 -0
  21. package/dist/{browser-web-CCgZsHmI.mjs → browser-web-slD8AvLp.mjs} +16 -17
  22. package/dist/{build-cache-CKCzxzxA.mjs → build-cache-gBJ-WKzA.mjs} +141 -37
  23. package/dist/{build-CAiw8Ezm.mjs → build-hS0QyFlQ.mjs} +38 -31
  24. package/dist/{build-plan-zCuHGlA8.mjs → build-plan-CBqYbzYM.mjs} +7 -6
  25. package/dist/{cache-manifest-BPXRONJz.mjs → cache-manifest-DEjqGGat.mjs} +1 -1
  26. package/dist/cache-manifest.mjs +1 -1
  27. package/dist/{budget-HyShrIpL.mjs → ccache-BWTze1Ls.mjs} +634 -1514
  28. package/dist/{cdp-BG3FJCCx.mjs → cdp-C3svvXiE.mjs} +1 -1
  29. package/dist/{chrome-BvV8ssI3.mjs → chrome-DyDWurWG.mjs} +1 -1
  30. package/dist/{cli-C4w607Pz.mjs → cli-ftXDLto1.mjs} +24 -23
  31. package/dist/cli.mjs +1 -1
  32. package/dist/{client-BQsmyp_-.mjs → client-xsdxrhWA.mjs} +77 -116
  33. package/dist/collector-run.d.mts +10 -9
  34. package/dist/collector-run.mjs +42 -27
  35. package/dist/{command-output-BBvS1H6v.mjs → command-output-Dj-WT4BP.mjs} +1 -1
  36. package/dist/{config-BZhXY15H.mjs → config-Cn94Jc0g.mjs} +343 -18
  37. package/dist/{launch-DscF1QfM.mjs → dependencies-BGokZ-Ng.mjs} +330 -279
  38. package/dist/{dependency-state-BwNCYG5Q.mjs → dependency-state-C3OtgG94.mjs} +14 -7
  39. package/dist/{detached-entry-KmieOKgN.mjs → detached-entry-BPlSaCwC.mjs} +1 -1
  40. package/dist/{dev-client-CHoXXZeg.mjs → dev-client-BMca82xo.mjs} +2 -2
  41. package/dist/{device-F5-4z9ef.mjs → device-BJm0irrq.mjs} +16 -16
  42. package/dist/{device-capacity-ZYaRALv5.mjs → device-capacity-6LP7QsaA.mjs} +103 -25
  43. package/dist/device-host-worker.mjs +19 -21
  44. package/dist/{device-ios-DkHBrmvH.mjs → device-ios-534c14fz.mjs} +288 -28
  45. package/dist/{device-lease-run-ogEeJ62K.mjs → device-lease-run-DwsGfjUN.mjs} +5 -5
  46. package/dist/{device-pool-BIg8MU8l.mjs → device-pool-BnQqvs6o.mjs} +3 -3
  47. package/dist/{device-B9FqTBYW.mjs → device-tM41-TUq.mjs} +25 -24
  48. package/dist/{doctor-A_Nr_fDe.mjs → doctor-Dbx1ZpD2.mjs} +1 -1
  49. package/dist/{doctor-k3P3PizH.mjs → doctor-DyqE9n1J.mjs} +28 -16
  50. package/dist/{doctor-Bm8TVpGn.mjs → doctor-F-3VUUyN.mjs} +128 -35
  51. package/dist/{doctor-fingerprint-zlujA1Gs.mjs → doctor-fingerprint-o7ZI9YLn.mjs} +9 -3
  52. package/dist/doctor-fingerprint.mjs +1 -1
  53. package/dist/{doctor-watchman-BovI1y3-.mjs → doctor-watchman-CxDbyrrt.mjs} +5 -5
  54. package/dist/{eas-build-CyDKNTBP.mjs → eas-build-CjNSWmjo.mjs} +4 -4
  55. package/dist/eas-fallback-9fqb46kW.mjs +87 -0
  56. package/dist/{eas-session-ledger-BMCA1gdO.mjs → eas-session-ledger-DrP4OuuM.mjs} +6 -6
  57. package/dist/{error-diagnostics-B1Mj_-7h.mjs → error-diagnostics-snej9k4j.mjs} +51 -25
  58. package/dist/{exec-VFRpu1iA.mjs → exec-aULwtNly.mjs} +57 -6
  59. package/dist/{gc-eaz403W6.mjs → gc-BRwAY3qT.mjs} +2 -2
  60. package/dist/{gc-B9jcnmIk.mjs → gc-CGZ1Yg88.mjs} +218 -72
  61. package/dist/{guide-DR0K0xW9.mjs → guide-_rfEh_zM.mjs} +839 -222
  62. package/dist/{hosted-android-Bvb2-ZxD.mjs → hosted-android-BMKzUNnY.mjs} +4 -4
  63. package/dist/{hosted-client-rsRqysJc.mjs → hosted-client-D7Pk_Oo1.mjs} +13 -9
  64. package/dist/{hosted-logs-CWKGYWNX.mjs → hosted-logs-B1gqQuva.mjs} +18 -12
  65. package/dist/hosted-logs-sync-Dg3qrZmV.mjs +195 -0
  66. package/dist/{hosted-macos-d0pv9Tth.mjs → hosted-macos-B9w4H8hc.mjs} +6 -6
  67. package/dist/{hosted-native-BxTizGU2.mjs → hosted-native-CdgaiuyP.mjs} +17 -17
  68. package/dist/{idle-shutdown-BIyfl0Zj.mjs → idle-shutdown-Cbvo5KEh.mjs} +21 -9
  69. package/dist/{ios-BRVM8b0P.mjs → ios-B5HlVMhQ.mjs} +138 -55
  70. package/dist/{ios-Dvt97HgQ.mjs → ios-Bk4mWbtS.mjs} +8 -8
  71. package/dist/{ios-ENC4sRPA.mjs → ios-CsRVik64.mjs} +7 -1
  72. package/dist/{ios-BQzvJlzw.mjs → ios-E26qSpkb.mjs} +94 -11
  73. package/dist/{ios-device-CKSvk0bu.mjs → ios-device-C_QkIf1Z.mjs} +3 -3
  74. package/dist/{ios-device-B6HK951C.mjs → ios-device-CiBA-er9.mjs} +1 -1
  75. package/dist/{ios-project-DCUDo06I.mjs → ios-project-D8Jcy_br.mjs} +22 -9
  76. package/dist/{ios-signing-4c_qXvQm.mjs → ios-signing-BJzvDPfU.mjs} +2 -2
  77. package/dist/{ios-state-CT9nV5SP.mjs → ios-state-BZ03NFgF.mjs} +1 -1
  78. package/dist/{js-swap-Z9HbC4cj.mjs → js-swap-Dg2a945i.mjs} +8 -7
  79. package/dist/{launch-D0lPvFFu.mjs → launch-CRyBeNsA.mjs} +53 -44
  80. package/dist/lightened-BCsdAGj_.mjs +171 -0
  81. package/dist/{logs-BsIRre8x.mjs → logs-CC_-ZzEE.mjs} +10 -174
  82. package/dist/{logs-Crs9y34Z.mjs → logs-aJQb-RDg.mjs} +112 -38
  83. package/dist/{macos-CcUlJPMu.mjs → macos-BGmTpnv9.mjs} +1 -1
  84. package/dist/{macos-D477nfzH.mjs → macos-DlCBhMw5.mjs} +73 -39
  85. package/dist/macos-run.mjs +1 -1
  86. package/dist/maintenance-run.mjs +30 -13
  87. package/dist/{metro-1pJgCX4U.mjs → metro-CvyD-xwe.mjs} +4 -4
  88. package/dist/{metro-gateway-Bp9gJQAN.mjs → metro-gateway-BnHdqMO_.mjs} +3 -4
  89. package/dist/{named-ports-BVbn8SvC.mjs → named-ports-BCyMWA6b.mjs} +3 -3
  90. package/dist/{native-android-BvMizJn1.mjs → native-android-DlpxCyOP.mjs} +17 -18
  91. package/dist/{native-gradle-artifact-inputs-BJscK4vN.mjs → native-gradle-artifact-inputs-C13qT7R_.mjs} +5 -5
  92. package/dist/{native-runtime-B9w_Qyz3.mjs → native-runtime-DqaIBO5w.mjs} +9 -9
  93. package/dist/{native-source-D-4Dh4JU.mjs → native-source-D1AzWiuP.mjs} +1 -1
  94. package/dist/{native-xcode-inputs-Cv4l-obn.mjs → native-xcode-inputs-B1W8SrxB.mjs} +3 -3
  95. package/dist/{native-xcode-ios-DGrg90hE.mjs → native-xcode-ios-QPP0Rm68.mjs} +17 -18
  96. package/dist/{ndjson-CgE_o3Dl.mjs → ndjson-Dy2W4vRa.mjs} +2 -2
  97. package/dist/offload-worker.d.mts +3 -1
  98. package/dist/offload-worker.mjs +34 -25
  99. package/dist/{operation-exec-CUZifp6Z.mjs → operation-exec-Dp4FuIGR.mjs} +1 -1
  100. package/dist/{ownership-jxxz-19m.mjs → ownership-Bx-0cku-.mjs} +3 -3
  101. package/dist/{page-CzEA_-5_.mjs → page-CQJlcj3x.mjs} +2 -2
  102. package/dist/{phone-reverses-CYb7RRAx.mjs → phone-reverses-C9yAaKEv.mjs} +18 -4
  103. package/dist/{placement-log-DjOXJRIH.mjs → placement-log-BXUbiZM6.mjs} +3 -3
  104. package/dist/{plan-placement-BqKgKHCi.mjs → plan-placement-UxnQjv5Z.mjs} +36 -10
  105. package/dist/{ports-DtS1Amb0.mjs → ports-uUrghqWN.mjs} +3 -3
  106. package/dist/prebuild-BDpToDgR.mjs +1246 -0
  107. package/dist/{project-BiC68mIp.mjs → project-DYj-HVx6.mjs} +17 -18
  108. package/dist/{pull-request-CbNlkFok.mjs → pull-request-BzLmzAIQ.mjs} +2 -2
  109. package/dist/pull-requests.mjs +1 -1
  110. package/dist/{react-native-android-DRYqoJc4.mjs → react-native-android-DDBccgJw.mjs} +56 -36
  111. package/dist/{react-native-build-CwZB-agu.mjs → react-native-build-DaK2IFyP.mjs} +9 -9
  112. package/dist/{react-native-doctor-BCrytK7h.mjs → react-native-doctor-B-Mw6DcR.mjs} +50 -9
  113. package/dist/{react-native-ios-Cr1buNFo.mjs → react-native-ios-DjYwUF9U.mjs} +45 -25
  114. package/dist/{recordings-BaoQ0t-J.mjs → recordings-C20iUZfp.mjs} +1 -1
  115. package/dist/{registration-CN6nlhcD.mjs → registration-BwCe64PH.mjs} +1 -1
  116. package/dist/{reload-Beftw2X3.mjs → reload-DJBeF_rC.mjs} +1 -1
  117. package/dist/{reload-FbSgMF8x.mjs → reload-fUdkH2_p.mjs} +16 -16
  118. package/dist/{remote-BuZj6AyU.mjs → remote-y6n99WMW.mjs} +22 -9
  119. package/dist/{result-CR2vk2cj.mjs → result-CTqBw2Te.mjs} +32 -21
  120. package/dist/{run-BtzE09Dl.mjs → run-DaCeNKRm.mjs} +3 -3
  121. package/dist/{selection-D5cxEl3t.mjs → selection-Bs8phwhN.mjs} +183 -62
  122. package/dist/{server-bare-CH9X0RLn.mjs → server-bare-BOh-iZEj.mjs} +2 -2
  123. package/dist/{server-command-CaBbDNU2.mjs → server-command-8CuA51Cv.mjs} +6 -6
  124. package/dist/{server-expo-Ck8EIidM.mjs → server-expo-ABQs2XFA.mjs} +4 -4
  125. package/dist/server-expo-Ba_Y21vB.mjs +2 -0
  126. package/dist/{settings-B5tCoq9N.mjs → settings-BDVI8Ivc.mjs} +331 -62
  127. package/dist/{settings-DzH9mGQZ.mjs → settings-CMHhQNwI.mjs} +24 -3
  128. package/dist/settings-payload-cm60Zhib.mjs +257 -0
  129. package/dist/settings-write-BdNRLlBn.mjs +185 -0
  130. package/dist/settings.schema.json +84 -39
  131. package/dist/{simslim-BjxszUAb.mjs → simslim-DCQwzoPy.mjs} +4 -3
  132. package/dist/{slot-launch-QeuJMlnJ.mjs → slot-launch-0l7RqSpr.mjs} +6 -6
  133. package/dist/{stage-D8xlRU2D.mjs → stage-F4cAfJGi.mjs} +2 -2
  134. package/dist/{start-C1JLjkNw.mjs → start-DcqnF9p8.mjs} +1 -1
  135. package/dist/{start-YMpNwLhh.mjs → start-DrPwER_2.mjs} +27 -23
  136. package/dist/{state-DMRNP7-4.mjs → state-BX1y5syV.mjs} +4 -5
  137. package/dist/{state-DPaM3u5K.mjs → state-CZxLyetD.mjs} +2 -3
  138. package/dist/{stats-CP7STzZw.mjs → stats-GxuCbles.mjs} +5 -5
  139. package/dist/{stats-report-n2U5plhk.mjs → stats-report-B86Cv7Uc.mjs} +1 -1
  140. package/dist/{status-Fect2w9R.mjs → status-7ULDEJ1e.mjs} +1 -1
  141. package/dist/{status-CRLiJ-O_.mjs → status-BfnK9pUd.mjs} +25 -26
  142. package/dist/{status-cNw6i0_t.mjs → status-Bj7ZYvvc.mjs} +1 -1
  143. package/dist/{status-watch-Bc2J4TZn.mjs → status-watch-BfabQn03.mjs} +5 -5
  144. package/dist/status-worker.mjs +1 -1
  145. package/dist/{stim-desktop-DS0mzR8U.mjs → stim-desktop-CbWo21kb.mjs} +2 -2
  146. package/dist/{stop-COP6PQqw.mjs → stop-CH79S3IZ.mjs} +5 -6
  147. package/dist/{stop-Db9Kh-UA.mjs → stop-D-EAr25B.mjs} +25 -22
  148. package/dist/{stop-Br7LKxBE.mjs → stop-x1Yu3He4.mjs} +2 -2
  149. package/dist/supervisor-run.d.mts +1 -0
  150. package/dist/supervisor-run.mjs +17 -18
  151. package/dist/{support-qKcbgmI5.mjs → support-Be0UeVZU.mjs} +10 -10
  152. package/dist/{support-Cxj9CmII.mjs → support-CNg_YsOk.mjs} +6 -6
  153. package/dist/{swiftpm-artifact-inputs-iY9fsmOc.mjs → swiftpm-artifact-inputs-uPqYvGBv.mjs} +4 -4
  154. package/dist/{swiftpm-macos-Bp_sn9P4.mjs → swiftpm-macos-Ch4YjY_A.mjs} +11 -12
  155. package/dist/toolchain-CbCG6C5Z.mjs +2413 -0
  156. package/dist/toolchain-JYAOquPg.mjs +2 -0
  157. package/dist/{trigger-CBMe2YLH.mjs → trigger-DMou-X7j.mjs} +3 -3
  158. package/dist/trigger-qp2ji5wE.mjs +2 -0
  159. package/dist/{warm-progress-BV3ZPmcY.mjs → warm-progress-C0RsbngN.mjs} +731 -728
  160. package/dist/{web-B4Yjyt1G.mjs → web-Cq_AQ4DK.mjs} +9 -10
  161. package/dist/web-Je6abcpz.mjs +2 -0
  162. package/dist/web-run.mjs +9 -9
  163. package/dist/windows-report.mjs +1 -1
  164. package/dist/{workspace-state-CPSp3mav.mjs → workspace-state-BjqdjnJR.mjs} +46 -8
  165. package/dist/{workspaces-C9w4aeXF.mjs → workspaces-DFbj07jp.mjs} +1801 -76
  166. package/dist/{worktree-CclFIe1Z.mjs → worktree-COAJ3-OD.mjs} +934 -511
  167. package/dist/{worktree-Ct9Gk3KL.mjs → worktree-JGUd_53L.mjs} +1 -1
  168. package/package.json +8 -8
  169. package/dist/ccache-h1FKhZeg.mjs +0 -143
  170. package/dist/created-devices-D2Aca-xu.mjs +0 -52
  171. package/dist/eas-fallback-gaZi-dJL.mjs +0 -381
  172. package/dist/hosted-ios-RtVcJH3u.mjs +0 -15
  173. package/dist/native-run-D5OJsGwA.mjs +0 -276
  174. package/dist/paths-DQtTgsBf.mjs +0 -42
  175. package/dist/prebuild-JUaqOLpc.mjs +0 -501
  176. package/dist/server-expo-C16Zf2k7.mjs +0 -2
  177. package/dist/settings-write-n0zwfohx.mjs +0 -360
  178. package/dist/toolchain-DB1aeqxO.mjs +0 -2
  179. package/dist/toolchain-VzLJzZ7O.mjs +0 -908
  180. package/dist/trigger-BLsHgAGR.mjs +0 -2
  181. package/dist/web-DS4UDWvf.mjs +0 -2
  182. package/dist/xcode-CiIIDnuD.mjs +0 -910
@@ -1,29 +1,30 @@
1
- import { l as WorkspaceProcessCleanupError, n as isTimeoutError, t as getExecutor } from "./exec-VFRpu1iA.mjs";
2
- import { g as workspaceStateFile$1, m as workspaceLogsDir$1 } from "./paths-DQtTgsBf.mjs";
3
- import { F as isOnMountedVolume, G as projectDeviceSlots, I as listMountedVolumes, L as measuredDirectorySizeAsync, N as directorySizeAsync, P as formatBytes, _ as loadConfig$1, p as getProject } from "./config-BZhXY15H.mjs";
4
- import { o as formatLongDuration, u as plural } from "./command-output-BBvS1H6v.mjs";
5
- import { c as gitQuiet, l as gitQuietAsync, n as runOperationFileQuiet, r as childEnvironment } from "./operation-exec-CUZifp6Z.mjs";
6
- import { r as readWorkspaceState$1, u as workspaceLastUsed } from "./workspace-state-CPSp3mav.mjs";
7
- import { Bt as unpushedCommitsAsync, Ct as hasUncommittedWorkAsync, D as maintenanceKeepSetting, Dt as listWorktreesAsync, Et as listWorktrees, Gt as removeTemporaryEntryAsync, It as resolveFullRefAsync, J as resolveProjectSettings, Lt as resolveSourceCheckoutAsync, Y as resolveProjectSettingsAsync, _t as dirtyPathsAsync, xt as hasPopulatedSubmodulesAsync, zt as sourceCheckoutOf } from "./settings-B5tCoq9N.mjs";
8
- import { i as findProjectRoot$1 } from "./project-BiC68mIp.mjs";
9
- import { F as listAvdsAsync, G as ownedAvdSerialResolverAsync, P as listAvds, V as ownedAvdDirectory, yt as readParked } from "./android-Cjcj8MCw.mjs";
10
- import { a as unregister, n as readManifest } from "./cache-manifest-BPXRONJz.mjs";
11
- import { A as workspaceBuildInProgress, Cr as discoverCaches, Ct as deleteParkedSims, Dt as deviceSweepIsScoped, E as collectOwnedDeviceActivityAsync, Q as emptyCaches, St as deleteParkedAvds, T as collectOwnedDeviceActivity, br as phase, bt as collectParkedAvds, d as trimmableCaches, f as stopOwnedMetro, hr as memorySweepIsScoped, ht as waitForGradleExit, i as enforceBudget, it as planCacheEmptying, jt as findStaleProjectDevices, lt as collectGradleVersions, o as machineNumber, r as cacheLabel, u as resolveBudget, ur as collectMemoryReport, ut as deleteGradleVersion, vt as DEVICE_LIST_TIMEOUT_MS, wr as pruneCache, wt as deleteProjectDevices, xt as collectParkedSims, yr as stopIdleGradleDaemons } from "./budget-HyShrIpL.mjs";
1
+ import { l as WorkspaceProcessCleanupError, n as isTimeoutError, t as getExecutor } from "./exec-aULwtNly.mjs";
2
+ import { j as workspaceStateFile$1, k as workspaceLogsDir$1, r as readWorkspaceState$1, u as workspaceLastUsed } from "./workspace-state-BjqdjnJR.mjs";
3
+ import { F as isOnMountedVolume, G as projectDeviceSlots, I as listMountedVolumes, L as measuredDirectorySizeAsync, N as directorySizeAsync, P as formatBytes, _ as loadConfig$1, p as getProject } from "./config-Cn94Jc0g.mjs";
4
+ import { o as formatLongDuration, u as plural } from "./command-output-Dj-WT4BP.mjs";
5
+ import { c as gitQuiet, l as gitQuietAsync, n as runOperationFileQuiet, r as childEnvironment } from "./operation-exec-Dp4FuIGR.mjs";
6
+ import { At as listWorktreesAsync, Bt as resolveSourceCheckoutAsync, Ct as hasPopulatedSubmodulesAsync, Ht as sourceCheckoutOf, O as maintenanceKeepSetting, Tt as hasUncommittedWorkAsync, Ut as unpushedCommitsAsync, X as resolveProjectSettings, Z as resolveProjectSettingsAsync, h as deviceIdleShutdownMinutesSetting, kt as listWorktrees, tn as removeTemporaryEntryAsync, yt as dirtyPathsAsync, zt as resolveFullRefAsync } from "./settings-BDVI8Ivc.mjs";
7
+ import { i as findProjectRoot$1 } from "./project-DYj-HVx6.mjs";
8
+ import { F as listAvdsAsync, G as ownedAvdSerialResolverAsync, P as listAvds, V as ownedAvdDirectory, yt as readParked } from "./android-DriNoAVy.mjs";
9
+ import { A as diskPressureLevel, G as planCacheEmptying, N as lowestFree, P as protectWindowMs, X as collectGradleVersions, Z as deleteGradleVersion, Zn as collectMemoryReport, _ as resolveBudget, at as DEVICE_LIST_TIMEOUT_MS, ct as collectParkedSims, d as enforceBudget, dr as pruneCache, dt as deleteProjectDevices, j as levelFloors, lt as deleteParkedAvds, mt as deviceSweepIsScoped, nr as memorySweepIsScoped, nt as waitForGradleExit, or as stopIdleGradleDaemons, p as machineNumber, sr as phase, st as collectParkedAvds, u as cacheLabel, ur as discoverCaches, ut as deleteParkedSims, v as trimmableCaches, vt as findStaleProjectDevices, y as stopOwnedMetro, z as emptyCaches } from "./ccache-BWTze1Ls.mjs";
10
+ import { a as unregister, n as readManifest } from "./cache-manifest-DEjqGGat.mjs";
11
+ import { Dt as readConcurrency, L as workspaceBuildInProgress, M as collectOwnedDeviceActivityAsync, St as GC_OUTPUT, Tt as eachLimited, _ as withIdleWorkspace, a as collectOrphanedWorkspaces, c as isInsideWorkspaces, h as ARTIFACT_DIRS, i as clearWorkspaceOutputs, j as collectOwnedDeviceActivity, l as listWorkspaceDirs, m as workspaceDirRemoved, o as collectWorkspaceOutputs, p as workspaceBuildReasons, s as deleteOrphanedWorkspaces, t as LIGHTEN_AFTER_MS, u as recentNativeUseReason, v as workspaceInUse, wt as takeGcResults, y as workspaceInUseAsync } from "./workspaces-DFbj07jp.mjs";
12
12
  import { t as canonicalPath } from "./paths-BsJMoPP2.mjs";
13
- import { B as GC_OUTPUT, H as takeGcResults, a as collectWorkspaceOutputs, c as listWorkspaceDirs, d as workspaceDirRemoved, h as workspaceInUseAsync, i as collectOrphanedWorkspaces, m as workspaceInUse, o as deleteOrphanedWorkspaces, p as withIdleWorkspace, r as clearWorkspaceOutputs, s as isInsideWorkspaces } from "./workspaces-C9w4aeXF.mjs";
14
- import { N as listBuildLocks, b as leaseIsExpired, g as workspaceActivity, x as listLeaseFiles } from "./activity-BpjwrQXU.mjs";
15
- import { d as listAllIosSims, f as listAllIosSimsAsync } from "./ios-BQzvJlzw.mjs";
13
+ import { N as listBuildLocks, _ as workspaceDeviceLastUseAt, b as leaseIsExpired, g as workspaceActivity, x as listLeaseFiles } from "./activity-Cd-ysFes.mjs";
14
+ import { d as listAllIosSims, f as listAllIosSimsAsync } from "./ios-E26qSpkb.mjs";
16
15
  import { s as metroStoreRoot } from "./errors-DHm_U5gv.mjs";
17
16
  import { t as resolveMaintenanceSettings } from "./settings-Djl-mpjX.mjs";
18
- import { A as measurePressureAsync, D as measureDisk, E as cacheBlocked, I as lowestFree, L as protectWindowMs, N as diskPressureLevel, O as measureDiskAsync, P as levelFloors, a as matchWorktreeEntry, c as reclaimKeys, i as excludeWatchmanCookies, k as measurePressure, m as removeWorktreeTarget, o as nestedWorktrees, r as excludePodChurn, s as porcelainPath } from "./worktree-CclFIe1Z.mjs";
19
- import { c as FETCH_FRESH_MS, d as localDefaultBranchAsync, l as fetchDefaultBranchAsync, o as recordPullRequestLookup, p as mergeStateAsync, u as lastFetchedAtAsync } from "./warm-progress-BV3ZPmcY.mjs";
20
- import { n as describePullRequest, r as endedPullRequest, s as pullRequestLookups } from "./pull-request-CbNlkFok.mjs";
17
+ import { D as stimCliEntries, E as serviceRunningFrom, N as worktreeArchiveLimits, O as stimCliUseOf, S as measurePressureAsync, T as uncommittedActivityOf, a as matchWorktreeEntry, b as measureDiskAsync, c as reclaimKeys, i as excludeWatchmanCookies, m as removeWorktreeTarget, o as nestedWorktrees, r as excludePodChurn, s as porcelainPath, v as cacheBlocked, x as measurePressure, y as measureDisk } from "./worktree-COAJ3-OD.mjs";
18
+ import { n as describePullRequest, r as endedPullRequest, s as pullRequestLookups } from "./pull-request-BzLmzAIQ.mjs";
19
+ import { a as lastFetchedAtAsync, c as mergeStateAsync, f as recordPullRequestLookup, i as fetchDefaultBranchAsync, o as localDefaultBranchAsync, r as FETCH_FRESH_MS } from "./warm-progress-C0RsbngN.mjs";
20
+ import { a as restoreProblem, r as recordLightened, t as finishLightened } from "./lightened-BCsdAGj_.mjs";
21
+ import { i as writeIdleShutdownEvent, n as dueIdleDevices, r as shutDownIdleDevices } from "./idle-shutdown-Cbvo5KEh.mjs";
21
22
  import { readClaimSet } from "@stim-cli/core/ownership-claim";
22
- import { existsSync, lstatSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
23
+ import { existsSync, lstatSync, readdirSync, readlinkSync, realpathSync, rmSync, statSync } from "node:fs";
23
24
  import { tmpdir } from "node:os";
24
25
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
25
26
  import { buildCacheRoot, configDir, metroCacheRoot } from "@stim-cli/core";
26
- import { LAST_BUILD_KEYS, MAINTENANCE_LOG_NAME, loadConfig, readBuildHistory, readMaintenanceState, readPullRequestCache, settingDefinition, sharedCompilationCache } from "@stim-cli/core/state";
27
+ import { LAST_BUILD_KEYS, MAINTENANCE_LOG_NAME, lastNativeUseFrom, loadConfig, readBuildHistory, readMaintenanceState, readPullRequestCache, settingDefinition, sharedCompilationCache } from "@stim-cli/core/state";
27
28
  import { isAbsolute as isAbsolute$1, join as join$1 } from "path";
28
29
  import chalk from "chalk";
29
30
  import { existsSync as existsSync$1, readdirSync as readdirSync$1, statSync as statSync$1 } from "fs";
@@ -34,6 +35,7 @@ const DIRTY_PATHS_SHOWN = 3;
34
35
  const DAY_MS = 864e5;
35
36
  const MINUTE_MS = 6e4;
36
37
  const GRACE_SETTING = "gc.worktreeGraceMinutes";
38
+ const PROTECT_SETTING = "maintenance.protectRecentHours";
37
39
  const NO_OWN_COMMITS_PRESSURE_FLOOR_MS = 5 * MINUTE_MS;
38
40
  const PULL_REQUEST_NOT_CHECKED = "not checked since the last maintenance worktree check";
39
41
  const WORKTREE_CHECK_SETTING = "maintenance.worktreeCheckMinutes";
@@ -78,6 +80,18 @@ function noOwnCommitsActivityBlocker(activity, idleMs, now) {
78
80
  eligibleAt
79
81
  };
80
82
  }
83
+ function dirtyLines(porcelain) {
84
+ return excludePodChurn(excludeWatchmanCookies(porcelain).lines).lines;
85
+ }
86
+ function finished(facts) {
87
+ return Boolean(facts.merge?.merged || endedPullRequest(facts.pullRequest));
88
+ }
89
+ function openPullRequest(lookup) {
90
+ return Boolean(lookup && "pullRequest" in lookup && lookup.pullRequest?.state === "open");
91
+ }
92
+ function newerActivity(a, b) {
93
+ return a && b ? b.at > a.at ? b : a : a ?? b;
94
+ }
81
95
  function hasNoOwnCommits(merge) {
82
96
  return merge?.merged === false && merge.noOwnCommits === true;
83
97
  }
@@ -116,11 +130,16 @@ function removalBlocker(facts, olderThan, now, noOwnCommitsIdleMs) {
116
130
  if (facts.nested?.length) return skip("nested-worktree", `contains the worktree ${facts.nested.join(", ")}`);
117
131
  if (facts.inUse.length) return skip("in-use", `in use: ${facts.inUse.join("; ")}`);
118
132
  if (facts.porcelain === null) return skip("status-unreadable", "git status could not be read");
119
- const dirty = excludePodChurn(excludeWatchmanCookies(facts.porcelain).lines).lines;
133
+ const dirty = dirtyLines(facts.porcelain);
120
134
  if (dirty.length) {
121
135
  const paths = dirty.slice(0, DIRTY_PATHS_SHOWN).flatMap((line) => porcelainPath(line) ?? []);
122
136
  const more = dirty.length > DIRTY_PATHS_SHOWN ? ", ..." : "";
123
- return skip("dirty", `dirty: ${plural(dirty.length, "uncommitted or untracked file")} (${paths.join(", ")}${more})`);
137
+ const kept = skip("dirty", `dirty: ${plural(dirty.length, "uncommitted or untracked file")} (${paths.join(", ")}${more})`);
138
+ if (!facts.archiveDirty) return kept;
139
+ return removalBlocker({
140
+ ...facts,
141
+ porcelain: []
142
+ }, olderThan, now, noOwnCommitsIdleMs) === null && finished(facts) ? null : kept;
124
143
  }
125
144
  if (facts.unpushed === null) return skip("unpushed-unchecked", "unpushed commits could not be checked");
126
145
  const merged = facts.merge?.merged ? facts.merge : null;
@@ -131,17 +150,9 @@ function removalBlocker(facts, olderThan, now, noOwnCommitsIdleMs) {
131
150
  if (merged || ended) return null;
132
151
  const notMerged = facts.merge && !facts.merge.merged ? facts.merge : null;
133
152
  const details = [notMerged?.detail, pullRequestNote(facts.pullRequest)].filter(Boolean);
134
- const noOwnCommits = hasNoOwnCommits(notMerged);
135
- if (olderThan === null) {
136
- if (noOwnCommits && noOwnCommitsIdleMs !== null) return noOwnCommitsActivityBlocker(facts.activity, noOwnCommitsIdleMs, now);
137
- return skip(notMerged?.unknown ? "merge-unknown" : "not-merged", details.join("; ") || "not merged");
138
- }
153
+ if (hasNoOwnCommits(notMerged) && !openPullRequest(facts.pullRequest) && noOwnCommitsIdleMs !== null) return noOwnCommitsActivityBlocker(facts.activity, noOwnCommitsIdleMs, now);
154
+ if (olderThan === null) return skip(notMerged?.unknown ? "merge-unknown" : "not-merged", details.join("; ") || "not merged");
139
155
  const also = details.map((detail) => `; ${detail}`).join("");
140
- if (facts.idleDays === null && noOwnCommits && facts.activity) {
141
- const days = activityIdleDays(facts.activity, now);
142
- if (days < olderThan) return skip("recently-used", `recently active: ${facts.activity.basis} ${days}d ago${also}`);
143
- return null;
144
- }
145
156
  if (facts.idleDays === null) return skip("last-use-unknown", `recently used: its last use is unknown${also}`);
146
157
  if (facts.idleDays < olderThan) return skip("recently-used", `recently used ${facts.idleDays}d ago${also}`);
147
158
  return null;
@@ -153,11 +164,16 @@ function removalBlocker(facts, olderThan, now, noOwnCommitsIdleMs) {
153
164
  */
154
165
  function worktreeRemovalReason(candidate, sweep) {
155
166
  const { merge, activity } = candidate;
156
- if (merge?.merged) return `merged into ${merge.into}`;
167
+ const archived = candidate.dirty ? `, ${plural(candidate.dirty, "uncommitted file")} archived first` : "";
168
+ if (merge?.merged) return `merged into ${merge.into}${archived}`;
157
169
  const ended = endedPullRequest(candidate.pullRequest);
158
- if (ended) return describePullRequest(ended);
170
+ if (ended) return `${describePullRequest(ended)}${archived}`;
159
171
  if (merge && hasNoOwnCommits(merge)) {
160
- if (sweep.noOwnCommitsIdleMs !== null && activity) return `${merge.detail}, inactive for ${formatLongDuration(Math.max(0, sweep.now - activity.at))} under disk pressure`;
172
+ if (sweep.noOwnCommitsIdleMs !== null && activity) {
173
+ const age = Math.max(0, sweep.now - activity.at);
174
+ if (sweep.underPressure) return `${merge.detail}, inactive for ${formatLongDuration(age)} under disk pressure`;
175
+ return age >= DAY_MS ? `${merge.detail}, idle ${activityIdleDays(activity, sweep.now)}d` : `${merge.detail}, inactive for ${formatLongDuration(age)}`;
176
+ }
161
177
  const days = candidate.idleDays ?? (activity ? activityIdleDays(activity, sweep.now) : null);
162
178
  return days === null ? merge.detail : `${merge.detail}, idle ${days}d`;
163
179
  }
@@ -343,6 +359,10 @@ function activityFromGitDir(gitDir, keys) {
343
359
  const newest = newestMtime(paths);
344
360
  return newest === "unreadable" ? null : newest;
345
361
  }
362
+ function protectRecentMs(operation) {
363
+ const { value, error } = machineNumber(PROTECT_SETTING, loadConfig$1(), operation?.env ?? process.env);
364
+ return (error ? Number(settingDefinition(PROTECT_SETTING)?.default) : value ?? 0) * 60 * MINUTE_MS;
365
+ }
346
366
  function graceMsSetting(operation) {
347
367
  const { value, error } = machineNumber(GRACE_SETTING, loadConfig$1(), operation?.env ?? process.env);
348
368
  if (error) {
@@ -448,7 +468,7 @@ async function checkMergeStates(pending, idle, grace, noOwnCommitsIdleMs, refres
448
468
  operation?.signal.throwIfAborted();
449
469
  const target = refresh ? await fetchDefaultBranchAsync(repo, grace.now, execution) : await localDefaultBranchAsync(repo, execution);
450
470
  const fetch = refresh ? null : await staleFetch(repo, grace.now, freshMs, execution);
451
- for (const { candidate, facts } of entries) {
471
+ await eachLimited(entries, readConcurrency(operation), async ({ candidate, facts }) => {
452
472
  operation?.signal.throwIfAborted();
453
473
  const merge = "error" in target ? {
454
474
  merged: false,
@@ -466,26 +486,29 @@ async function checkMergeStates(pending, idle, grace, noOwnCommitsIdleMs, refres
466
486
  skipped: verdict ? note ? `${verdict.text}; ${note}` : verdict.text : null,
467
487
  eligibleAt: verdict?.eligibleAt ?? null
468
488
  });
469
- }
489
+ });
470
490
  }
471
491
  }
472
492
  /**
473
- * Classifies each linked worktree of a registered project or its repository. A merged one is removable; with `idle`, so is one unused for
474
- * `olderThan` days; with `removesNoOwnCommits`, so is one with no commits of its own beyond the default branch whose
475
- * last activity is older than the grace period, and never younger than 5 minutes. Device use is read last, in one pass against one simulator list and one AVD resolver, so
493
+ * Classifies each linked worktree of a registered project or its repository. A merged one is removable, a dirty one
494
+ * after archiving its uncommitted work when archives are on; with `idle`, so is one unused for `olderThan` days. One
495
+ * with no commits of its own beyond the default branch is removable once its last activity is older than the grace
496
+ * period and `maintenance.protectRecentHours`, or, `underPressure`, than the grace period alone; never younger than 5
497
+ * minutes. Device use is read last, in one pass against one simulator list and one AVD resolver, so
476
498
  * the resolver's time budget covers only device reads. Pull requests are asked once per repository, sequentially
477
499
  * with an operation and concurrently otherwise. Merge state is checked only where it decides the verdict, after one fetch per repository.
478
500
  * Without `refresh`, it runs no fetch and no `gh`: merge state comes from the existing remote-tracking ref and pull
479
501
  * requests from the cache `status --watch` writes, and a kept verdict that fresher state could change says so.
480
502
  */
481
- async function collectWorktreeSweep({ idle, olderThan, now, graceMs, removesNoOwnCommits = false, refresh = true, operation }) {
503
+ async function collectWorktreeSweep({ idle, olderThan, now, graceMs, underPressure = false, refresh = true, operation }) {
482
504
  const days = idle ? olderThan ?? DEFAULT_WORKTREE_IDLE_DAYS : null;
483
505
  const grace = {
484
506
  ms: graceMs ?? graceMsSetting(operation),
485
507
  now
486
508
  };
487
509
  const freshMs = refresh ? 0 : reportFreshMs(operation);
488
- const noOwnCommitsIdleMs = removesNoOwnCommits ? Math.max(grace.ms, NO_OWN_COMMITS_PRESSURE_FLOOR_MS) : null;
510
+ const noOwnCommitsIdleMs = Math.max(grace.ms, underPressure ? 0 : protectRecentMs(operation), NO_OWN_COMMITS_PRESSURE_FLOOR_MS);
511
+ const cli = stimCliEntries(operation?.env);
489
512
  const groups = /* @__PURE__ */ new Map();
490
513
  operation?.signal.throwIfAborted();
491
514
  const read = {
@@ -496,9 +519,13 @@ async function collectWorktreeSweep({ idle, olderThan, now, graceMs, removesNoOw
496
519
  const outside = [];
497
520
  const candidates = await candidateRootsAsync(operation);
498
521
  if (candidates.length) phase("worktrees", `checking ${plural(candidates.length, "project")} for merged or idle worktrees`, operation);
499
- for (const root of candidates) {
522
+ const matched = [];
523
+ await eachLimited([...candidates.entries()], readConcurrency(operation), async ([index, root]) => {
500
524
  operation?.signal.throwIfAborted();
501
- const entry = matchWorktreeEntry(await listWorktreesAsync(root, read), root);
525
+ matched[index] = matchWorktreeEntry(await listWorktreesAsync(root, read), root);
526
+ });
527
+ for (const [index, root] of candidates.entries()) {
528
+ const entry = matched[index];
502
529
  if (!entry) {
503
530
  outside.push({
504
531
  path: root,
@@ -517,31 +544,38 @@ async function collectWorktreeSweep({ idle, olderThan, now, graceMs, removesNoOw
517
544
  groups.set(entry.path, [...groups.get(entry.path) ?? [], root]);
518
545
  }
519
546
  const inspected = [];
520
- for (const [path, roots] of groups) {
547
+ await eachLimited([...[...groups].entries()], readConcurrency(operation), async ([index, [path, roots]]) => {
521
548
  operation?.signal.throwIfAborted();
522
549
  const { entries, ...source } = await resolveSourceCheckoutAsync(path, read);
523
550
  const entry = matchWorktreeEntry(entries, path);
524
551
  const keys = [.../* @__PURE__ */ new Set([...roots, ...reclaimKeys(path)])];
525
552
  const idleDays = idleDaysOf(keys, now);
526
553
  const linked = !("refusal" in source) && entry !== null && source.path !== entry.path;
527
- const activity = linked && (grace.ms > 0 || days !== null || removesNoOwnCommits) ? await worktreeActivityOfAsync(path, keys, operation) : null;
528
- const gitAnswered = linked ? await unlessTimedOut(() => hasUncommittedWorkAsync(path, read), null) : null;
554
+ const porcelain = await porcelainOf(path, linked ? await unlessTimedOut(() => hasUncommittedWorkAsync(path, read), null) : null, operation);
555
+ const dirty = linked ? dirtyLines(porcelain).length : 0;
556
+ const archiveDirty = dirty > 0 && !underPressure && !("refusal" in worktreeArchiveLimits(path, operation?.env));
557
+ let activity = linked ? await worktreeActivityOfAsync(path, keys, operation) : null;
558
+ if (activity && archiveDirty) {
559
+ const edited = await uncommittedActivityOf(path, operation);
560
+ activity = edited === "unreadable" ? null : newerActivity(activity, edited);
561
+ }
529
562
  const head = linked ? await resolveFullRefAsync(path, "HEAD", read) : null;
530
563
  const facts = {
531
564
  source: "refusal" in source ? source : linked ? "linked" : "source",
532
565
  bare: Boolean(entry?.bare),
533
566
  locked: Boolean(entry?.locked),
534
567
  nested: entry ? nestedWorktrees(entries, entry.path) : [],
535
- porcelain: await porcelainOf(path, gitAnswered, operation),
568
+ porcelain,
536
569
  unpushed: linked ? await unlessTimedOut(() => unpushedCommitsAsync(path, read), null) : null,
537
570
  submodules: linked ? await unlessTimedOut(() => hasPopulatedSubmodulesAsync(path, read), null) : false,
538
- inUse: [],
571
+ inUse: linked ? stimCliUseOf(path, canonicalPath, cli) : [],
539
572
  idleDays,
540
573
  merge: null,
541
574
  pullRequest: null,
542
- activity
575
+ activity,
576
+ archiveDirty
543
577
  };
544
- inspected.push({
578
+ inspected[index] = {
545
579
  candidate: {
546
580
  path,
547
581
  keys,
@@ -552,13 +586,14 @@ async function collectWorktreeSweep({ idle, olderThan, now, graceMs, removesNoOw
552
586
  skipCode: null,
553
587
  skipped: null,
554
588
  eligibleAt: null,
555
- activity
589
+ activity,
590
+ ...archiveDirty ? { dirty } : {}
556
591
  },
557
592
  facts,
558
593
  repo: "refusal" in source ? null : source.path,
559
594
  branch: head && entry?.branch ? entry.branch : null
560
- });
561
- }
595
+ };
596
+ });
562
597
  const linked = inspected.filter(({ facts }) => facts.source === "linked");
563
598
  for (const { candidate, facts } of linked) for (const key of candidate.keys) facts.inUse.push(...await workspaceInUseAsync(key, { managedLocks: true }, operation?.signal, operation));
564
599
  const devices = await reportDeviceReads(linked.flatMap(({ candidate }) => candidate.keys), operation);
@@ -575,7 +610,7 @@ async function collectWorktreeSweep({ idle, olderThan, now, graceMs, removesNoOw
575
610
  skipped: verdict?.text ?? null,
576
611
  eligibleAt: verdict?.eligibleAt ?? null
577
612
  });
578
- if (verdict && MERGE_DECIDES.has(verdict.code) && repo !== null) pending.push({
613
+ if (verdict && (MERGE_DECIDES.has(verdict.code) || verdict.code === "dirty" && facts.archiveDirty) && repo !== null) pending.push({
579
614
  candidate,
580
615
  facts,
581
616
  repo
@@ -592,6 +627,7 @@ async function collectWorktreeSweep({ idle, olderThan, now, graceMs, removesNoOw
592
627
  graceMs: grace.ms,
593
628
  now,
594
629
  noOwnCommitsIdleMs,
630
+ underPressure,
595
631
  worktrees: listed
596
632
  };
597
633
  }
@@ -659,16 +695,8 @@ function cachedPullRequest(cached, branch, head, now, freshMs) {
659
695
  } };
660
696
  }
661
697
  async function noOwnCommitsRecheck(sweep, path, keys, now, operation) {
662
- if (!sweep.idle) {
663
- if (sweep.noOwnCommitsIdleMs === null) return "it has no commits of its own and is not idle";
664
- return noOwnCommitsActivityBlocker(await worktreeActivityOfAsync(path, keys, operation), sweep.noOwnCommitsIdleMs, now)?.text ?? null;
665
- }
666
- const idleDays = idleDaysOf(keys, now);
667
- if (idleDays !== null) return idleDays < sweep.idle.olderThan ? `used ${idleDays}d ago since gc checked it` : null;
668
- const activity = await worktreeActivityOfAsync(path, keys, operation);
669
- if (!activity) return "its last activity could not be read";
670
- const days = activityIdleDays(activity, now);
671
- return days < sweep.idle.olderThan ? `active ${days}d ago since gc checked it: ${activity.basis}` : null;
698
+ if (sweep.noOwnCommitsIdleMs === null) return "it has no commits of its own and is not idle";
699
+ return noOwnCommitsActivityBlocker(await worktreeActivityOfAsync(path, keys, operation), sweep.noOwnCommitsIdleMs, now)?.text ?? null;
672
700
  }
673
701
  async function removeWorktrees(sweep, { now = Date.now(), operation } = {}, output = GC_OUTPUT) {
674
702
  let failures = 0;
@@ -680,7 +708,11 @@ async function removeWorktrees(sweep, { now = Date.now(), operation } = {}, outp
680
708
  const keys = [.../* @__PURE__ */ new Set([...candidate.keys, ...lockedKeys])];
681
709
  const unlocked = keys.filter((key) => !lockedKeys.includes(key));
682
710
  const devices = await reportDeviceReads(keys, operation);
683
- const reasons = [...await inUseOf(lockedKeys, { managedLocks: false }, devices, operation?.signal, operation), ...await inUseOf(unlocked, { managedLocks: true }, devices, operation?.signal, operation)].map((r) => `in use: ${r}`);
711
+ const reasons = [
712
+ ...stimCliUseOf(candidate.path, canonicalPath, stimCliEntries(operation?.env)),
713
+ ...await inUseOf(lockedKeys, { managedLocks: false }, devices, operation?.signal, operation),
714
+ ...await inUseOf(unlocked, { managedLocks: true }, devices, operation?.signal, operation)
715
+ ].map((r) => `in use: ${r}`);
684
716
  const mergedHead = candidate.merge?.merged ? candidate.merge.head : endedPullRequest(candidate.pullRequest) ? candidate.head : null;
685
717
  if (mergedHead) {
686
718
  if (await resolveFullRefAsync(candidate.path, "HEAD", {
@@ -701,10 +733,12 @@ async function removeWorktrees(sweep, { now = Date.now(), operation } = {}, outp
701
733
  if (idleDays === null || !sweep.idle || idleDays < sweep.idle.olderThan) reasons.push(`used ${idleDays === null ? "at an unknown time" : `${idleDays}d ago`} since gc checked it`);
702
734
  }
703
735
  if (sweep.graceMs > 0) {
736
+ const activity = await worktreeActivityOfAsync(candidate.path, keys, operation);
737
+ const edited = candidate.dirty && activity ? await uncommittedActivityOf(candidate.path, operation) : null;
704
738
  const recent = graceBlocker({
705
739
  merge: candidate.merge,
706
740
  pullRequest: candidate.pullRequest,
707
- activity: await worktreeActivityOfAsync(candidate.path, keys, operation)
741
+ activity: edited === "unreadable" ? null : newerActivity(activity, edited)
708
742
  }, {
709
743
  ms: sweep.graceMs,
710
744
  now: Date.now()
@@ -716,6 +750,7 @@ async function removeWorktrees(sweep, { now = Date.now(), operation } = {}, outp
716
750
  };
717
751
  let removed = false;
718
752
  let error = null;
753
+ const archived = { archive: null };
719
754
  try {
720
755
  const ended = endedPullRequest(candidate.pullRequest);
721
756
  const mergedHead = candidate.merge?.merged && candidate.merge.coversUnpushed ? candidate.merge.head : ended?.state === "merged" ? candidate.head ?? void 0 : void 0;
@@ -726,7 +761,17 @@ async function removeWorktrees(sweep, { now = Date.now(), operation } = {}, outp
726
761
  mergedHead,
727
762
  expectedHead,
728
763
  output,
729
- operation
764
+ operation,
765
+ ...candidate.dirty ? { archiveDirty: {
766
+ pullRequest: ended ? {
767
+ number: ended.number,
768
+ url: ended.url,
769
+ state: ended.state
770
+ } : null,
771
+ onArchived: (saved) => {
772
+ archived.archive = saved;
773
+ }
774
+ } } : {}
730
775
  });
731
776
  } catch (thrown) {
732
777
  if (thrown instanceof WorkspaceProcessCleanupError || thrown instanceof Error && "reportCleanup" in thrown) throw thrown;
@@ -735,8 +780,16 @@ async function removeWorktrees(sweep, { now = Date.now(), operation } = {}, outp
735
780
  output.error(chalk.red(`Could not remove ${candidate.path}: ${error}`));
736
781
  }
737
782
  if (removed) {
738
- output.log(chalk.green(`Removed the worktree ${candidate.path} (${worktreeRemovalReason(candidate, sweep)})`));
739
- output.record("worktree", "done", candidate.path, { detail: worktreeRemovalReason(candidate, sweep) });
783
+ const saved = archived.archive;
784
+ const reason = saved ? `${worktreeRemovalReason({
785
+ ...candidate,
786
+ dirty: 0
787
+ }, sweep)}; archived ${plural(saved.tracked.length + saved.untracked.length, "uncommitted file")} to ${saved.dir}; restore: ${saved.restore.join(" && ")}` : worktreeRemovalReason({
788
+ ...candidate,
789
+ dirty: 0
790
+ }, sweep);
791
+ output.log(chalk.green(`Removed the worktree ${candidate.path} (${reason})`));
792
+ output.record("worktree", "done", candidate.path, { detail: reason });
740
793
  } else if (kept.length) {
741
794
  output.log(chalk.yellow(`Kept the worktree ${candidate.path}: ${kept.join("; ")}`));
742
795
  output.record("worktree", "kept", candidate.path, { detail: kept.join("; ") });
@@ -1213,6 +1266,44 @@ function finishedWorkspaces(sweep, now) {
1213
1266
  }));
1214
1267
  });
1215
1268
  }
1269
+ /** Each workspace's effective devices.idleShutdownMinutes, or 0 when it cannot be resolved. */
1270
+ function idleShutdownMinutes(root) {
1271
+ try {
1272
+ return deviceIdleShutdownMinutesSetting(resolveProjectSettings(root).settings);
1273
+ } catch {
1274
+ return 0;
1275
+ }
1276
+ }
1277
+ /**
1278
+ * Plans shutting down each booted owned simulator and emulator idle for its workspace's devices.idleShutdownMinutes,
1279
+ * whether or not a supervisor runs there. The devices are shut down, never deleted.
1280
+ */
1281
+ function planIdleShutdown({ protectedRoot, now, env = process.env }) {
1282
+ const plan = {
1283
+ actions: [],
1284
+ blocked: [],
1285
+ skips: []
1286
+ };
1287
+ if (deviceSweepIsScoped(env)) return plan;
1288
+ const minutes = new Map(Object.entries(loadConfig$1()?.projects ?? {}).flatMap(([root, project]) => {
1289
+ const value = !projectDeviceSlots(project).some(({ platforms }) => platforms.ios?.owned === true || platforms.android?.owned === true) || canonicalPath(root) === protectedRoot ? 0 : idleShutdownMinutes(root);
1290
+ return value > 0 ? [[root, value]] : [];
1291
+ }));
1292
+ if (minutes.size === 0) return plan;
1293
+ for (const { device, idleForMs } of dueIdleDevices(void 0, Math.min(...minutes.values()) * 6e4, now)) {
1294
+ const own = minutes.get(device.project);
1295
+ if (own === void 0 || idleForMs < own * 6e4 || now - workspaceDeviceLastUseAt(device.project) < own * 6e4) continue;
1296
+ const root = canonicalPath(device.project);
1297
+ plan.actions.push({
1298
+ kind: "would-shutdown-device",
1299
+ target: device.id,
1300
+ workspace: root,
1301
+ bytes: 0,
1302
+ reason: `${device.kind === "ios" ? "simulator" : "emulator"} ${device.name} idle ${formatLongDuration(idleForMs)}, over devices.idleShutdownMinutes ${own}`
1303
+ });
1304
+ }
1305
+ return plan;
1306
+ }
1216
1307
  //#endregion
1217
1308
  //#region src/maintenance/sweep.ts
1218
1309
  async function planSweep({ settings, sizes, protectedRoot, pinned, now, devices = true, operation }) {
@@ -1220,13 +1311,13 @@ async function planSweep({ settings, sizes, protectedRoot, pinned, now, devices
1220
1311
  const skips = [];
1221
1312
  const blocked = [];
1222
1313
  const days = settings.olderThanDays;
1223
- const outputs = await collectWorkspaceOutputs({
1314
+ const outputs = days > 0 ? await collectWorkspaceOutputs({
1224
1315
  olderThan: days,
1225
1316
  now,
1226
1317
  measure: false,
1227
1318
  signal: operation?.signal,
1228
1319
  operation
1229
- });
1320
+ }) : { workspaces: [] };
1230
1321
  for (const entry of outputs.workspaces) {
1231
1322
  const root = entry.projectRoot === null ? null : canonicalPath(entry.projectRoot);
1232
1323
  if (root === null || !entry.willClear || root === protectedRoot) continue;
@@ -1249,7 +1340,7 @@ async function planSweep({ settings, sizes, protectedRoot, pinned, now, devices
1249
1340
  check: "sweep"
1250
1341
  });
1251
1342
  }
1252
- for (const cache of trimmableCaches(operation?.env, operation?.cwd).filter((candidate) => !isInsideWorkspaces(candidate.dir))) actions.push({
1343
+ for (const cache of days > 0 ? trimmableCaches(operation?.env, operation?.cwd).filter((candidate) => !isInsideWorkspaces(candidate.dir)) : []) actions.push({
1253
1344
  kind: "would-trim-cache",
1254
1345
  target: cacheLabel(cache),
1255
1346
  dir: cache.dir,
@@ -1298,6 +1389,19 @@ async function planSweep({ settings, sizes, protectedRoot, pinned, now, devices
1298
1389
  actions.push(...reaped.actions);
1299
1390
  skips.push(...reaped.skips);
1300
1391
  }
1392
+ if (devices && days > 0 && !deviceSweepIsScoped(operation?.env ?? process.env)) for (const kind of ["ios", "android"]) for (const record of readParked(kind)) {
1393
+ const parkedDays = Math.floor((now - Date.parse(record.parkedAt)) / 864e5);
1394
+ if (!(parkedDays >= days)) continue;
1395
+ const label = `parked ${kind === "ios" ? "simulator" : "emulator"} ${record.name}`;
1396
+ actions.push({
1397
+ kind: "would-delete-parked-device",
1398
+ target: kind === "ios" ? record.udid : record.name,
1399
+ bytes: sizes.find((size) => size.category === "owned-devices" && !size.workspace && size.name === label)?.bytes ?? 0,
1400
+ olderThanDays: days,
1401
+ reason: `${label} unused for ${parkedDays} days, older than ${days} days (age sweep)`,
1402
+ check: "sweep"
1403
+ });
1404
+ }
1301
1405
  const mounted = listMountedVolumes();
1302
1406
  for (const cache of readManifest().caches) {
1303
1407
  if (!isAbsolute(cache.dir) || existsSync(cache.dir) || !isOnMountedVolume(cache.dir, mounted)) continue;
@@ -1316,12 +1420,12 @@ async function planSweep({ settings, sizes, protectedRoot, pinned, now, devices
1316
1420
  skips
1317
1421
  };
1318
1422
  }
1319
- async function planWorktrees({ protectedRoot, pinned, now, level, graceMs, sizes, protectRecentMs = 0, refresh, operation }) {
1423
+ async function planWorktrees({ protectedRoot, pinned, now, level, graceMs, sizes, protectRecentMs = 0, refresh, idleDays = null, operation }) {
1320
1424
  const sweep = await collectWorktreeSweep({
1321
- idle: false,
1322
- olderThan: null,
1425
+ idle: idleDays !== null,
1426
+ olderThan: idleDays,
1323
1427
  now,
1324
- removesNoOwnCommits: level === 3,
1428
+ underPressure: level === 3,
1325
1429
  operation,
1326
1430
  ...graceMs === void 0 ? {} : { graceMs },
1327
1431
  ...refresh === void 0 ? {} : { refresh }
@@ -1346,7 +1450,7 @@ async function planWorktrees({ protectedRoot, pinned, now, level, graceMs, sizes
1346
1450
  target: worktree.path,
1347
1451
  bytes: 0,
1348
1452
  reason: worktreeRemovalReason(worktree, sweep),
1349
- check: "worktree",
1453
+ check: level === void 0 && idleDays !== null && !worktree.merge?.merged && !endedPullRequest(worktree.pullRequest) ? "sweep" : "worktree",
1350
1454
  ...level ? { level } : {}
1351
1455
  });
1352
1456
  }
@@ -1377,18 +1481,118 @@ async function planWorktrees({ protectedRoot, pinned, now, level, graceMs, sizes
1377
1481
  };
1378
1482
  }
1379
1483
  //#endregion
1484
+ //#region src/workspace/running-from.ts
1485
+ const LIST_TIMEOUT_MS = 1e4;
1486
+ function parsePs(text) {
1487
+ return text.split("\n").flatMap((row) => {
1488
+ const match = /^\s*(\d+)\s+(.*)$/.exec(row);
1489
+ return match ? [{
1490
+ pid: Number(match[1]),
1491
+ line: match[2] ?? ""
1492
+ }] : [];
1493
+ });
1494
+ }
1495
+ /** `lsof -Fpn` output: a `p<pid>` line, then the `n<path>` of its working directory. */
1496
+ function parseLsof(text) {
1497
+ const cwds = [];
1498
+ let pid = 0;
1499
+ for (const row of text.split("\n")) if (row.startsWith("p")) pid = Number(row.slice(1));
1500
+ else if (row.startsWith("n") && pid) cwds.push({
1501
+ pid,
1502
+ path: row.slice(1)
1503
+ });
1504
+ return cwds;
1505
+ }
1506
+ function procCwds() {
1507
+ try {
1508
+ return readdirSync("/proc").filter((name) => /^\d+$/.test(name)).flatMap((name) => {
1509
+ try {
1510
+ return [{
1511
+ pid: Number(name),
1512
+ path: readlinkSync(join("/proc", name, "cwd"))
1513
+ }];
1514
+ } catch {
1515
+ return [];
1516
+ }
1517
+ });
1518
+ } catch {
1519
+ return null;
1520
+ }
1521
+ }
1522
+ function listsSelf(list) {
1523
+ return list.some(({ pid }) => pid === process.pid);
1524
+ }
1525
+ function runningFrom(ps, cwds) {
1526
+ if (ps === null || cwds === null) return null;
1527
+ const commands = parsePs(ps);
1528
+ return listsSelf(commands) && listsSelf(cwds) ? {
1529
+ commands,
1530
+ cwds
1531
+ } : null;
1532
+ }
1533
+ const PS_ARGS = [
1534
+ "-axww",
1535
+ "-o",
1536
+ "pid=,args="
1537
+ ];
1538
+ const LSOF_ARGS = [
1539
+ "-a",
1540
+ "-d",
1541
+ "cwd",
1542
+ "-Fpn",
1543
+ "-w"
1544
+ ];
1545
+ function readRunningProcesses(platform = process.platform) {
1546
+ if (platform !== "darwin" && platform !== "linux") return null;
1547
+ const exec = getExecutor();
1548
+ const ps = exec.runFileQuiet("ps", PS_ARGS, { timeoutMs: LIST_TIMEOUT_MS });
1549
+ if (platform === "linux") return runningFrom(ps, procCwds());
1550
+ const lsof = exec.runFileQuiet("lsof", LSOF_ARGS, { timeoutMs: LIST_TIMEOUT_MS });
1551
+ return runningFrom(ps, lsof === null ? null : parseLsof(lsof));
1552
+ }
1553
+ async function readRunningProcessesAsync(signal, platform = process.platform) {
1554
+ if (platform !== "darwin" && platform !== "linux") return null;
1555
+ const exec = getExecutor();
1556
+ const ps = await exec.runFileQuietAsync("ps", PS_ARGS, {
1557
+ timeoutMs: LIST_TIMEOUT_MS,
1558
+ signal
1559
+ });
1560
+ if (platform === "linux") return runningFrom(ps, procCwds());
1561
+ const lsof = await exec.runFileQuietAsync("lsof", LSOF_ARGS, {
1562
+ timeoutMs: LIST_TIMEOUT_MS,
1563
+ signal
1564
+ });
1565
+ return runningFrom(ps, lsof === null ? null : parseLsof(lsof));
1566
+ }
1567
+ function stimOnPath() {
1568
+ const found = getExecutor().findExecutable("stim");
1569
+ if (!found) return null;
1570
+ try {
1571
+ return realpathSync(found);
1572
+ } catch {
1573
+ return null;
1574
+ }
1575
+ }
1576
+ /**
1577
+ * Why something installed in or running from `folder` still needs it, or null: an installed stim-server service, the
1578
+ * `stim` on PATH, or a live process whose command line or working directory is inside it.
1579
+ */
1580
+ function runningFromReason(folder, running) {
1581
+ const root = canonicalPath(folder);
1582
+ const inside = (path) => path === root || path.startsWith(`${root}${sep}`);
1583
+ const service = serviceRunningFrom(root, canonicalPath);
1584
+ if (service) return `the stim-server service ${service.label} runs from it`;
1585
+ const stim = stimOnPath();
1586
+ if (stim && inside(stim)) return `the stim on PATH runs from it (${stim})`;
1587
+ if (!running) return "the running processes cannot be listed to prove nothing runs from it";
1588
+ const command = running.commands.find(({ line }) => line.includes(`${root}${sep}`));
1589
+ if (command) return `process ${command.pid} runs from it`;
1590
+ const cwd = running.cwds.find(({ path }) => inside(canonicalPath(path)));
1591
+ if (cwd) return `process ${cwd.pid} works in it`;
1592
+ return null;
1593
+ }
1594
+ //#endregion
1380
1595
  //#region src/maintenance/artifacts.ts
1381
- /** Directories that hold only what a build or an install regenerates, relative to an app or package root. */
1382
- const ARTIFACT_DIRS = {
1383
- build: [
1384
- "ios/build",
1385
- "android/app/build",
1386
- "android/build",
1387
- "android/.gradle",
1388
- ".build"
1389
- ],
1390
- dependencies: ["node_modules", "ios/Pods"]
1391
- };
1392
1596
  const PACKAGE_PARENTS = ["apps", "packages"];
1393
1597
  const SCAN_DEPTH = {
1394
1598
  build: 3,
@@ -1402,6 +1606,14 @@ function isDirectory(path) {
1402
1606
  return false;
1403
1607
  }
1404
1608
  }
1609
+ function pathExists(path) {
1610
+ try {
1611
+ lstatSync(path);
1612
+ return true;
1613
+ } catch {
1614
+ return false;
1615
+ }
1616
+ }
1405
1617
  function artifactWorktrees() {
1406
1618
  const found = /* @__PURE__ */ new Map();
1407
1619
  for (const root of candidateRoots()) {
@@ -1487,8 +1699,8 @@ async function newestWriteAsync(dir, maxDepth, signal) {
1487
1699
  };
1488
1700
  return await walk(dir, 0) ? newest : null;
1489
1701
  }
1490
- function keptReason(candidate, kind, { now, windowMs, pinned, locked }, reads) {
1491
- const guarded = artifactGuard(candidate, kind, pinned, locked);
1702
+ function keptReason(candidate, kind, { now, windowMs, pinned, locked, running }, reads) {
1703
+ const guarded = artifactGuard(candidate, kind, pinned, locked, running);
1492
1704
  if (guarded) return guarded;
1493
1705
  const { worktree, path, keys } = candidate;
1494
1706
  const tracked = reads ? reads.tracked : gitQuiet(worktree, [
@@ -1498,15 +1710,22 @@ function keptReason(candidate, kind, { now, windowMs, pinned, locked }, reads) {
1498
1710
  ]);
1499
1711
  if (tracked === null) return "git cannot tell whether it holds tracked files";
1500
1712
  if (tracked.trim()) return "it holds files git tracks";
1713
+ if (kind === "build") return buildNativeUseReason(keys, now);
1501
1714
  const activity = reads ? reads.activity : worktreeActivityOf(worktree, keys);
1502
1715
  const written = reads ? reads.written : newestWrite(path, SCAN_DEPTH[kind]);
1503
1716
  if (!activity || written === null) return "its last activity cannot be read";
1504
1717
  if (now - Math.max(activity.at, written) < windowMs) return `used within the last ${Math.round(windowMs / 6e4)} minutes`;
1505
1718
  return null;
1506
1719
  }
1507
- function artifactGuard(candidate, kind, pinned, locked) {
1720
+ function buildNativeUseReason(keys, now) {
1721
+ const states = keys.map((key) => readWorkspaceState$1(key)).filter((state) => state !== null);
1722
+ if (!states.length) return "no Stim workspace records when it last ran ios, android or macos";
1723
+ return recentNativeUseReason(Math.max(...states.map((state) => lastNativeUseFrom(state)).filter(Number.isFinite)), now);
1724
+ }
1725
+ function artifactGuard(candidate, kind, pinned, locked, running) {
1508
1726
  const { worktree, path, keys } = candidate;
1509
1727
  if (keys.some(pinned)) return "pinned by maintenance.keep";
1728
+ if (!pathExists(path)) return "it is already gone";
1510
1729
  let real;
1511
1730
  try {
1512
1731
  real = canonicalPath(path);
@@ -1514,16 +1733,15 @@ function artifactGuard(candidate, kind, pinned, locked) {
1514
1733
  return "its path cannot be resolved";
1515
1734
  }
1516
1735
  if (!isDirectory(path) || real !== join(canonicalPath(worktree), relative(worktree, path))) return "it or a directory above it is a link, or it lies outside its worktree";
1517
- const inUse = keys.flatMap((key) => workspaceInUse(key, {
1518
- supervisor: kind === "dependencies",
1519
- nativeRun: key !== locked
1520
- }));
1736
+ const inUse = keys.flatMap((key) => kind === "build" ? workspaceBuildReasons(key, { nativeRun: key !== locked }) : workspaceInUse(key, { nativeRun: key !== locked }));
1521
1737
  if (inUse.length) return `in use: ${inUse.join("; ")}`;
1738
+ if (kind === "dependencies") return runningFromReason(worktree, running === void 0 ? readRunningProcesses() : running);
1522
1739
  return null;
1523
1740
  }
1524
1741
  async function collectArtifactsAsync(kind, options, signal, operation) {
1525
1742
  const candidates = [];
1526
1743
  const skips = [];
1744
+ const running = kind === "dependencies" ? await readRunningProcessesAsync(signal) : null;
1527
1745
  for (const worktree of await artifactWorktreesAsync(signal, operation)) {
1528
1746
  if (kind === "dependencies" && !worktree.linked) continue;
1529
1747
  const root = canonicalPath(worktree.path);
@@ -1554,7 +1772,7 @@ async function collectArtifactsAsync(kind, options, signal, operation) {
1554
1772
  path,
1555
1773
  keys: worktree.keys
1556
1774
  };
1557
- let reason = protectedHere ? "the command that started this pass runs inside its worktree" : artifactGuard(candidate, kind, options.pinned);
1775
+ let reason = protectedHere ? "the command that started this pass runs inside its worktree" : artifactGuard(candidate, kind, options.pinned, void 0, running);
1558
1776
  if (!reason) {
1559
1777
  const tracked = await gitQuietAsync(worktree.path, [
1560
1778
  "ls-files",
@@ -1567,11 +1785,16 @@ async function collectArtifactsAsync(kind, options, signal, operation) {
1567
1785
  declareFor: operation,
1568
1786
  killSignal: "SIGKILL"
1569
1787
  });
1570
- const untracked = tracked !== null && !tracked.trim();
1571
- reason = keptReason(candidate, kind, options, {
1788
+ const windowed = tracked !== null && !tracked.trim() && kind === "dependencies";
1789
+ const activity = windowed ? await worktreeActivityOfAsync(worktree.path, worktree.keys, operation) : null;
1790
+ const written = windowed ? await newestWriteAsync(path, SCAN_DEPTH[kind], signal) : null;
1791
+ reason = keptReason(candidate, kind, {
1792
+ ...options,
1793
+ running
1794
+ }, {
1572
1795
  tracked,
1573
- activity: untracked ? await worktreeActivityOfAsync(worktree.path, worktree.keys, operation) : null,
1574
- written: untracked ? await newestWriteAsync(path, SCAN_DEPTH[kind], signal) : null
1796
+ activity,
1797
+ written
1575
1798
  });
1576
1799
  }
1577
1800
  if (reason) skips.push({
@@ -1590,6 +1813,7 @@ async function collectArtifactsAsync(kind, options, signal, operation) {
1590
1813
  function collectArtifacts(kind, options) {
1591
1814
  const candidates = [];
1592
1815
  const skips = [];
1816
+ const running = kind === "dependencies" ? readRunningProcesses() : null;
1593
1817
  for (const worktree of artifactWorktrees()) {
1594
1818
  if (kind === "dependencies" && !worktree.linked) continue;
1595
1819
  const root = canonicalPath(worktree.path);
@@ -1606,7 +1830,10 @@ function collectArtifacts(kind, options) {
1606
1830
  path,
1607
1831
  keys: worktree.keys
1608
1832
  };
1609
- const reason = protectedHere ? "the command that started this pass runs inside its worktree" : keptReason(candidate, kind, options);
1833
+ const reason = protectedHere ? "the command that started this pass runs inside its worktree" : keptReason(candidate, kind, {
1834
+ ...options,
1835
+ running
1836
+ });
1610
1837
  if (reason) skips.push({
1611
1838
  target: path,
1612
1839
  reason
@@ -1642,34 +1869,50 @@ async function clearArtifact(action, kind, options) {
1642
1869
  path: action.target,
1643
1870
  keys: worktree.keys
1644
1871
  };
1872
+ const running = kind === "dependencies" ? await readRunningProcessesAsync() : null;
1645
1873
  const early = keptReason(candidate, kind, {
1646
1874
  ...options,
1647
- now: Date.now()
1875
+ now: Date.now(),
1876
+ running
1648
1877
  });
1649
1878
  if (early) return {
1650
1879
  status: "kept",
1651
1880
  bytes: 0,
1652
1881
  detail: early
1653
1882
  };
1883
+ const unrestorable = kind === "dependencies" ? await restoreProblem(candidate, options.restoreCache) : null;
1884
+ if (unrestorable && options.requireRestorable) return {
1885
+ status: "kept",
1886
+ bytes: 0,
1887
+ detail: unrestorable
1888
+ };
1654
1889
  const before = lowestFree(measureDisk());
1655
1890
  const [key] = candidate.keys;
1656
- const remove = () => {
1891
+ const remove = async () => {
1657
1892
  const reason = keptReason(candidate, kind, {
1658
1893
  ...options,
1659
1894
  now: Date.now(),
1895
+ running: kind === "dependencies" ? await readRunningProcessesAsync() : null,
1660
1896
  ...key ? { locked: key } : {}
1661
1897
  });
1662
1898
  if (reason) return reason;
1899
+ const record = kind === "dependencies" && !unrestorable;
1900
+ if (record) recordLightened(candidate.keys, candidate.path);
1663
1901
  rmSync(candidate.path, {
1664
1902
  recursive: true,
1665
1903
  force: true
1666
1904
  });
1905
+ if (record) finishLightened(candidate.keys, candidate.path);
1667
1906
  return null;
1668
1907
  };
1669
1908
  const kept = key ? await withIdleWorkspace(key, remove, {
1670
1909
  purpose: "maintenance",
1671
- supervisor: kind === "dependencies"
1672
- }).then((run) => run.ran ? run.value : `in use: ${run.reasons.join("; ")}`) : remove();
1910
+ supervisor: kind === "dependencies",
1911
+ ...kind === "build" ? {
1912
+ hosted: false,
1913
+ managedLocks: false
1914
+ } : {}
1915
+ }).then((run) => run.ran ? run.value : `in use: ${run.reasons.join("; ")}`) : await remove();
1673
1916
  if (kept) return {
1674
1917
  status: "kept",
1675
1918
  bytes: 0,
@@ -2032,17 +2275,19 @@ async function planLevel2({ settings, sizes, protectedRoot, pinned, now, operati
2032
2275
  const actions = [];
2033
2276
  const skips = [];
2034
2277
  const reason = `disk pressure level 2, idle for ${settings.pressureProtectMinutes} minutes`;
2278
+ const lightenReason = `disk pressure level 2, no ios, android or macos run in the last ${LIGHTEN_AFTER_MS / 6e4} minutes`;
2035
2279
  const outputs = (await collectWorkspaceOutputs({
2036
2280
  olderThan: null,
2037
2281
  now,
2038
2282
  measure: false,
2039
2283
  signal: operation?.signal,
2040
- operation
2284
+ operation,
2285
+ lighten: true
2041
2286
  })).workspaces.flatMap((entry) => {
2042
2287
  const root = entry.projectRoot === null ? null : canonicalPath(entry.projectRoot);
2043
2288
  if (root === null || !entry.willClear || root === protectedRoot || pinned(root)) return [];
2044
2289
  const used = workspaceLastUsed(root);
2045
- return Number.isFinite(used) && now - used >= windowMs ? [{
2290
+ return Number.isFinite(used) ? [{
2046
2291
  root,
2047
2292
  used
2048
2293
  }] : [];
@@ -2054,7 +2299,7 @@ async function planLevel2({ settings, sizes, protectedRoot, pinned, now, operati
2054
2299
  target: root,
2055
2300
  workspace: root,
2056
2301
  bytes: size?.bytes ?? 0,
2057
- reason,
2302
+ reason: lightenReason,
2058
2303
  level: 2
2059
2304
  });
2060
2305
  }
@@ -2090,7 +2335,7 @@ async function planLevel2({ settings, sizes, protectedRoot, pinned, now, operati
2090
2335
  kind: "would-clear-build-dirs",
2091
2336
  target: candidate.path,
2092
2337
  bytes: 0,
2093
- reason,
2338
+ reason: lightenReason,
2094
2339
  level: 2
2095
2340
  });
2096
2341
  skips.push(...build.skips);
@@ -2256,7 +2501,7 @@ async function planPressureLevels(level, context) {
2256
2501
  function triggeringRoot(operation) {
2257
2502
  return canonicalPath(findProjectRoot$1(operation?.cwd ?? process.cwd()) ?? join(configDir(), "maintenance", "no-project"));
2258
2503
  }
2259
- async function plannedMaintenance(pressure, recordedSizes, settings, { devices = true, sweep = false, worktrees = false, refresh = true, operation } = {}) {
2504
+ async function plannedMaintenance(pressure, recordedSizes, settings, { devices = true, sweep = false, worktrees = false, idle = false, refresh = true, operation } = {}) {
2260
2505
  const env = operation?.env ?? process.env;
2261
2506
  const resolved = resolveBudget({ env });
2262
2507
  if (resolved.error) throw new Error(resolved.error);
@@ -2353,11 +2598,17 @@ async function plannedMaintenance(pressure, recordedSizes, settings, { devices =
2353
2598
  sizes: currentSizes,
2354
2599
  protectRecentMs: settings.protectRecentHours * 36e5,
2355
2600
  refresh,
2601
+ idleDays: sweep && settings.removeIdleWorktrees && settings.olderThanDays > 0 ? settings.olderThanDays : null,
2356
2602
  operation
2357
2603
  });
2358
2604
  merge(planned);
2359
2605
  result.worktreeSweep = planned.sweep;
2360
2606
  }
2607
+ if (idle && devices) merge(planIdleShutdown({
2608
+ protectedRoot: root,
2609
+ now: Date.now(),
2610
+ env
2611
+ }));
2361
2612
  if (pressure) {
2362
2613
  const pressured = await planPressureLevels(diskPressureLevel(lowestFree(pressure.disk), budget, settings), {
2363
2614
  settings,
@@ -2405,6 +2656,7 @@ async function previewMaintenance({ devices = true, refresh = true, operation }
2405
2656
  ...await plannedMaintenance(pressure, state?.sizes ?? [], settings, {
2406
2657
  devices,
2407
2658
  sweep: true,
2659
+ idle: true,
2408
2660
  refresh,
2409
2661
  operation
2410
2662
  }),
@@ -2543,14 +2795,16 @@ async function clearOutputs(action, context) {
2543
2795
  if (!root) return kept("the action names no workspace");
2544
2796
  if (root === context.protectedRoot) return kept("the command that started this pass runs in it");
2545
2797
  if (maintenancePinned(root)) return kept("pinned by maintenance.keep");
2546
- const recent = recentUse(root, context.settings, protectWindowMs(action, context.settings));
2798
+ const lighten = action.level === 2;
2799
+ const recent = lighten ? null : recentUse(root, context.settings, protectWindowMs(action, context.settings));
2547
2800
  if (recent) return kept(recent);
2548
2801
  const olderThan = action.olderThanDays ?? null;
2549
2802
  const now = Date.now();
2550
2803
  const report = collectWorkspaceOutputs({
2551
2804
  olderThan,
2552
2805
  now,
2553
- measure: false
2806
+ measure: false,
2807
+ lighten
2554
2808
  });
2555
2809
  const entry = report.workspaces.find((candidate) => candidate.projectRoot && canonicalPath(candidate.projectRoot) === root);
2556
2810
  if (!entry) return kept("it has no build outputs any more");
@@ -2559,7 +2813,8 @@ async function clearOutputs(action, context) {
2559
2813
  workspaces: [entry]
2560
2814
  }, {
2561
2815
  olderThan,
2562
- now
2816
+ now,
2817
+ lighten
2563
2818
  }));
2564
2819
  return outcomeOf(results, action.bytes);
2565
2820
  }
@@ -2575,7 +2830,12 @@ async function removeWorktree(action, context) {
2575
2830
  ...sweep,
2576
2831
  worktrees: [worktree]
2577
2832
  }));
2578
- return outcomeOf(results, freedSince(before));
2833
+ const outcome = outcomeOf(results, freedSince(before));
2834
+ const removal = results.find((result) => result.status === "done")?.detail;
2835
+ return outcome.status === "done" ? {
2836
+ ...outcome,
2837
+ detail: removal ?? action.reason
2838
+ } : outcome;
2579
2839
  }
2580
2840
  async function removeOrphan(action) {
2581
2841
  const orphan = (await collectOrphanedWorkspaces(Object.keys(loadConfig$1()?.projects ?? {}), listMountedVolumes())).orphaned.find((candidate) => candidate.dir === action.target);
@@ -2583,11 +2843,47 @@ async function removeOrphan(action) {
2583
2843
  const { results } = await quietly(() => deleteOrphanedWorkspaces([orphan]));
2584
2844
  return outcomeOf(results, orphan.bytes ?? 0);
2585
2845
  }
2846
+ async function shutDownIdleDevice(action) {
2847
+ if (deviceSweepIsScoped()) return kept("STIM_HOME scopes this config, and devices are machine-wide");
2848
+ const root = action.workspace ?? action.target;
2849
+ const minutes = idleShutdownMinutes(root);
2850
+ if (minutes === 0) return kept("devices.idleShutdownMinutes is 0 for this workspace");
2851
+ const idleMs = minutes * 6e4;
2852
+ const due = dueIdleDevices(root, idleMs).find(({ device }) => device.id === action.target);
2853
+ if (!due) return kept(`it is no longer idle for ${minutes} minutes`);
2854
+ const failures = [];
2855
+ const skips = [];
2856
+ const log = (event) => {
2857
+ writeIdleShutdownEvent(root, event);
2858
+ if (event.event === "device_idle_shutdown_failed") (event.skipped ? skips : failures).push(event.msg);
2859
+ };
2860
+ const run = await withIdleWorkspace(root, () => shutDownIdleDevices(root, idleMs, log, Date.now(), { only: due.device }), {
2861
+ purpose: "automatic maintenance idle shutdown",
2862
+ supervisor: false,
2863
+ managedLocks: false
2864
+ });
2865
+ if (!run.ran) return kept(`in use: ${run.reasons.join("; ")}`);
2866
+ if (run.value) return {
2867
+ status: "done",
2868
+ bytes: 0,
2869
+ detail: action.target
2870
+ };
2871
+ if (failures.length) return {
2872
+ status: "failed",
2873
+ bytes: 0,
2874
+ detail: failures.join("; ")
2875
+ };
2876
+ return kept(skips.join("; ") || `it is no longer idle for ${minutes} minutes`);
2877
+ }
2586
2878
  async function deleteParkedDevice(action) {
2587
2879
  if (deviceSweepIsScoped()) return kept("STIM_HOME scopes this config, and devices are machine-wide");
2588
- const sims = (await collectParkedSims({})).filter((sim) => sim.udid === action.target);
2589
- const avds = sims.length ? [] : (await collectParkedAvds({})).filter((avd) => avd.name === action.target);
2590
- if (!sims.length && !avds.length) return kept("it is no longer parked");
2880
+ const age = {
2881
+ olderThanDays: action.olderThanDays ?? null,
2882
+ now: Date.now()
2883
+ };
2884
+ const sims = (await collectParkedSims({}, age)).filter((sim) => sim.udid === action.target);
2885
+ const avds = sims.length ? [] : (await collectParkedAvds({}, age)).filter((avd) => avd.name === action.target);
2886
+ if (!sims.length && !avds.length) return kept(action.olderThanDays === void 0 ? "it is no longer parked" : `it is no longer parked, or was parked within ${action.olderThanDays} days`);
2591
2887
  const before = lowestFree(measureDisk());
2592
2888
  const { results } = await quietly(async () => await deleteParkedSims(sims) + await deleteParkedAvds(avds));
2593
2889
  return outcomeOf(results, freedSince(before));
@@ -2799,6 +3095,7 @@ async function executeAction(action, context) {
2799
3095
  case "would-unregister-cache": return unregisterCache(action);
2800
3096
  case "would-delete-parked-device": return deleteParkedDevice(action);
2801
3097
  case "would-delete-devices": return deleteDevices(action, context);
3098
+ case "would-shutdown-device": return action.check === "disk" ? kept("automatic maintenance does not stop devices or dev servers for disk") : shutDownIdleDevice(action);
2802
3099
  case "would-stop-workspace": return action.level === 3 ? stopWorkspace(action, context) : kept("automatic maintenance stops dev servers only at disk pressure level 3");
2803
3100
  case "would-clear-build-dirs":
2804
3101
  case "would-delete-dependencies": return clearArtifacts(action, context);
@@ -2813,4 +3110,4 @@ function diskRecovered(budget) {
2813
3110
  return measureDisk().every((disk) => disk.freeMb >= floor);
2814
3111
  }
2815
3112
  //#endregion
2816
- export { collectWorktreeSweep as _, previewMaintenance as a, worktreeRemovalReason as b, deleteTempLeftovers as c, ARTIFACT_DIRS as d, appRoots as f, candidateRootsAsync as g, ownedDeviceUsage as h, plannedMaintenance as i, tempLeftoverLines as l, measureOwnedDevices as m, diskRecovered as n, triggeringRoot as o, finishedWorkspaces as p, executeAction as r, collectTempLeftovers as s, deleteIdleWorkspaceDevices as t, tempLeftoverSection as u, removeWorktrees as v, worktreePullRequestNote as y };
3113
+ export { worktreeRemovalReason as C, worktreePullRequestNote as S, ownedDeviceUsage as _, previewMaintenance as a, collectWorktreeSweep as b, deleteTempLeftovers as c, appRoots as d, artifactWorktrees as f, measureOwnedDevices as g, finishedWorkspaces as h, plannedMaintenance as i, tempLeftoverLines as l, collectArtifactsAsync as m, diskRecovered as n, triggeringRoot as o, clearArtifact as p, executeAction as r, collectTempLeftovers as s, deleteIdleWorkspaceDevices as t, tempLeftoverSection as u, maintenancePinned as v, removeWorktrees as x, candidateRootsAsync as y };