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,211 +1,45 @@
1
- import { D as ownership_claim_exports, S as process_identity_exports, b as operationState, l as WorkspaceProcessCleanupError, m as declareSpawnsOn, n as isTimeoutError, t as getExecutor, v as stopDeclaringSpawnsOn } from "./exec-VFRpu1iA.mjs";
2
- import { d as workspaceDir, l as workspaceAgentDeviceDir } from "./paths-DQtTgsBf.mjs";
3
- import { E as upsertProject, G as projectDeviceSlots, L as measuredDirectorySizeAsync, O as withConfigLockAsync$1, S as removeProject, _ as loadConfig$1, a as clearDevice, d as getConfigDir, g as isPathPrefix, j as acquireAvdClaim, p as getProject } from "./config-BZhXY15H.mjs";
4
- import { a as formatElapsed, h as shortUdid, i as formatDuration, l as phaseLine, p as releasedLeaseFact, u as plural } from "./command-output-BBvS1H6v.mjs";
5
- import { c as gitQuiet, o as git, r as childEnvironment, s as gitAsync, t as runOperationFile } from "./operation-exec-CUZifp6Z.mjs";
6
- import { a as recordWorkspaceUseAsync, l as withWorkspaceStateLockAsync, r as readWorkspaceState$1 } from "./workspace-state-CPSp3mav.mjs";
7
- import { At as podsOutOfSync, B as readCommittedSettings, Bt as unpushedCommitsAsync, Ct as hasUncommittedWorkAsync, Dt as listWorktreesAsync, Ft as resolveFullRef, Gt as removeTemporaryEntryAsync, Ht as filesystemDevice, It as resolveFullRefAsync, J as resolveProjectSettings, Mt as removeWorktreeAsync, Ot as locallyKnownUpstream, Rt as restoreFileAsync, St as hasRemoteAsync, Vt as warmWorktreePaths, X as resolveSettings, _t as dirtyPathsAsync, a as SETTING_SHAPE_REMEDY, at as unknownSettingKeys, bt as gitCommonDirOnDisk, ft as branchExistsAsync, ht as depsOutOfSync, jt as readWorktreeExclude, mt as deleteBranchAsync, pt as cloneIgnoredEntries, q as resolveIosProjectDir, tt as settingShapeErrors, wt as isPodInstallChurn, xt as hasPopulatedSubmodulesAsync, yt as gitCommonDirAsync, zt as sourceCheckoutOf } from "./settings-B5tCoq9N.mjs";
8
- import { F as readRemoteSessionId, M as readMetroTunnel, O as clearManagedMetroTunnel, P as readRemoteSession, T as MODE_COMMAND, i as findProjectRoot$1, k as clearRemoteSession, o as isMobileProject } from "./project-BiC68mIp.mjs";
9
- import { t as macosAppPresent } from "./state-DPaM3u5K.mjs";
10
- import { a as workspaceLinkLine, o as workspaceLinks } from "./stim-desktop-DS0mzR8U.mjs";
11
- import { ft as POOL_SETTING_REMEDY, vt as parkedMaxSetting } from "./android-Cjcj8MCw.mjs";
12
- import { c as pidExists, d as resolveProjectMetro, p as signalProcessTreeAsync, r as killMetroTreeAsync } from "./metro-1pJgCX4U.mjs";
13
- import { o as readWebRecord } from "./state-DMRNP7-4.mjs";
14
- import { Cr as discoverCaches, S as withdrawDevServerStopRequest, Vt as endRecordedSession, Xn as resolveEasCliBin, b as requestDevServerStop, c as readVolumeSpace, it as planCacheEmptying, l as readVolumeSpaceAsync, o as machineNumber, u as resolveBudget, zn as runProcessOperation } from "./budget-HyShrIpL.mjs";
1
+ import { D as ownership_claim_exports, S as process_identity_exports, b as operationState, l as WorkspaceProcessCleanupError, m as declareSpawnsOn, n as isTimeoutError, t as getExecutor, v as stopDeclaringSpawnsOn } from "./exec-aULwtNly.mjs";
2
+ import { E as workspaceDir, a as recordWorkspaceUseAsync, l as withWorkspaceStateLockAsync, r as readWorkspaceState$1, w as workspaceAgentDeviceDir } from "./workspace-state-BjqdjnJR.mjs";
3
+ import { E as upsertProject, G as projectDeviceSlots, L as measuredDirectorySizeAsync, O as withConfigLockAsync$1, S as removeProject, _ as loadConfig$1, a as clearDevice, d as getConfigDir, g as isPathPrefix$1, j as acquireAvdClaim, p as getProject } from "./config-Cn94Jc0g.mjs";
4
+ import { a as formatElapsed, h as shortUdid, i as formatDuration, l as phaseLine, p as releasedLeaseFact, u as plural } from "./command-output-Dj-WT4BP.mjs";
5
+ import { c as gitQuiet, l as gitQuietAsync, o as git, r as childEnvironment, s as gitAsync, t as runOperationFile } from "./operation-exec-Dp4FuIGR.mjs";
6
+ import { At as listWorktreesAsync, Ct as hasPopulatedSubmodulesAsync, Et as isPodInstallChurn, Ft as removeWorktreeAsync, Ht as sourceCheckoutOf, Nt as podsOutOfSync, Pt as readWorktreeExclude, Q as resolveSettings, Rt as resolveFullRef, St as gitCommonDirOnDisk, Tt as hasUncommittedWorkAsync, Ut as unpushedCommitsAsync, V as readCommittedSettings, Vt as restoreFileAsync, Wt as warmWorktreePaths, X as resolveProjectSettings, Xt as moveIgnoredTreesToTrash, Y as resolveIosProjectDir, Yt as disposeTrash, Zt as restoreTrashedTrees, _ as inertSettingWarning, _t as depsOutOfSync, a as SETTING_SHAPE_REMEDY, gt as deleteBranchAsync, ht as cloneIgnoredEntries, jt as locallyKnownUpstream, mt as branchExistsAsync, rt as settingShapeErrors, st as unknownSettingKeys, wt as hasRemoteAsync, xt as gitCommonDirAsync, yt as dirtyPathsAsync, zt as resolveFullRefAsync } from "./settings-BDVI8Ivc.mjs";
7
+ import { F as readRemoteSessionId, M as readMetroTunnel, O as clearManagedMetroTunnel, P as readRemoteSession, T as MODE_COMMAND, i as findProjectRoot$1, k as clearRemoteSession, o as isMobileProject } from "./project-DYj-HVx6.mjs";
8
+ import { t as macosAppPresent } from "./state-CZxLyetD.mjs";
9
+ import { a as workspaceLinkLine, o as workspaceLinks } from "./stim-desktop-CbWo21kb.mjs";
10
+ import { ft as POOL_SETTING_REMEDY, vt as parkedMaxSetting } from "./android-DriNoAVy.mjs";
11
+ import { c as pidExists, d as resolveProjectMetro, p as signalProcessTreeAsync, r as killMetroTreeAsync } from "./metro-CvyD-xwe.mjs";
12
+ import { A as diskPressureLevel, D as requestDevServerStop, En as runProcessOperation, G as planCacheEmptying, Ln as resolveEasCliBin, N as lowestFree, Ot as endRecordedSession, _ as resolveBudget, g as readVolumeSpaceAsync, h as readVolumeSpace, k as withdrawDevServerStopRequest, p as machineNumber, ur as discoverCaches } from "./ccache-BWTze1Ls.mjs";
13
+ import { o as readWebRecord } from "./state-BX1y5syV.mjs";
14
+ import { D as withManagedTunnelRemovalLock, S as stopTunnel, T as withManagedRemoteWorktreeRemovalLock, _ as withIdleWorkspace, b as resolveSupervisorTarget, dt as teardownOwnedBrowser, ft as teardownOwnedIosSim, g as emptyWorkspaceDir, o as collectWorkspaceOutputs, ut as teardownOwnedAvd } from "./workspaces-DFbj07jp.mjs";
15
15
  import { t as canonicalPath } from "./paths-BsJMoPP2.mjs";
16
- import { p as cacheUser } from "./native-run-D5OJsGwA.mjs";
17
- import { A as teardownOwnedIosSim, C as withManagedTunnelRemovalLock, O as teardownOwnedAvd, a as collectWorkspaceOutputs, f as emptyWorkspaceDir, g as resolveSupervisorTarget, k as teardownOwnedBrowser, p as withIdleWorkspace, v as stopTunnel, x as withManagedRemoteWorktreeRemovalLock } from "./workspaces-C9w4aeXF.mjs";
18
- import { D as releaseWorkspaceLeases, t as agentDeviceDaemon } from "./activity-BpjwrQXU.mjs";
19
- import { I as readHostMemoryPressure, L as readHostMemoryPressureAsync } from "./ios-BQzvJlzw.mjs";
20
- import { t as clearNamedPorts } from "./named-ports-BVbn8SvC.mjs";
21
- import { r as HEARTBEAT_INTERVAL_MS, x as podInstallCommand } from "./xcode-CiIIDnuD.mjs";
16
+ import { D as runCaptured, O as runPodInstall, S as DEPS_ERROR, T as readPodState, Y as podInstallCommand, ct as cacheUser, j as HEARTBEAT_INTERVAL_MS, w as podsAreStale } from "./toolchain-CbCG6C5Z.mjs";
17
+ import { D as releaseWorkspaceLeases, t as agentDeviceDaemon } from "./activity-Cd-ysFes.mjs";
18
+ import { R as readHostMemoryPressure, z as readHostMemoryPressureAsync } from "./ios-E26qSpkb.mjs";
19
+ import { t as clearNamedPorts } from "./named-ports-BCyMWA6b.mjs";
22
20
  import { t as resolveMaintenanceSettings } from "./settings-Djl-mpjX.mjs";
23
- import { _ as runCaptured, h as readPodState, m as podsAreStale, p as DEPS_ERROR, v as runPodInstall } from "./toolchain-VzLJzZ7O.mjs";
24
- import { i as stopHostedAndroid } from "./hosted-android-Bvb2-ZxD.mjs";
25
- import { r as stopHostedIos } from "./hosted-ios-RtVcJH3u.mjs";
26
- import { n as stopMacosApp } from "./stop-COP6PQqw.mjs";
27
- import { n as verifyCollectorOwnership } from "./ownership-jxxz-19m.mjs";
28
- import { n as removePhoneReverses } from "./phone-reverses-CYb7RRAx.mjs";
29
- import { a as discoverXcodeProject } from "./react-native-build-CwZB-agu.mjs";
30
- import { n as hasInstalledDependencies, r as installedNpmTreeIsValid, t as dependencyState } from "./dependency-state-BwNCYG5Q.mjs";
31
- import { a as forgetStatusMeasures, p as mergeStateAsync, t as startWarmProgress } from "./warm-progress-BV3ZPmcY.mjs";
32
- import { o as pullRequestLookupAsync, r as endedPullRequest } from "./pull-request-CbNlkFok.mjs";
33
- import { r as workspaceRecordingEnabled } from "./recordings-BaoQ0t-J.mjs";
21
+ import { i as stopHostedAndroid } from "./hosted-android-BMKzUNnY.mjs";
22
+ import { a as stopHostedIos, n as removePhoneReverses } from "./phone-reverses-C9yAaKEv.mjs";
23
+ import { n as stopMacosApp } from "./stop-CH79S3IZ.mjs";
24
+ import { n as verifyCollectorOwnership } from "./ownership-Bx-0cku-.mjs";
25
+ import { o as pullRequestLookupAsync, r as endedPullRequest } from "./pull-request-BzLmzAIQ.mjs";
26
+ import { c as mergeStateAsync, d as forgetStatusMeasures, t as startWarmProgress } from "./warm-progress-C0RsbngN.mjs";
27
+ import { r as workspaceRecordingEnabled } from "./recordings-C20iUZfp.mjs";
28
+ import { i as installedNpmTreeProblems, n as hasInstalledDependencies, t as dependencyState } from "./dependency-state-C3OtgG94.mjs";
29
+ import { a as discoverXcodeProject } from "./react-native-build-DaK2IFyP.mjs";
34
30
  import { claimRemoveCommand, clearFreeClaimSet, readClaimSet, releaseClaim, tryAcquireClaim } from "@stim-cli/core/ownership-claim";
35
31
  import { createHash, randomUUID } from "node:crypto";
36
32
  import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
37
33
  import { cpus, freemem, homedir, loadavg, totalmem } from "node:os";
38
34
  import { basename, dirname, join, relative } from "node:path";
39
- import { buildCacheRoot, configDir, metroCacheRoot, unitLines, unitWords, withDirLockTransaction, workspaceName } from "@stim-cli/core";
40
- import { LAST_BUILD_KEYS, archiveDir, archiveEnabled, archiveLock, archiveRoot, archivedUsage, buildWorkerRoot, countErrorEntries, endedAgentSessionOf, isJsonObject, listRecordedDevicesAsync, loadConfig, queryLogs, readArchivesAsync, readBuildHistory, readJsonObject, readLastBuilds, readMacosRecord, readPullRequestCache, readWorkspaceState, recordingEnabled, sharedCcache, sharedCompilationCache, sortByTs, trashRoot } from "@stim-cli/core/state";
35
+ import { buildCacheRoot, configDir, metroCacheRoot, quotedPath, unitLines, unitWords, withDirLockTransaction, workspaceName } from "@stim-cli/core";
36
+ import { LAST_BUILD_KEYS, archiveDir, archiveEnabled, archiveLock, archiveRoot, archivedUsage, buildWorkerRoot, countErrorEntries, endedAgentSessionOf, isJsonObject, isPathPrefix, listRecordedDevicesAsync, loadConfig, queryLogs, readArchivesAsync, readBuildHistory, readJsonObject, readLastBuilds, readMacosRecord, readPullRequestCache, readWorkspaceState, recordingEnabled, sharedCcache, sharedCompilationCache, sortByTs, trace, worktreeArchiveRoot } from "@stim-cli/core/state";
41
37
  import { basename as basename$1, dirname as dirname$1, isAbsolute as isAbsolute$1, join as join$1, relative as relative$1, resolve as resolve$1 } from "path";
42
38
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
43
- import { captureProcessIdentity } from "@stim-cli/core/process-identity";
44
39
  import chalk from "chalk";
45
40
  import { existsSync as existsSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, readlinkSync as readlinkSync$1, realpathSync as realpathSync$1, renameSync as renameSync$1, rmSync as rmSync$1, symlinkSync as symlinkSync$1, unlinkSync as unlinkSync$1, writeFileSync as writeFileSync$1 } from "fs";
46
- import { chmod, lstat, mkdir, mkdtemp, readdir, rename, rm, rmdir, stat, utimes, writeFile } from "node:fs/promises";
41
+ import { chmod, lstat, mkdir, readdir, rename, rm, rmdir, stat, utimes, writeFile } from "node:fs/promises";
47
42
  import { createHash as createHash$1 } from "crypto";
48
- //#region src/maintenance/pressure.ts
49
- /**
50
- * The free-disk floor of each level in MiB. Level 2 has no floor of its own: it runs when level 1 left free disk
51
- * under the budget floor, and stops once free disk is back above it. Its urgent threshold only sets the reported level.
52
- */
53
- function levelFloors(budget, settings) {
54
- const floor = Math.max(budget.minFreeDiskMb, budget.hardFloorDiskMb);
55
- return {
56
- 1: floor,
57
- 2: floor,
58
- 3: Math.max(settings.criticalFreeDiskGb * 1024, budget.hardFloorDiskMb),
59
- urgent: settings.urgentFreeDiskGb * 1024
60
- };
61
- }
62
- function diskPressureLevel(freeMb, budget, settings) {
63
- const floors = levelFloors(budget, settings);
64
- if (freeMb < floors[3]) return 3;
65
- if (freeMb < floors.urgent) return 2;
66
- return freeMb < floors[1] ? 1 : 0;
67
- }
68
- function lowestFree(disk) {
69
- return disk.length ? Math.min(...disk.map((volume) => volume.freeMb)) : Infinity;
70
- }
71
- /** Whether an action planned at a pressure level may still run with this much free disk. */
72
- function levelStillShort(action, freeMb, budget, settings) {
73
- return action.level === void 0 || freeMb < levelFloors(budget, settings)[action.level];
74
- }
75
- /** The protection window in milliseconds: protectRecentHours, or maintenance.pressureProtectMinutes for actions of level 2 and 3. */
76
- function protectWindowMs(action, settings) {
77
- return action.level === void 0 ? settings.protectRecentHours * 36e5 : settings.pressureProtectMinutes * 6e4;
78
- }
79
- //#endregion
80
- //#region src/maintenance/measure.ts
81
- function measureDisk() {
82
- return readVolumeSpace([
83
- configDir(),
84
- ...Object.keys(loadConfig()?.projects ?? {}),
85
- buildWorkerRoot(loadConfig())
86
- ]);
87
- }
88
- function measureDiskAsync(operation) {
89
- return readVolumeSpaceAsync([
90
- configDir(),
91
- ...Object.keys(loadConfig()?.projects ?? {}),
92
- buildWorkerRoot(loadConfig())
93
- ], operation.signal);
94
- }
95
- /** The disk pressure level of the fullest volume Stim writes to, measured now. */
96
- function currentDiskLevel(disk = measureDisk()) {
97
- return diskPressureLevel(lowestFree(disk), resolveBudget().budget, resolveMaintenanceSettings());
98
- }
99
- function measurePressure(settings, previous, now) {
100
- return pressureFromObservations(settings, previous, now, readHostMemoryPressure(), measureDisk());
101
- }
102
- async function measurePressureAsync(settings, previous, now, operation) {
103
- return pressureFromObservations(settings, previous, now, await readHostMemoryPressureAsync(void 0, void 0, operation.signal, operation), await readVolumeSpaceAsync([
104
- configDir(),
105
- ...Object.keys(loadConfig()?.projects ?? {}),
106
- buildWorkerRoot(loadConfig())
107
- ], operation.signal));
108
- }
109
- function pressureFromObservations(settings, previous, now, level, disk) {
110
- const availableBytes = process.platform === "darwin" ? null : freemem();
111
- const warningSince = level === "warning" ? previous?.warningSince ?? now : null;
112
- return {
113
- disk,
114
- memory: {
115
- level,
116
- availableBytes,
117
- pressured: settings.memoryPressureLevel !== "off" && (level === "critical" || settings.memoryPressureLevel === "warning" && warningSince !== null && now - warningSince >= settings.memoryWarningMinutes * 6e4 || availableBytes !== null && availableBytes < (settings.minAvailableMemoryGb === void 0 ? totalmem() * .1 : settings.minAvailableMemoryGb * 1024 ** 3))
118
- },
119
- warningSince
120
- };
121
- }
122
- function sizeScanDeferred(settings) {
123
- return loadavg()[0] / Math.max(1, cpus().length) > settings.maxLoadPerCore;
124
- }
125
- function cacheBlocked(entry, discovered = discoverCaches(), env = process.env) {
126
- return planCacheEmptying([{
127
- ...entry,
128
- note: "",
129
- prune: "entries",
130
- ...discovered.some((cache) => canonicalPath(cache.dir) === canonicalPath(entry.dir) && cache.source === "registered") ? { source: "registered" } : {}
131
- }], false, env)[0]?.machineGlobal ?? (entry.category === "compilation-cache" ? cacheUser(entry.dir) : void 0);
132
- }
133
- async function measureSizes(now, failure) {
134
- const sizes = [];
135
- const outputs = await collectWorkspaceOutputs({
136
- olderThan: null,
137
- now,
138
- sizeTimeoutMs: 12e4
139
- });
140
- for (const entry of outputs.workspaces) {
141
- if (entry.bytes === null) {
142
- failure(entry.dir, entry.projectRoot ?? void 0);
143
- continue;
144
- }
145
- sizes.push({
146
- name: "workspace build outputs",
147
- category: "workspace-outputs",
148
- dir: entry.dir,
149
- bytes: entry.bytes,
150
- measuredAt: now,
151
- ...entry.projectRoot ? { workspace: canonicalPath(entry.projectRoot) } : {},
152
- idleDays: entry.idleDays,
153
- ...!entry.willClear ? { blocked: entry.keptReason ?? "workspace ownership is unresolved" } : {}
154
- });
155
- }
156
- const known = [
157
- {
158
- name: "shared build cache",
159
- dir: buildCacheRoot(),
160
- category: "build-cache"
161
- },
162
- {
163
- name: "Metro transform caches",
164
- dir: metroCacheRoot(),
165
- category: "metro-cache"
166
- },
167
- {
168
- name: "ccache",
169
- dir: sharedCcache(),
170
- category: "ccache"
171
- },
172
- {
173
- name: "Swift compilation cache",
174
- dir: sharedCompilationCache(),
175
- category: "compilation-cache"
176
- }
177
- ];
178
- const discovered = discoverCaches();
179
- for (const cache of discovered) {
180
- if (cache.source !== "registered" && !canonicalPath(cache.dir).startsWith(`${canonicalPath(configDir())}/`)) continue;
181
- if (cache.prune === "report-only") continue;
182
- const dir = canonicalPath(cache.dir);
183
- if (known.some((item) => dir === canonicalPath(item.dir) || dir.startsWith(`${canonicalPath(item.dir)}/`))) continue;
184
- const category = cache.name.startsWith("Metro transform cache") ? "metro-cache" : /^(Expo build cache|native build cache|Build cache)$/.test(cache.name) ? "build-cache" : "other";
185
- known.push({
186
- name: cache.name,
187
- dir: cache.dir,
188
- category
189
- });
190
- }
191
- for (const entry of known) {
192
- if (!existsSync(entry.dir)) continue;
193
- const bytes = await measuredDirectorySizeAsync(entry.dir, { timeoutMs: 12e4 });
194
- if (bytes === null) {
195
- failure(entry.dir);
196
- continue;
197
- }
198
- const blocked = cacheBlocked(entry, discovered);
199
- sizes.push({
200
- ...entry,
201
- bytes,
202
- measuredAt: now,
203
- ...blocked ? { blocked } : {}
204
- });
205
- }
206
- return sizes;
207
- }
208
- //#endregion
209
43
  //#region src/workspace/pods-relocate.ts
210
44
  const CHECKSUMS_HEADER = "SPEC CHECKSUMS:\n";
211
45
  const GENERATED_DIRS = [
@@ -830,6 +664,95 @@ async function removeKind(record, kind) {
830
664
  function logRemovalOrder(path) {
831
665
  return /\.ndjson\.\d+$/.test(path) ? 0 : /^build-.*\.ndjson$/.test(basename(path)) ? 1 : 2;
832
666
  }
667
+ /**
668
+ * How long and how large worktree archives of `root` are kept, from `archive.maxAgeDays` and `archive.maxTotalGb`, or
669
+ * why its uncommitted changes cannot be archived: archives are disabled for it or for a project registered inside it,
670
+ * a limit is 0, or a setting is invalid.
671
+ */
672
+ function worktreeArchiveLimits(root, env = process.env) {
673
+ if (!enabled(root, env)) return { refusal: "archive.enabled is false for it" };
674
+ const nested = Object.keys(loadConfig()?.projects ?? {}).find((key) => key !== root && isPathPrefix(root, key) && !enabled(key, env));
675
+ if (nested) return { refusal: `archive.enabled is false for ${nested}, inside it` };
676
+ let limits;
677
+ try {
678
+ limits = archiveSettings(env);
679
+ } catch (error) {
680
+ return { refusal: (error instanceof Error ? error.message : String(error)).split("\n")[0] ?? "" };
681
+ }
682
+ if (!limits.maxAgeDays) return { refusal: "archive.maxAgeDays is 0, so no archive would be kept" };
683
+ if (!limits.maxTotalGb) return { refusal: "archive.maxTotalGb is 0, so no archive would be kept" };
684
+ return {
685
+ maxAgeDays: limits.maxAgeDays,
686
+ maxBytes: limits.maxTotalGb * 1024 ** 3
687
+ };
688
+ }
689
+ async function listWorktreeArchives(signal) {
690
+ const root = worktreeArchiveRoot();
691
+ let repositories;
692
+ try {
693
+ repositories = await readdir(root);
694
+ } catch {
695
+ return {
696
+ repositories: [],
697
+ entries: []
698
+ };
699
+ }
700
+ const entries = [];
701
+ for (const repository of repositories) {
702
+ signal?.throwIfAborted();
703
+ const dir = join(root, repository);
704
+ if (!(await lstat(dir)).isDirectory()) continue;
705
+ for (const name of await readdir(dir)) {
706
+ if (name.endsWith(".claims")) continue;
707
+ const path = join(dir, name);
708
+ const entry = await lstat(path);
709
+ if (!entry.isDirectory()) continue;
710
+ const created = Date.parse(String(readJsonObject(join(path, "manifest.json"))?.createdAt ?? ""));
711
+ const bytes = (await files(path, signal)).reduce((sum, file) => sum + file.bytes, 0);
712
+ entries.push({
713
+ path,
714
+ at: Number.isFinite(created) ? created : entry.mtimeMs,
715
+ bytes
716
+ });
717
+ }
718
+ }
719
+ return {
720
+ repositories,
721
+ entries
722
+ };
723
+ }
724
+ /** Bytes every worktree archive holds, the one being written included. */
725
+ async function worktreeArchiveBytes(signal) {
726
+ return (await listWorktreeArchives(signal)).entries.reduce((sum, entry) => sum + entry.bytes, 0);
727
+ }
728
+ async function pruneWorktreeArchives(now, limits, signal) {
729
+ const listed = await listWorktreeArchives(signal);
730
+ const { repositories } = listed;
731
+ const entries = listed.entries.filter((entry) => {
732
+ const claims = readClaimSet(`${entry.path}.claims`);
733
+ return !claims.live.length && !claims.unresolved.length;
734
+ });
735
+ entries.sort((a, b) => a.at - b.at);
736
+ let total = entries.reduce((sum, entry) => sum + entry.bytes, 0);
737
+ const cap = (limits.maxTotalGb ?? 0) * 1024 ** 3;
738
+ const maxAgeMs = (limits.maxAgeDays ?? 0) * DAY;
739
+ for (const entry of entries) {
740
+ signal?.throwIfAborted();
741
+ if (now - entry.at < maxAgeMs && total <= cap) continue;
742
+ if (clearFreeClaimSet({
743
+ root: `${entry.path}.claims`,
744
+ label: "worktree archive"
745
+ }).status !== "cleared") continue;
746
+ await rm(entry.path, {
747
+ recursive: true,
748
+ force: true
749
+ });
750
+ total -= entry.bytes;
751
+ }
752
+ for (const repository of repositories) try {
753
+ await rmdir(join(worktreeArchiveRoot(), repository));
754
+ } catch {}
755
+ }
833
756
  async function enforceArchiveRetention(now = Date.now(), operation) {
834
757
  const signal = operation?.signal;
835
758
  const env = operation?.env ?? process.env;
@@ -903,6 +826,7 @@ async function enforceArchiveRetention(now = Date.now(), operation) {
903
826
  record.expires.record = new Date(Date.parse(record.removedAt) + limits.maxAgeDays * DAY).toISOString();
904
827
  if (JSON.stringify(record.expires) !== before) await writeRecord(archiveDir(record.id), record);
905
828
  }
829
+ await pruneWorktreeArchives(now, limits, signal);
906
830
  }, {
907
831
  waitMs: 3e4,
908
832
  signal
@@ -1458,7 +1382,7 @@ async function reclaimIdleProject(path, workspaceExisted, { archive, deleteOwned
1458
1382
  //#region src/workspace/service-guard.ts
1459
1383
  const SERVICE_IN_USE = { code: "STIM_WORKTREE_SERVICE" };
1460
1384
  function inside(folder, canonical) {
1461
- return (value) => typeof value === "string" && value.startsWith("/") && isPathPrefix(folder, canonical(value));
1385
+ return (value) => typeof value === "string" && value.startsWith("/") && isPathPrefix$1(folder, canonical(value));
1462
1386
  }
1463
1387
  function serviceUseOf(plist, file, folder, canonical) {
1464
1388
  if (typeof plist !== "object" || plist === null) return null;
@@ -1537,37 +1461,561 @@ function serviceRunningFrom(folder, canonical, agentsDir = join(homedir(), "Libr
1537
1461
  argument: null
1538
1462
  };
1539
1463
  }
1540
- for (const name of names) {
1541
- const file = join(agentsDir, name);
1542
- const label = name.replace(/\.plist$/, "");
1543
- try {
1544
- if (!readFileSync(file).includes("StimService")) continue;
1545
- } catch {
1546
- return {
1547
- label,
1548
- plist: file,
1549
- argument: null
1550
- };
1464
+ for (const name of names) {
1465
+ const file = join(agentsDir, name);
1466
+ const label = name.replace(/\.plist$/, "");
1467
+ try {
1468
+ if (!readFileSync(file).includes("StimService")) continue;
1469
+ } catch {
1470
+ return {
1471
+ label,
1472
+ plist: file,
1473
+ argument: null
1474
+ };
1475
+ }
1476
+ try {
1477
+ const json = getExecutor().runFile("plutil", [
1478
+ "-convert",
1479
+ "json",
1480
+ "-o",
1481
+ "-",
1482
+ file
1483
+ ], { timeoutMs: 5e3 });
1484
+ const use = serviceUseOf(JSON.parse(json), file, folder, canonical);
1485
+ if (use) return use;
1486
+ } catch {
1487
+ return {
1488
+ label,
1489
+ plist: file,
1490
+ argument: null
1491
+ };
1492
+ }
1493
+ }
1494
+ return null;
1495
+ }
1496
+ /** The Stim CLI entry points removal must not delete: the running one and the `stim` on PATH. */
1497
+ function stimCliEntries(env) {
1498
+ const entries = [];
1499
+ if (process.argv[1]) entries.push({
1500
+ label: "the running stim",
1501
+ path: process.argv[1]
1502
+ });
1503
+ const onPath = getExecutor().findExecutable("stim", env ? { env } : void 0);
1504
+ if (onPath) entries.push({
1505
+ label: "the stim on PATH",
1506
+ path: onPath
1507
+ });
1508
+ return entries;
1509
+ }
1510
+ /** Why removing `folder` would delete a Stim CLI in `entries`, resolved through symlinks; empty when it would not. */
1511
+ function stimCliUseOf(folder, canonical, entries) {
1512
+ return entries.flatMap(({ label, path }) => {
1513
+ const resolved = canonical(path);
1514
+ return isPathPrefix$1(folder, resolved) ? [`it holds ${label} (${resolved})`] : [];
1515
+ });
1516
+ }
1517
+ //#endregion
1518
+ //#region src/workspace/worktree-archive.ts
1519
+ const WATCHMAN_COOKIE = /(?:^|\/)\.watchman-cookie-[^/]*$/;
1520
+ const ARCHIVE_TIMEOUT_MS = 6e5;
1521
+ function execution(operation) {
1522
+ return {
1523
+ ...operation ? childEnvironment(operation.env) : {},
1524
+ signal: operation?.signal,
1525
+ declareFor: operation,
1526
+ killSignal: "SIGKILL"
1527
+ };
1528
+ }
1529
+ function nulList(text) {
1530
+ return text.split("\0").filter(Boolean);
1531
+ }
1532
+ async function changesOf(path, operation) {
1533
+ const read = {
1534
+ ...execution(operation),
1535
+ untrimmed: true,
1536
+ timeoutMs: 6e4
1537
+ };
1538
+ const tracked = nulList(await gitAsync(path, [
1539
+ "diff",
1540
+ "--name-only",
1541
+ "--no-renames",
1542
+ "-z",
1543
+ "HEAD",
1544
+ "--"
1545
+ ], read));
1546
+ const untracked = nulList(await gitAsync(path, [
1547
+ "ls-files",
1548
+ "-z",
1549
+ "--others",
1550
+ "--exclude-standard",
1551
+ "--"
1552
+ ], read)).filter((file) => !WATCHMAN_COOKIE.test(file));
1553
+ return {
1554
+ tracked: tracked.toSorted(),
1555
+ untracked: untracked.toSorted()
1556
+ };
1557
+ }
1558
+ /**
1559
+ * Paths whose staged content differs from the working tree. `git diff HEAD` saves only the working tree, so a staged
1560
+ * version that the working tree no longer holds would be lost.
1561
+ */
1562
+ async function stagedOnly(path, operation) {
1563
+ const read = {
1564
+ ...execution(operation),
1565
+ untrimmed: true,
1566
+ timeoutMs: 6e4
1567
+ };
1568
+ const staged = new Set(nulList(await gitAsync(path, [
1569
+ "diff",
1570
+ "--cached",
1571
+ "--name-only",
1572
+ "--no-renames",
1573
+ "-z",
1574
+ "--"
1575
+ ], read)));
1576
+ return nulList(await gitAsync(path, [
1577
+ "diff",
1578
+ "--name-only",
1579
+ "--no-renames",
1580
+ "-z",
1581
+ "--"
1582
+ ], read)).filter((file) => staged.has(file));
1583
+ }
1584
+ async function headSizes(path, files, operation) {
1585
+ if (!files.length) return 0;
1586
+ if (files.some((file) => file.includes("\n"))) throw new Error("a changed path contains a newline");
1587
+ return (await gitAsync(path, ["cat-file", "--batch-check=%(objectsize)"], {
1588
+ ...execution(operation),
1589
+ timeoutMs: 6e4,
1590
+ input: `${files.map((file) => `HEAD:${file}`).join("\n")}\n`
1591
+ })).split("\n").reduce((sum, line) => sum + (/^\d+$/.test(line.trim()) ? Number(line.trim()) : 0), 0);
1592
+ }
1593
+ async function changedAt(file, deleted) {
1594
+ for (let current = file;; current = dirname(current)) try {
1595
+ const { mtimeMs, ctimeMs } = await lstat(current);
1596
+ return Math.max(mtimeMs, ctimeMs);
1597
+ } catch (error) {
1598
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
1599
+ if (deleted === "skip" || dirname(current) === current) return null;
1600
+ }
1601
+ }
1602
+ /** With `parent`, a deleted file counts its nearest existing parent directory's change, since unlinking changes it. */
1603
+ async function newestChange(path, changes, deleted = "skip") {
1604
+ let newest = null;
1605
+ for (const file of [...changes.tracked, ...changes.untracked]) {
1606
+ const changed = await changedAt(join(path, file), deleted);
1607
+ if (changed !== null && (newest === null || changed > newest)) newest = changed;
1608
+ }
1609
+ return newest;
1610
+ }
1611
+ /**
1612
+ * Plans a bundle of HEAD's commits that the default branch lacks, all of HEAD's history without `origin/HEAD`, so the
1613
+ * restore can recreate HEAD after its branch is deleted and git collects the commits; null when the default branch
1614
+ * already contains HEAD. `bytes` is their size on disk.
1615
+ */
1616
+ async function headBundlePlan(path, head, operation) {
1617
+ const run = {
1618
+ ...execution(operation),
1619
+ timeoutMs: ARCHIVE_TIMEOUT_MS
1620
+ };
1621
+ const base = (await gitQuietAsync(path, [
1622
+ "symbolic-ref",
1623
+ "--quiet",
1624
+ "refs/remotes/origin/HEAD"
1625
+ ], run))?.trim();
1626
+ if (base && await gitQuietAsync(path, [
1627
+ "merge-base",
1628
+ "--is-ancestor",
1629
+ head,
1630
+ base
1631
+ ], run) !== null) return null;
1632
+ const exclude = base ? [`^${base}`] : [];
1633
+ const bytes = Number(await gitAsync(path, [
1634
+ "rev-list",
1635
+ "--disk-usage",
1636
+ "--objects",
1637
+ "HEAD",
1638
+ ...exclude
1639
+ ], run));
1640
+ if (!Number.isFinite(bytes)) throw new Error("git did not report the size of the commits to bundle");
1641
+ return {
1642
+ exclude,
1643
+ bytes
1644
+ };
1645
+ }
1646
+ async function bundleHead(path, plan, dir, operation) {
1647
+ const run = {
1648
+ ...execution(operation),
1649
+ timeoutMs: ARCHIVE_TIMEOUT_MS
1650
+ };
1651
+ const bundle = join(dir, "head.bundle");
1652
+ await gitAsync(path, [
1653
+ "bundle",
1654
+ "create",
1655
+ bundle,
1656
+ "HEAD",
1657
+ ...plan.exclude
1658
+ ], run);
1659
+ await chmod(bundle, 384);
1660
+ await gitAsync(path, [
1661
+ "bundle",
1662
+ "verify",
1663
+ bundle
1664
+ ], run);
1665
+ return bundle;
1666
+ }
1667
+ /**
1668
+ * When a worktree's uncommitted or untracked, non-ignored files last changed, a deleted file by its nearest existing
1669
+ * parent directory: null when it has none, and `unreadable` when git or the files cannot be read. git records no time
1670
+ * for these edits, so the idle checks read the files themselves.
1671
+ */
1672
+ async function uncommittedActivityOf(path, operation) {
1673
+ try {
1674
+ const at = await newestChange(path, await changesOf(path, operation), "parent");
1675
+ return at === null ? null : {
1676
+ at,
1677
+ basis: "an uncommitted file change"
1678
+ };
1679
+ } catch (error) {
1680
+ operation?.signal.throwIfAborted();
1681
+ if (error instanceof Error && "reportCleanup" in error) throw error;
1682
+ return "unreadable";
1683
+ }
1684
+ }
1685
+ function sameList(a, b) {
1686
+ return a.length === b.length && a.every((value, index) => value === b[index]);
1687
+ }
1688
+ function safeName(value) {
1689
+ return value.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[.-]+/, "") || "worktree";
1690
+ }
1691
+ function stamp(at) {
1692
+ return new Date(at).toISOString().replace(/[-:]/g, "").replace(".", "-");
1693
+ }
1694
+ /**
1695
+ * Why the archive no longer describes the worktree, or null when it still does: the same changed and untracked files,
1696
+ * none modified since the archive started, and the patch still reverses cleanly against the working tree.
1697
+ */
1698
+ async function worktreeArchiveMismatch(path, archive, operation) {
1699
+ if (archive.tracked.length) try {
1700
+ await gitAsync(path, [
1701
+ "apply",
1702
+ "--check",
1703
+ "-R",
1704
+ join(archive.dir, "tracked.patch")
1705
+ ], {
1706
+ ...execution(operation),
1707
+ timeoutMs: ARCHIVE_TIMEOUT_MS
1708
+ });
1709
+ } catch (error) {
1710
+ if (error instanceof Error && "reportCleanup" in error) throw error;
1711
+ operation?.signal.throwIfAborted();
1712
+ return `the archived patch does not reverse against the worktree: ${(error instanceof Error ? error.message : String(error)).split("\n")[0]}`;
1713
+ }
1714
+ const staged = await stagedOnly(path, operation);
1715
+ if (staged.length) return `the staged content of ${staged.join(", ")} differs from its working tree`;
1716
+ const now = await changesOf(path, operation);
1717
+ if (!sameList(now.tracked, archive.tracked) || !sameList(now.untracked, archive.untracked)) return "its uncommitted or untracked files changed while they were archived";
1718
+ const newest = await newestChange(path, now);
1719
+ if (newest !== null && newest >= archive.at) return "an uncommitted file changed while it was archived";
1720
+ for (const file of [
1721
+ ...archive.bundled ? ["head.bundle"] : [],
1722
+ ...archive.tracked.length ? ["tracked.patch"] : [],
1723
+ ...archive.untracked.length ? ["untracked.tar"] : []
1724
+ ]) if (!await lstat(join(archive.dir, file)).catch(() => null)) return `its archive lost ${file}`;
1725
+ return null;
1726
+ }
1727
+ /**
1728
+ * Saves a worktree's uncommitted work under `$STIM_HOME/archive/worktrees/<repository>/<branch-or-dir>-<timestamp>/`:
1729
+ * `tracked.patch` (`git diff --binary HEAD`, staged and unstaged changes together), `untracked.tar` (untracked files
1730
+ * git does not ignore) and `manifest.json` naming the branch, HEAD, pull request, files and restore commands. Ignored
1731
+ * files are not saved. Throws, leaving no archive, when archives are disabled or limited to 0, the files exceed
1732
+ * `archive.maxTotalGb` or the disk floor, an untracked nested repository is present, a staged version differs from
1733
+ * the working tree, or the written archive does not verify.
1734
+ */
1735
+ async function archiveWorktreeChanges({ path, repository, branch, head, pullRequest, operation }) {
1736
+ const limits = worktreeArchiveLimits(path, operation?.env);
1737
+ if ("refusal" in limits) throw new Error(limits.refusal);
1738
+ const at = Date.now();
1739
+ const changes = await changesOf(path, operation);
1740
+ const nested = changes.untracked.find((file) => file.endsWith("/"));
1741
+ if (nested) throw new Error(`${nested} is an untracked nested repository`);
1742
+ const staged = await stagedOnly(path, operation);
1743
+ if (staged.length) throw new Error(`the staged content of ${staged.join(", ")} differs from its working tree`);
1744
+ if (!changes.tracked.length && !changes.untracked.length) throw new Error("git reported no change it could save");
1745
+ const retention = async () => {
1746
+ try {
1747
+ await enforceArchiveRetention(void 0, operation);
1748
+ } catch (error) {
1749
+ if (error instanceof Error && "reportCleanup" in error) throw error;
1750
+ operation?.signal.throwIfAborted();
1751
+ }
1752
+ };
1753
+ await retention();
1754
+ const existing = await worktreeArchiveBytes(operation?.signal);
1755
+ let bytes = 0;
1756
+ for (const file of changes.untracked) bytes += (await lstat(join(path, file))).size;
1757
+ let tracked = await headSizes(path, changes.tracked, operation);
1758
+ for (const file of changes.tracked) tracked += (await lstat(join(path, file)).catch(() => null))?.size ?? 0;
1759
+ const plan = await headBundlePlan(path, head, operation);
1760
+ if (existing + bytes + (plan?.bytes ?? 0) > limits.maxBytes) throw new Error(`its untracked files and unmerged commits (${bytes + (plan?.bytes ?? 0)} bytes) do not fit in archive.maxTotalGb beside the other archives`);
1761
+ const [volume] = await readVolumeSpaceAsync([worktreeArchiveRoot()], operation?.signal);
1762
+ const floorMb = resolveBudget({ env: operation?.env }).budget.hardFloorDiskMb;
1763
+ const neededMb = (bytes + 2 * tracked + 2 * (plan?.bytes ?? 0)) / 1024 ** 2;
1764
+ if (!volume || volume.freeMb - neededMb < floorMb) throw new Error(volume ? `writing it would leave less free disk than budget.hardFloorDiskGb on ${volume.volume}` : "free disk for it could not be read");
1765
+ const dir = join(worktreeArchiveRoot(), safeName(basename(repository)), `${safeName(branch?.replace(/^refs\/heads\//, "") ?? basename(path))}-${stamp(at)}-${randomUUID().slice(0, 8)}`);
1766
+ await mkdir(dirname(dir), {
1767
+ recursive: true,
1768
+ mode: 448
1769
+ });
1770
+ const attempt = tryAcquireClaim({
1771
+ root: `${dir}.claims`,
1772
+ mode: "exclusive",
1773
+ label: "worktree archive"
1774
+ });
1775
+ const claim = attempt.acquired;
1776
+ if (!claim) {
1777
+ releaseClaim(attempt.pending);
1778
+ throw new Error("its archive claim is held");
1779
+ }
1780
+ let created = false;
1781
+ const restore = [`git -C ${quotedPath(repository)} worktree add --detach ${quotedPath(path)} ${head}`];
1782
+ try {
1783
+ await mkdir(dir, { mode: 448 });
1784
+ created = true;
1785
+ const bundle = plan ? await bundleHead(path, plan, dir, operation) : null;
1786
+ if (bundle) restore.unshift(`git -C ${quotedPath(repository)} bundle unbundle ${quotedPath(bundle)}`);
1787
+ if (changes.tracked.length) {
1788
+ const patch = join(dir, "tracked.patch");
1789
+ await gitAsync(path, [
1790
+ "diff",
1791
+ "--binary",
1792
+ "--no-color",
1793
+ "--no-ext-diff",
1794
+ "--no-textconv",
1795
+ "--no-relative",
1796
+ "--src-prefix=a/",
1797
+ "--dst-prefix=b/",
1798
+ `--output=${patch}`,
1799
+ "HEAD",
1800
+ "--"
1801
+ ], {
1802
+ ...execution(operation),
1803
+ timeoutMs: ARCHIVE_TIMEOUT_MS
1804
+ });
1805
+ await chmod(patch, 384);
1806
+ restore.push(`git -C ${quotedPath(path)} apply ${quotedPath(patch)}`);
1807
+ }
1808
+ if (changes.untracked.length) {
1809
+ const tarball = join(dir, "untracked.tar");
1810
+ const exec = {
1811
+ ...execution(operation),
1812
+ cwd: path,
1813
+ timeoutMs: ARCHIVE_TIMEOUT_MS
1814
+ };
1815
+ await getExecutor().runFileAsync("tar", [
1816
+ "-cf",
1817
+ tarball,
1818
+ "--null",
1819
+ "-T",
1820
+ "-"
1821
+ ], {
1822
+ ...exec,
1823
+ input: `${changes.untracked.join("\0")}\0`
1824
+ });
1825
+ await chmod(tarball, 384);
1826
+ if (!sameList((await getExecutor().runFileAsync("tar", ["-tf", tarball], {
1827
+ ...exec,
1828
+ untrimmed: true
1829
+ })).split(/\r?\n/).filter(Boolean).toSorted(), changes.untracked)) throw new Error("the untracked tarball does not list every file");
1830
+ restore.push(`tar -xf ${quotedPath(tarball)} -C ${quotedPath(path)}`);
1831
+ }
1832
+ const archive = {
1833
+ dir,
1834
+ at,
1835
+ ...changes,
1836
+ restore,
1837
+ bundled: bundle !== null,
1838
+ claim
1839
+ };
1840
+ const mismatch = await worktreeArchiveMismatch(path, archive, operation);
1841
+ if (mismatch) throw new Error(mismatch);
1842
+ const manifest = {
1843
+ version: 1,
1844
+ createdAt: new Date(at).toISOString(),
1845
+ worktree: path,
1846
+ repository,
1847
+ branch,
1848
+ head,
1849
+ pullRequest,
1850
+ headBundle: bundle ? "head.bundle" : null,
1851
+ trackedPatch: changes.tracked.length ? "tracked.patch" : null,
1852
+ untrackedTarball: changes.untracked.length ? "untracked.tar" : null,
1853
+ tracked: changes.tracked,
1854
+ untracked: changes.untracked,
1855
+ restore
1856
+ };
1857
+ const tmp = join(dir, ".manifest.json.tmp");
1858
+ await writeFile(tmp, `${JSON.stringify(manifest, null, 2)}\n`, { mode: 384 });
1859
+ await rename(tmp, join(dir, "manifest.json"));
1860
+ const total = await worktreeArchiveBytes(operation?.signal);
1861
+ if (total > limits.maxBytes) throw new Error(`the archive (${total - existing} bytes) does not fit in archive.maxTotalGb beside the other archives`);
1862
+ const [after] = await readVolumeSpaceAsync([dir], operation?.signal);
1863
+ if (!after || after.freeMb < floorMb) throw new Error(`it left less free disk than budget.hardFloorDiskGb on ${after?.volume ?? "its volume"}`);
1864
+ await retention();
1865
+ if (!await lstat(join(dir, "manifest.json")).catch(() => null)) throw new Error("archive retention removed it");
1866
+ return archive;
1867
+ } catch (error) {
1868
+ try {
1869
+ if (created) await rm(dir, {
1870
+ recursive: true,
1871
+ force: true
1872
+ });
1873
+ } finally {
1874
+ releaseClaim(claim);
1875
+ }
1876
+ throw error;
1877
+ }
1878
+ }
1879
+ /** Deletes an archive whose worktree was kept intact, so a retried removal does not pile up copies. */
1880
+ async function discardWorktreeArchive(archive) {
1881
+ await rm(archive.dir, {
1882
+ recursive: true,
1883
+ force: true
1884
+ });
1885
+ releaseWorktreeArchive(archive);
1886
+ }
1887
+ /** Lets retention manage the archive once the removal that needed it has ended. */
1888
+ function releaseWorktreeArchive(archive) {
1889
+ releaseClaim(archive.claim);
1890
+ }
1891
+ //#endregion
1892
+ //#region src/maintenance/measure.ts
1893
+ function measureDisk() {
1894
+ return readVolumeSpace([
1895
+ configDir(),
1896
+ ...Object.keys(loadConfig()?.projects ?? {}),
1897
+ buildWorkerRoot(loadConfig())
1898
+ ]);
1899
+ }
1900
+ function measureDiskAsync(operation) {
1901
+ return readVolumeSpaceAsync([
1902
+ configDir(),
1903
+ ...Object.keys(loadConfig()?.projects ?? {}),
1904
+ buildWorkerRoot(loadConfig())
1905
+ ], operation.signal);
1906
+ }
1907
+ /** The disk pressure level of the fullest volume Stim writes to, measured now. */
1908
+ function currentDiskLevel(disk = measureDisk()) {
1909
+ return diskPressureLevel(lowestFree(disk), resolveBudget().budget, resolveMaintenanceSettings());
1910
+ }
1911
+ function measurePressure(settings, previous, now) {
1912
+ return pressureFromObservations(settings, previous, now, readHostMemoryPressure(), measureDisk());
1913
+ }
1914
+ async function measurePressureAsync(settings, previous, now, operation) {
1915
+ return pressureFromObservations(settings, previous, now, await readHostMemoryPressureAsync(void 0, void 0, operation.signal, operation), await readVolumeSpaceAsync([
1916
+ configDir(),
1917
+ ...Object.keys(loadConfig()?.projects ?? {}),
1918
+ buildWorkerRoot(loadConfig())
1919
+ ], operation.signal));
1920
+ }
1921
+ function pressureFromObservations(settings, previous, now, level, disk) {
1922
+ const availableBytes = process.platform === "darwin" ? null : freemem();
1923
+ const warningSince = level === "warning" ? previous?.warningSince ?? now : null;
1924
+ return {
1925
+ disk,
1926
+ memory: {
1927
+ level,
1928
+ availableBytes,
1929
+ pressured: settings.memoryPressureLevel !== "off" && (level === "critical" || settings.memoryPressureLevel === "warning" && warningSince !== null && now - warningSince >= settings.memoryWarningMinutes * 6e4 || availableBytes !== null && availableBytes < (settings.minAvailableMemoryGb === void 0 ? totalmem() * .1 : settings.minAvailableMemoryGb * 1024 ** 3))
1930
+ },
1931
+ warningSince
1932
+ };
1933
+ }
1934
+ function sizeScanDeferred(settings) {
1935
+ return loadavg()[0] / Math.max(1, cpus().length) > settings.maxLoadPerCore;
1936
+ }
1937
+ function cacheBlocked(entry, discovered = discoverCaches(), env = process.env) {
1938
+ return planCacheEmptying([{
1939
+ ...entry,
1940
+ note: "",
1941
+ prune: "entries",
1942
+ ...discovered.some((cache) => canonicalPath(cache.dir) === canonicalPath(entry.dir) && cache.source === "registered") ? { source: "registered" } : {}
1943
+ }], false, env)[0]?.machineGlobal ?? (entry.category === "compilation-cache" ? cacheUser(entry.dir) : void 0);
1944
+ }
1945
+ async function measureSizes(now, failure) {
1946
+ const sizes = [];
1947
+ const outputs = await collectWorkspaceOutputs({
1948
+ olderThan: null,
1949
+ now,
1950
+ sizeTimeoutMs: 12e4
1951
+ });
1952
+ for (const entry of outputs.workspaces) {
1953
+ if (entry.bytes === null) {
1954
+ failure(entry.dir, entry.projectRoot ?? void 0);
1955
+ continue;
1956
+ }
1957
+ sizes.push({
1958
+ name: "workspace build outputs",
1959
+ category: "workspace-outputs",
1960
+ dir: entry.dir,
1961
+ bytes: entry.bytes,
1962
+ measuredAt: now,
1963
+ ...entry.projectRoot ? { workspace: canonicalPath(entry.projectRoot) } : {},
1964
+ idleDays: entry.idleDays,
1965
+ ...!entry.willClear ? { blocked: entry.keptReason ?? "workspace ownership is unresolved" } : {}
1966
+ });
1967
+ }
1968
+ const known = [
1969
+ {
1970
+ name: "shared build cache",
1971
+ dir: buildCacheRoot(),
1972
+ category: "build-cache"
1973
+ },
1974
+ {
1975
+ name: "Metro transform caches",
1976
+ dir: metroCacheRoot(),
1977
+ category: "metro-cache"
1978
+ },
1979
+ {
1980
+ name: "ccache",
1981
+ dir: sharedCcache(),
1982
+ category: "ccache"
1983
+ },
1984
+ {
1985
+ name: "Swift compilation cache",
1986
+ dir: sharedCompilationCache(),
1987
+ category: "compilation-cache"
1551
1988
  }
1552
- try {
1553
- const json = getExecutor().runFile("plutil", [
1554
- "-convert",
1555
- "json",
1556
- "-o",
1557
- "-",
1558
- file
1559
- ], { timeoutMs: 5e3 });
1560
- const use = serviceUseOf(JSON.parse(json), file, folder, canonical);
1561
- if (use) return use;
1562
- } catch {
1563
- return {
1564
- label,
1565
- plist: file,
1566
- argument: null
1567
- };
1989
+ ];
1990
+ const discovered = discoverCaches();
1991
+ for (const cache of discovered) {
1992
+ if (cache.source !== "registered" && !canonicalPath(cache.dir).startsWith(`${canonicalPath(configDir())}/`)) continue;
1993
+ if (cache.prune === "report-only") continue;
1994
+ const dir = canonicalPath(cache.dir);
1995
+ if (known.some((item) => dir === canonicalPath(item.dir) || dir.startsWith(`${canonicalPath(item.dir)}/`))) continue;
1996
+ const category = cache.name.startsWith("Metro transform cache") ? "metro-cache" : /^(Expo build cache|native build cache|Build cache)$/.test(cache.name) ? "build-cache" : "other";
1997
+ known.push({
1998
+ name: cache.name,
1999
+ dir: cache.dir,
2000
+ category
2001
+ });
2002
+ }
2003
+ for (const entry of known) {
2004
+ if (!existsSync(entry.dir)) continue;
2005
+ const bytes = await measuredDirectorySizeAsync(entry.dir, { timeoutMs: 12e4 });
2006
+ if (bytes === null) {
2007
+ failure(entry.dir);
2008
+ continue;
1568
2009
  }
2010
+ const blocked = cacheBlocked(entry, discovered);
2011
+ sizes.push({
2012
+ ...entry,
2013
+ bytes,
2014
+ measuredAt: now,
2015
+ ...blocked ? { blocked } : {}
2016
+ });
1569
2017
  }
1570
- return null;
2018
+ return sizes;
1571
2019
  }
1572
2020
  //#endregion
1573
2021
  //#region src/engine/warm-claim.ts
@@ -1827,9 +2275,11 @@ function checkoutFactLine(branch, plan, head) {
1827
2275
  /**
1828
2276
  * Whether to install, decided against the lockfile as it stands now rather than against what HEAD did.
1829
2277
  * A lockfile that did not move is not evidence that its dependencies are installed: an install that was
1830
- * interrupted leaves a present node_modules behind and moves HEAD no further.
2278
+ * interrupted leaves a present node_modules behind and moves HEAD no further. A `patches/` change counts
2279
+ * as a dependency change because patch-package applies it only on a fresh install. A tree `npm ls` already
2280
+ * rejected right after the last completed install, with the same problems, is not one another install would fix.
1831
2281
  */
1832
- function depsPlan({ lockfile, lockfileChanged, installed, treeValid, lastInstall, lockHash }) {
2282
+ function depsPlan({ lockfile, lockfileChanged, patchesChanged, installed, treeProblems, lastInstall, lockHash, patchesHash }) {
1833
2283
  if (!lockfile) return {
1834
2284
  run: false,
1835
2285
  reason: "no lockfile in this repository"
@@ -1838,7 +2288,7 @@ function depsPlan({ lockfile, lockfileChanged, installed, treeValid, lastInstall
1838
2288
  run: true,
1839
2289
  reason: "no installed dependencies"
1840
2290
  };
1841
- if (treeValid === false) return {
2291
+ if (treeProblems && lastInstall?.treeProblems !== treeProblems) return {
1842
2292
  run: true,
1843
2293
  reason: `the installed tree does not match ${lockfile}`
1844
2294
  };
@@ -1855,6 +2305,10 @@ function depsPlan({ lockfile, lockfileChanged, installed, treeValid, lastInstall
1855
2305
  run: true,
1856
2306
  reason: `${lockfile} does not match the last completed install`
1857
2307
  };
2308
+ if (lastInstall.patches !== patchesHash) return {
2309
+ run: true,
2310
+ reason: "patches/ does not match the last completed install"
2311
+ };
1858
2312
  return {
1859
2313
  run: false,
1860
2314
  reason: `${lockfile} matches the last completed install`
@@ -1864,6 +2318,10 @@ function depsPlan({ lockfile, lockfileChanged, installed, treeValid, lastInstall
1864
2318
  run: true,
1865
2319
  reason: `${lockfile} changed`
1866
2320
  };
2321
+ if (patchesChanged) return {
2322
+ run: true,
2323
+ reason: "patches/ changed"
2324
+ };
1867
2325
  return {
1868
2326
  run: false,
1869
2327
  reason: `${lockfile} unchanged`
@@ -1875,18 +2333,51 @@ function depsPlan({ lockfile, lockfileChanged, installed, treeValid, lastInstall
1875
2333
  * nothing about this one, and no record at all is the state of every repository before its first refresh.
1876
2334
  */
1877
2335
  function incompleteInstallRefusal(root, appDir) {
1878
- const dependencies = dependencyState(existsSync(appDir) ? appDir : root);
1879
- if (!dependencies) return null;
1880
- const last = readInstallEvidence(dependencies.root);
1881
- if (!last || last.completed || last.lock !== dependencies.lock) return null;
1882
- const hash = lockfileHash(dependencies.root, dependencies.lock);
1883
- if (hash === null || last.hash !== hash) return null;
1884
- return {
1885
- code: "STIM_DEPS_INCOMPLETE",
1886
- message: `Refusing to copy from ${dependencies.root}: the last install of ${dependencies.lock} there did not finish, so its dependencies are partial.`,
1887
- lines: [],
1888
- remedy: `Run \`stim worktree warm --refresh\` to install them in the source checkout -- it reinstalls rather than skipping, because no completed install of this ${dependencies.lock} is recorded -- then warm again.`
1889
- };
2336
+ for (const dependencies of dependencyStates(root, appDir)) {
2337
+ const last = readInstallEvidence(dependencies.root);
2338
+ if (!last || last.completed || last.lock !== dependencies.lock) continue;
2339
+ const hash = lockfileHash(dependencies.root, dependencies.lock);
2340
+ if (hash === null || last.hash !== hash) continue;
2341
+ return {
2342
+ code: "STIM_DEPS_INCOMPLETE",
2343
+ message: `Refusing to copy from ${dependencies.root}: the last install of ${dependencies.lock} there did not finish, so its dependencies are partial.`,
2344
+ lines: [],
2345
+ remedy: `Run \`stim worktree warm --refresh\` to install them in the source checkout -- it reinstalls rather than skipping, because no completed install of this ${dependencies.lock} is recorded -- then warm again.`
2346
+ };
2347
+ }
2348
+ return null;
2349
+ }
2350
+ function dependencyStates(root, appDir) {
2351
+ const app = dependencyState(existsSync(appDir) ? appDir : root);
2352
+ const top = dependencyState(root, root);
2353
+ return [app, top && top.root !== app?.root && (hasInstalledDependencies(top.root, top.installed) || readInstallEvidence(top.root)) ? top : null].filter((state) => state !== null);
2354
+ }
2355
+ /**
2356
+ * An npm project whose own `.npmrc` sets `ignore-scripts` gets no lifecycle scripts from `npm ci`, its own
2357
+ * postinstall included. `npm run` still runs a named script under that setting, so the project's postinstall
2358
+ * (usually patch-package) runs explicitly; dependencies' install scripts stay blocked.
2359
+ */
2360
+ function skippedPostinstall(dependencies) {
2361
+ if (dependencies.lock !== "package-lock.json") return false;
2362
+ let postinstall;
2363
+ try {
2364
+ postinstall = JSON.parse(readFileSync(join(dependencies.root, "package.json"), "utf-8"))?.scripts?.postinstall;
2365
+ } catch {
2366
+ return false;
2367
+ }
2368
+ if (typeof postinstall !== "string" || !postinstall.trim()) return false;
2369
+ try {
2370
+ return getExecutor().runFile("npm", [
2371
+ "config",
2372
+ "get",
2373
+ "ignore-scripts"
2374
+ ], {
2375
+ cwd: dependencies.root,
2376
+ timeoutMs: 3e4
2377
+ }) === "true";
2378
+ } catch {
2379
+ return false;
2380
+ }
1890
2381
  }
1891
2382
  function podsPlan({ hasIos, hasPodfile, podfileLockChanged, stale, dir = "ios/" }) {
1892
2383
  if (!hasIos) return {
@@ -1938,6 +2429,22 @@ function lockfileHash(depsRoot, lockfile) {
1938
2429
  return null;
1939
2430
  }
1940
2431
  }
2432
+ function hashPatches(depsRoot) {
2433
+ const dir = join(depsRoot, "patches");
2434
+ let names;
2435
+ try {
2436
+ names = readdirSync(dir, {
2437
+ recursive: true,
2438
+ withFileTypes: true
2439
+ }).filter((entry) => entry.isFile() && entry.name.endsWith(".patch")).map((entry) => relative(dir, join(entry.parentPath, entry.name))).toSorted();
2440
+ } catch {
2441
+ return "";
2442
+ }
2443
+ if (!names.length) return "";
2444
+ const hash = createHash("sha256");
2445
+ for (const name of names) hash.update(name).update("\0").update(readFileSync(join(dir, name))).update("\0");
2446
+ return hash.digest("hex");
2447
+ }
1941
2448
  function readInstallEvidence(depsRoot) {
1942
2449
  try {
1943
2450
  const parsed = JSON.parse(readFileSync(installRecordFile(depsRoot), "utf-8"));
@@ -1946,21 +2453,19 @@ function readInstallEvidence(depsRoot) {
1946
2453
  return {
1947
2454
  lock: parsed.lock,
1948
2455
  hash: parsed.hash,
1949
- completed: parsed.completed === true
2456
+ patches: typeof parsed.patches === "string" ? parsed.patches : "",
2457
+ completed: parsed.completed === true,
2458
+ treeProblems: typeof parsed.treeProblems === "string" ? parsed.treeProblems : null
1950
2459
  };
1951
2460
  } catch {
1952
2461
  return null;
1953
2462
  }
1954
2463
  }
1955
- function writeInstallEvidence(depsRoot, lockfile, hash, completed) {
2464
+ function writeInstallEvidence(depsRoot, evidence) {
1956
2465
  const file = installRecordFile(depsRoot);
1957
2466
  const tmp = `${file}.${process.pid}.${randomUUID()}.tmp`;
1958
2467
  mkdirSync(join(getConfigDir(), "warm-installs"), { recursive: true });
1959
- writeFileSync(tmp, JSON.stringify({
1960
- lock: lockfile,
1961
- hash: hash ?? "",
1962
- completed
1963
- }));
2468
+ writeFileSync(tmp, JSON.stringify(evidence));
1964
2469
  try {
1965
2470
  renameSync(tmp, file);
1966
2471
  } catch (error) {
@@ -1968,8 +2473,17 @@ function writeInstallEvidence(depsRoot, lockfile, hash, completed) {
1968
2473
  throw error;
1969
2474
  }
1970
2475
  }
2476
+ function pendingInstallEvidence(dependencies) {
2477
+ return {
2478
+ lock: dependencies.lock,
2479
+ hash: lockfileHash(dependencies.root, dependencies.lock) ?? "",
2480
+ patches: dependencies.lock === "package-lock.json" ? hashPatches(dependencies.root) : "",
2481
+ completed: false,
2482
+ treeProblems: null
2483
+ };
2484
+ }
1971
2485
  function restoreInstallEvidence(depsRoot, prior) {
1972
- if (prior) writeInstallEvidence(depsRoot, prior.lock, prior.hash, prior.completed);
2486
+ if (prior) writeInstallEvidence(depsRoot, prior);
1973
2487
  else rmSync(installRecordFile(depsRoot), { force: true });
1974
2488
  }
1975
2489
  function inProgressOperation(gitDir) {
@@ -2183,10 +2697,13 @@ async function refreshMainCheckout({ root, appDir, settings, iosProject, emit, s
2183
2697
  const plan = checkoutPlan(locallyKnownUpstream(root));
2184
2698
  if (plan.kind === "diverged") return divergedRefusal(root, branch, plan);
2185
2699
  if (inspectOnly && plan.kind === "fast-forward") return { mutation: `fast-forward ${plan.behind} commit${plan.behind === 1 ? "" : "s"}` };
2186
- const depsBefore = dependencyState(existsSync(appDir) ? appDir : root);
2187
- const priorInstall = depsBefore ? readInstallEvidence(depsBefore.root) : null;
2188
- const marked = plan.kind === "fast-forward" ? depsBefore : null;
2189
- if (marked) writeInstallEvidence(marked.root, marked.lock, lockfileHash(marked.root, marked.lock), false);
2700
+ const depsBefore = dependencyStates(root, appDir);
2701
+ const priorInstalls = new Map(depsBefore.map((entry) => [entry.root, readInstallEvidence(entry.root)]));
2702
+ const marked = plan.kind === "fast-forward" ? depsBefore : [];
2703
+ for (const entry of marked) writeInstallEvidence(entry.root, pendingInstallEvidence(entry));
2704
+ const restoreMarked = (depsRoot) => {
2705
+ if (marked.some((entry) => entry.root === depsRoot)) restoreInstallEvidence(depsRoot, priorInstalls.get(depsRoot) ?? null);
2706
+ };
2190
2707
  let head = before;
2191
2708
  if (plan.kind === "fast-forward") {
2192
2709
  const merged = fastForward(root, plan.upstream);
@@ -2201,43 +2718,71 @@ async function refreshMainCheckout({ root, appDir, settings, iosProject, emit, s
2201
2718
  emit(checkoutFactLine(branch, plan, head));
2202
2719
  const note = defaultBranchNote(branch, resolveDefaultBranch(root, settings));
2203
2720
  if (note.kind !== "match") for (const line of note.lines) emit(phaseLine("", line));
2204
- const dependencies = dependencyState(existsSync(appDir) ? appDir : root);
2205
- const installed = dependencies ? hasInstalledDependencies(dependencies.root, dependencies.installed) : false;
2206
- const consumed = dependencies ? lockfileHash(dependencies.root, dependencies.lock) : null;
2207
- const deps = depsPlan({
2208
- lockfile: dependencies?.lock ?? null,
2209
- lockfileChanged: dependencies ? pathChangedBetween(root, before, head, gitPath(relative(root, dependencies.root), dependencies.lock)) : false,
2210
- installed,
2211
- treeValid: dependencies?.lock === "package-lock.json" && installed ? installedNpmTreeIsValid(dependencies.root) : null,
2212
- lastInstall: priorInstall,
2213
- lockHash: consumed
2214
- });
2215
- const depsSubject = `source ${dependencies?.root ?? root}: ${deps.reason}`;
2216
- if (deps.run && dependencies) {
2217
- if (inspectOnly) return { mutation: deps.reason };
2218
- writeInstallEvidence(dependencies.root, dependencies.lock, consumed, false);
2219
- const install = await runInstallCommand({
2220
- command: dependencies.command,
2221
- cwd: dependencies.root,
2222
- spawnFn: spawn,
2223
- now,
2224
- heartbeatMs,
2225
- onHeartbeat: emit
2721
+ const dependencyRoots = dependencyStates(root, appDir);
2722
+ for (const entry of marked) if (!dependencyRoots.some((other) => other.root === entry.root)) restoreMarked(entry.root);
2723
+ if (!dependencyRoots.length) emit(stepLine("deps", `source ${root}: no lockfile in this repository`, "skipped"));
2724
+ const owed = [];
2725
+ for (const dependencies of dependencyRoots) {
2726
+ const installed = hasInstalledDependencies(dependencies.root, dependencies.installed);
2727
+ const depsDir = relative(root, dependencies.root);
2728
+ const consumed = pendingInstallEvidence(dependencies);
2729
+ const deps = depsPlan({
2730
+ lockfile: dependencies.lock,
2731
+ lockfileChanged: pathChangedBetween(root, before, head, gitPath(depsDir, dependencies.lock)),
2732
+ patchesChanged: dependencies.lock === "package-lock.json" && pathChangedBetween(root, before, head, gitPath(depsDir, "patches/*.patch")),
2733
+ installed,
2734
+ treeProblems: dependencies.lock === "package-lock.json" && installed ? installedNpmTreeProblems(dependencies.root) : null,
2735
+ lastInstall: priorInstalls.has(dependencies.root) ? priorInstalls.get(dependencies.root) ?? null : readInstallEvidence(dependencies.root),
2736
+ lockHash: consumed.hash || null,
2737
+ patchesHash: consumed.patches
2226
2738
  });
2227
- emit(stepLine("deps", depsSubject, `${dependencies.command} (${formatDuration(install.durationMs)})`));
2228
- const running = await awaitSpawnedWork(`\`${dependencies.command}\``);
2229
- if (!install.ok) return {
2230
- code: DEPS_ERROR,
2231
- message: String(install.reason),
2232
- lines: install.lastLines,
2233
- remedy: `Run \`cd ${dependencies.root} && ${dependencies.command}\` and fix what it reports, then run warm again.`
2234
- };
2235
- if (running) return running;
2236
- writeInstallEvidence(dependencies.root, dependencies.lock, consumed, true);
2237
- } else {
2238
- if (marked) restoreInstallEvidence(marked.root, priorInstall);
2739
+ const depsSubject = `source ${dependencies.root}: ${deps.reason}`;
2740
+ if (deps.run) {
2741
+ if (inspectOnly) return { mutation: deps.reason };
2742
+ owed.push({
2743
+ dependencies,
2744
+ subject: depsSubject
2745
+ });
2746
+ continue;
2747
+ }
2748
+ restoreMarked(dependencies.root);
2239
2749
  emit(stepLine("deps", depsSubject, "skipped"));
2240
2750
  }
2751
+ for (const { dependencies, subject: depsSubject } of owed) {
2752
+ const consumed = pendingInstallEvidence(dependencies);
2753
+ writeInstallEvidence(dependencies.root, consumed);
2754
+ const runStep = async (command, subject, remedy) => {
2755
+ const install = await runInstallCommand({
2756
+ command,
2757
+ cwd: dependencies.root,
2758
+ spawnFn: spawn,
2759
+ now,
2760
+ heartbeatMs,
2761
+ onHeartbeat: emit
2762
+ });
2763
+ emit(stepLine("deps", subject, `${command} (${formatDuration(install.durationMs)})`));
2764
+ const running = await awaitSpawnedWork(`\`${command}\``);
2765
+ if (!install.ok) return {
2766
+ code: DEPS_ERROR,
2767
+ message: String(install.reason),
2768
+ lines: install.lastLines,
2769
+ remedy: `Run \`cd ${dependencies.root} && ${remedy}\` and fix what it reports, then run warm again.`
2770
+ };
2771
+ return running;
2772
+ };
2773
+ const postinstall = skippedPostinstall(dependencies) ? "npm run postinstall" : null;
2774
+ const installFailure = await runStep(dependencies.command, depsSubject, postinstall ? `${dependencies.command} && ${postinstall}` : dependencies.command);
2775
+ if (installFailure) return installFailure;
2776
+ if (postinstall) {
2777
+ const postinstallFailure = await runStep(postinstall, `source ${dependencies.root}: ignore-scripts skipped postinstall`, postinstall);
2778
+ if (postinstallFailure) return postinstallFailure;
2779
+ }
2780
+ writeInstallEvidence(dependencies.root, {
2781
+ ...consumed,
2782
+ completed: true,
2783
+ treeProblems: dependencies.lock === "package-lock.json" ? installedNpmTreeProblems(dependencies.root) : null
2784
+ });
2785
+ }
2241
2786
  const app = relative(root, appDir) || ".";
2242
2787
  const where = `source ${appDir}: `;
2243
2788
  const ios = iosProject;
@@ -2274,205 +2819,6 @@ async function refreshMainCheckout({ root, appDir, settings, iosProject, emit, s
2274
2819
  return podsRunning;
2275
2820
  }
2276
2821
  //#endregion
2277
- //#region src/workspace/worktree-trash.ts
2278
- const TRASH_MANIFEST = "trash.json";
2279
- const TRASH_ENTRY_PREFIX = "remove-";
2280
- const TRASH_ENTRIES = "entries";
2281
- const TRASH_CLAIMS = "claims";
2282
- const LIST_TIMEOUT_MS = 3e4;
2283
- const DELETE_NICENESS = "10";
2284
- function rethrowCancellation(error, signal) {
2285
- if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
2286
- signal?.throwIfAborted();
2287
- }
2288
- async function isPlainDirectory(path) {
2289
- const stat = await lstat(path).catch(() => null);
2290
- return Boolean(stat?.isDirectory());
2291
- }
2292
- async function ignoredDirectories(worktree, options) {
2293
- const out = await gitAsync(worktree, [
2294
- "ls-files",
2295
- "--others",
2296
- "--ignored",
2297
- "--exclude-standard",
2298
- "--directory",
2299
- "--no-empty-directory",
2300
- "-z"
2301
- ], {
2302
- ...options.env ? childEnvironment(options.env) : {},
2303
- signal: options.signal,
2304
- declareFor: options.declareFor,
2305
- timeoutMs: LIST_TIMEOUT_MS,
2306
- untrimmed: true
2307
- });
2308
- const found = [];
2309
- for (const entry of out.split("\0")) {
2310
- if (!entry.endsWith("/")) continue;
2311
- const rel = entry.slice(0, -1);
2312
- const parts = rel.split("/");
2313
- let plain = true;
2314
- for (let depth = 1; depth <= parts.length && plain; depth++) plain = await isPlainDirectory(join(worktree, ...parts.slice(0, depth)));
2315
- if (plain) found.push(rel);
2316
- }
2317
- return found;
2318
- }
2319
- /**
2320
- * Moves the worktree's ignored directories, which `git worktree remove` would delete, into a new entry under
2321
- * `$STIM_HOME/trash` held by an exclusive ownership claim. Null, with nothing moved, when the trash is on another
2322
- * filesystem, the platform is Windows, git cannot list the entries, or the claim cannot be taken. A directory that
2323
- * does not rename stays in the worktree for git to delete.
2324
- */
2325
- async function moveIgnoredTreesToTrash(worktree, options = {}) {
2326
- if ((options.platform ?? process.platform) === "win32") return null;
2327
- let listed;
2328
- try {
2329
- listed = await ignoredDirectories(worktree, options);
2330
- } catch (error) {
2331
- rethrowCancellation(error, options.signal);
2332
- return null;
2333
- }
2334
- if (!listed.length) return null;
2335
- const root = trashRoot();
2336
- let dir = null;
2337
- let claim = null;
2338
- const entries = listed.map((from, index) => ({
2339
- from,
2340
- to: String(index)
2341
- }));
2342
- try {
2343
- await mkdir(root, { recursive: true });
2344
- if (filesystemDevice(root) !== filesystemDevice(worktree)) return null;
2345
- dir = await mkdtemp(join(root, TRASH_ENTRY_PREFIX));
2346
- claim = (0, ownership_claim_exports.tryAcquireClaim)({
2347
- root: join(dir, "claims"),
2348
- mode: "exclusive",
2349
- label: "worktree trash"
2350
- }).acquired ?? null;
2351
- if (!claim) throw new Error("the trash entry claim was not acquired");
2352
- const manifest = {
2353
- worktree,
2354
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
2355
- };
2356
- await writeFile(join(dir, TRASH_MANIFEST), `${JSON.stringify(manifest)}\n`);
2357
- await mkdir(join(dir, TRASH_ENTRIES));
2358
- } catch (error) {
2359
- if (claim) (0, ownership_claim_exports.releaseClaim)(claim);
2360
- if (dir) await rm(dir, {
2361
- recursive: true,
2362
- force: true
2363
- }).catch(() => {});
2364
- rethrowCancellation(error, options.signal);
2365
- return null;
2366
- }
2367
- const moved = [];
2368
- for (const entry of entries) try {
2369
- await rename(join(worktree, entry.from), join(dir, TRASH_ENTRIES, entry.to));
2370
- moved.push(entry);
2371
- } catch {}
2372
- const trash = {
2373
- dir,
2374
- worktree,
2375
- moved,
2376
- claim
2377
- };
2378
- if (moved.length) return trash;
2379
- await discardTrash(trash);
2380
- return null;
2381
- }
2382
- async function discardTrash(trash) {
2383
- await rmdir(join(trash.dir, TRASH_ENTRIES)).catch(() => {});
2384
- await rm(join(trash.dir, TRASH_MANIFEST), { force: true }).catch(() => {});
2385
- (0, ownership_claim_exports.releaseClaim)(trash.claim);
2386
- await rmdir(trash.dir).catch(() => {});
2387
- }
2388
- /** Moves every tree back into the worktree. Returns the relative paths that stay in the trash for gc. */
2389
- async function restoreTrashedTrees(trash) {
2390
- const kept = [];
2391
- for (const entry of trash.moved.toReversed()) {
2392
- const target = join(trash.worktree, entry.from);
2393
- try {
2394
- if (await lstat(target).catch(() => null) || !await isPlainDirectory(dirname(target))) {
2395
- kept.push(entry.from);
2396
- continue;
2397
- }
2398
- await rename(join(trash.dir, TRASH_ENTRIES, entry.to), target);
2399
- } catch {
2400
- kept.push(entry.from);
2401
- }
2402
- }
2403
- if (kept.length) (0, ownership_claim_exports.releaseClaim)(trash.claim);
2404
- else await discardTrash(trash);
2405
- return kept;
2406
- }
2407
- /** Deletes one trash entry: `rm -rf` for the bulk, then a walk that also clears read-only directories. */
2408
- async function deleteTrashEntry(dir, operation) {
2409
- if (process.platform !== "win32") try {
2410
- await runOperationFile(operation, getExecutor(), "rm", [
2411
- "-rf",
2412
- "--",
2413
- join(dir, TRASH_ENTRIES)
2414
- ], {
2415
- timeoutMs: "unbounded",
2416
- signal: operation?.signal,
2417
- cwd: dirname(dir)
2418
- });
2419
- } catch (error) {
2420
- rethrowCancellation(error, operation?.signal);
2421
- }
2422
- await removeTemporaryEntryAsync(dir, operation?.signal);
2423
- }
2424
- function deleteInBackground(trash) {
2425
- try {
2426
- (0, ownership_claim_exports.markClaimChildPending)(trash.claim);
2427
- const child = getExecutor().spawn("nice", [
2428
- "-n",
2429
- DELETE_NICENESS,
2430
- "rm",
2431
- "-rf",
2432
- "--",
2433
- join(trash.dir, TRASH_ENTRIES),
2434
- trash.dir
2435
- ], {
2436
- cwd: dirname(trash.dir),
2437
- detached: true,
2438
- stdio: "ignore"
2439
- });
2440
- child.on?.("error", () => {});
2441
- child.unref?.();
2442
- const captured = child.pid === void 0 ? null : captureProcessIdentity(child.pid);
2443
- if (!captured?.ok || child.pid === void 0) throw new Error("the trash deletion process was not recorded");
2444
- (0, ownership_claim_exports.setClaimChild)(trash.claim, {
2445
- pid: child.pid,
2446
- processToken: captured.token
2447
- });
2448
- return true;
2449
- } catch {
2450
- try {
2451
- (0, ownership_claim_exports.clearClaimChild)(trash.claim);
2452
- } catch {}
2453
- (0, ownership_claim_exports.releaseClaim)(trash.claim);
2454
- return false;
2455
- }
2456
- }
2457
- /**
2458
- * Deletes the trash entry once git removed the worktree. A CLI command starts a detached, low-priority `rm` recorded
2459
- * as the claim's child, so gc reports the entry as in use until `rm` exits. Inside a bounded report, such as the
2460
- * programmatic API, every child is stopped when the report settles, so the entry is deleted before returning.
2461
- * `left` means the entry waits for gc with its claim released.
2462
- */
2463
- async function disposeTrash(trash, operation) {
2464
- if (!getExecutor().reportSignal?.()) return deleteInBackground(trash) ? "background" : "left";
2465
- try {
2466
- await deleteTrashEntry(trash.dir, operation);
2467
- return "deleted";
2468
- } catch (error) {
2469
- rethrowCancellation(error, operation?.signal);
2470
- return "left";
2471
- } finally {
2472
- (0, ownership_claim_exports.releaseClaim)(trash.claim);
2473
- }
2474
- }
2475
- //#endregion
2476
2822
  //#region src/commands/worktree.ts
2477
2823
  function dependencyInstallCommand(target, dir = ".") {
2478
2824
  const installRoot = resolve$1(target, dir);
@@ -2529,7 +2875,7 @@ function reportRefreshFailure(failure, out) {
2529
2875
  out(chalk.red(`failed: ${failure.code}`));
2530
2876
  }
2531
2877
  function mainCheckoutAppDir(root, target, cwd) {
2532
- const app = findProjectRoot$1(cwd);
2878
+ const app = warmedWorkspace(root, target, cwd) ?? findProjectRoot$1(cwd);
2533
2879
  if (!app) return root;
2534
2880
  const rel = relative$1(target, app);
2535
2881
  if (!rel || rel.startsWith("..") || isAbsolute$1(rel)) return root;
@@ -2537,20 +2883,26 @@ function mainCheckoutAppDir(root, target, cwd) {
2537
2883
  }
2538
2884
  /**
2539
2885
  * The app workspace a warm prepares: the nearest app below the worktree root, or else the one app registered in
2540
- * the source checkout, at the same place in this worktree, or else the worktree root when it is the app. A monorepo
2541
- * root can declare react-native without being the app.
2886
+ * the source checkout, at the same place in this worktree, or else the one registered in this worktree, or else
2887
+ * the worktree root when it is the app. A monorepo root can declare react-native without being the app.
2542
2888
  */
2543
2889
  function warmedWorkspace(root, target, cwd) {
2544
2890
  const nearest = findProjectRoot$1(cwd);
2545
- const app = nearest && isPathPrefix(target, nearest) && isMobileProject(nearest) ? nearest : null;
2891
+ const app = nearest && isPathPrefix$1(target, nearest) && isMobileProject(nearest) ? nearest : null;
2546
2892
  if (app && app !== target) return app;
2547
- const apps = /* @__PURE__ */ new Set();
2548
- for (const path of Object.keys(loadConfig$1()?.projects ?? {})) {
2549
- const rel = relative$1(root, path);
2550
- if (rel.startsWith("..") || isAbsolute$1(rel)) continue;
2551
- const mapped = resolve$1(target, rel);
2552
- if (isMobileProject(mapped)) apps.add(mapped);
2553
- }
2893
+ const registered = Object.keys(loadConfig$1()?.projects ?? {});
2894
+ const appsUnder = (checkout) => {
2895
+ const apps = /* @__PURE__ */ new Set();
2896
+ for (const path of registered) {
2897
+ const rel = relative$1(checkout, path);
2898
+ if (rel.startsWith("..") || isAbsolute$1(rel)) continue;
2899
+ const mapped = resolve$1(target, rel);
2900
+ if (isMobileProject(mapped)) apps.add(mapped);
2901
+ }
2902
+ return apps;
2903
+ };
2904
+ const source = appsUnder(root);
2905
+ const apps = source.size ? source : appsUnder(target);
2554
2906
  return apps.size === 1 ? [...apps][0] : app;
2555
2907
  }
2556
2908
  async function registerWarmedWorkspace(workspace, out) {
@@ -2605,7 +2957,7 @@ async function warmWorktreeTarget(opts) {
2605
2957
  for (const key of scope) opts.beforeWarm?.(nativeCanonicalPath(key));
2606
2958
  if (app) await recordWorkspaceUseAsync(app, /* @__PURE__ */ new Date(), operation.env, opts.cwd, operation.signal);
2607
2959
  if (workspace && workspace !== app) await recordWorkspaceUseAsync(workspace, /* @__PURE__ */ new Date(), operation.env, opts.cwd, operation.signal);
2608
- if (!workspace) out(chalk.dim(phaseLine("project", "no single app to register here; run warm from the app directory to list it in status while it warms")));
2960
+ if (!workspace) out(chalk.dim(phaseLine("project", opts.refresh ? "no single app to register here; --refresh updates only the repository root. Run warm --refresh from the app directory to refresh that app and list it in status" : "no single app to register here; run warm from the app directory to list it in status while it warms")));
2609
2961
  else {
2610
2962
  await registerWarmedWorkspace(workspace, out);
2611
2963
  progress = startWarmProgress(workspace, opts.refresh ? "refresh" : "copy", { note: (line) => out(chalk.yellow(line)) });
@@ -2621,7 +2973,7 @@ async function warmWorktreeTarget(opts) {
2621
2973
  out(chalk.dim(SETTING_SHAPE_REMEDY));
2622
2974
  return null;
2623
2975
  }
2624
- for (const key of unknownSettingKeys(settings)) out(chalk.yellow(`Warning: setting "${key}" is not read by Stim and will be ignored.`));
2976
+ for (const key of unknownSettingKeys(settings)) out(chalk.yellow(inertSettingWarning(settings, key)));
2625
2977
  return settings;
2626
2978
  };
2627
2979
  const copy = async (wait) => {
@@ -2654,12 +3006,12 @@ async function warmWorktreeTarget(opts) {
2654
3006
  }
2655
3007
  const excluded = readWorktreeExclude(root);
2656
3008
  const patterns = excluded?.length ? excluded : settings.worktree?.exclude || [];
2657
- const result = await cloneIgnoredEntries({
3009
+ const result = await trace.spanAsync("carry ignored entries", () => cloneIgnoredEntries({
2658
3010
  root,
2659
3011
  target,
2660
3012
  patterns,
2661
3013
  operation
2662
- });
3014
+ }));
2663
3015
  for (const entry of result.skipped) out(chalk.dim(phaseLine("carry", `kept ${entry.file} (${entry.reason})`)));
2664
3016
  for (const entry of result.failed) out(chalk.yellow(phaseLine("carry", `could not copy ${entry.file}: ${entry.error}`)));
2665
3017
  if (result.copied.length) {
@@ -2780,7 +3132,7 @@ function customIosDirs(worktree) {
2780
3132
  } catch {
2781
3133
  return [];
2782
3134
  }
2783
- return Object.keys(loadConfig$1()?.projects ?? {}).filter((path) => isPathPrefix(worktree, path)).map((path) => ({
3135
+ return Object.keys(loadConfig$1()?.projects ?? {}).filter((path) => isPathPrefix$1(worktree, path)).map((path) => ({
2784
3136
  path,
2785
3137
  ios: appIosProjectDir(path)
2786
3138
  })).filter(({ ios }) => ios.custom && existsSync$1(ios.dir)).map(({ path, ios }) => ({
@@ -2841,13 +3193,13 @@ function nativeCanonicalPath(path) {
2841
3193
  /** Other worktrees git lists inside `path`, which removing `path` would delete with it. */
2842
3194
  function nestedWorktrees(entries, path) {
2843
3195
  const root = nativeCanonicalPath(path);
2844
- return entries.filter((candidate) => !candidate.bare && nativeCanonicalPath(candidate.path) !== root).filter((candidate) => isPathPrefix(root, nativeCanonicalPath(candidate.path))).map((candidate) => candidate.path);
3196
+ return entries.filter((candidate) => !candidate.bare && nativeCanonicalPath(candidate.path) !== root).filter((candidate) => isPathPrefix$1(root, nativeCanonicalPath(candidate.path))).map((candidate) => candidate.path);
2845
3197
  }
2846
3198
  function matchWorktreeEntry(entries, path) {
2847
3199
  let best = null;
2848
3200
  const target = nativeCanonicalPath(path);
2849
3201
  (entries || []).forEach((entry, index) => {
2850
- if (!entry?.path || !isPathPrefix(nativeCanonicalPath(entry.path), target)) return;
3202
+ if (!entry?.path || !isPathPrefix$1(nativeCanonicalPath(entry.path), target)) return;
2851
3203
  if (!best || entry.path.length > best.path.length) best = {
2852
3204
  ...entry,
2853
3205
  index
@@ -2883,10 +3235,11 @@ function pathArgs(lines, limit = 5) {
2883
3235
  const shown = paths.slice(0, limit).map((p) => /[\s"'\\$`]/.test(p) ? JSON.stringify(p) : p);
2884
3236
  return `${shown.join(" ")}${paths.length > shown.length ? " ..." : ""}`;
2885
3237
  }
3238
+ const DIRTY_BLOCKER = "uncommitted changes or untracked files";
2886
3239
  function removalBlockers({ dirty, unpushed }) {
2887
3240
  const blockers = [];
2888
3241
  if (dirty === null || unpushed === null) blockers.push("could not determine git status; re-run with --force to override");
2889
- if (dirty) blockers.push("uncommitted changes or untracked files");
3242
+ if (dirty) blockers.push(DIRTY_BLOCKER);
2890
3243
  if (unpushed && unpushed.length) blockers.push(`${plural(unpushed.length, "commit")} not on any remote or any other local branch`);
2891
3244
  return blockers;
2892
3245
  }
@@ -2898,7 +3251,7 @@ function reclaimKeys(rootPath) {
2898
3251
  const keys = /* @__PURE__ */ new Set([rootPath]);
2899
3252
  const root = nativeCanonicalPath(rootPath);
2900
3253
  if (cfg?.projects) {
2901
- for (const key of Object.keys(cfg.projects)) if (isPathPrefix(rootPath, key) || isPathPrefix(root, nativeCanonicalPath(key))) keys.add(key);
3254
+ for (const key of Object.keys(cfg.projects)) if (isPathPrefix$1(rootPath, key) || isPathPrefix$1(root, nativeCanonicalPath(key))) keys.add(key);
2902
3255
  }
2903
3256
  return [...keys].toSorted();
2904
3257
  }
@@ -3039,11 +3392,11 @@ function reportRetainedResources(root, result, output) {
3039
3392
  }
3040
3393
  function hasRegisteredProjectUnder(rootPath) {
3041
3394
  const cfg = loadConfig$1();
3042
- return Object.keys(cfg?.projects ?? {}).some((key) => isPathPrefix(rootPath, key));
3395
+ return Object.keys(cfg?.projects ?? {}).some((key) => isPathPrefix$1(rootPath, key));
3043
3396
  }
3044
3397
  function projectAtOrAbove(root, path) {
3045
3398
  const here = nativeCanonicalPath(path);
3046
- return Object.keys(loadConfig$1()?.projects ?? {}).filter((key) => isPathPrefix(nativeCanonicalPath(root), nativeCanonicalPath(key)) && isPathPrefix(nativeCanonicalPath(key), here)).toSorted((a, b) => b.length - a.length)[0] ?? null;
3399
+ return Object.keys(loadConfig$1()?.projects ?? {}).filter((key) => isPathPrefix$1(nativeCanonicalPath(root), nativeCanonicalPath(key)) && isPathPrefix$1(nativeCanonicalPath(key), here)).toSorted((a, b) => b.length - a.length)[0] ?? null;
3047
3400
  }
3048
3401
  async function reclaimEnvironment(root, why, output, operation, beforeRemove) {
3049
3402
  await withManagedRemoteWorktreeRemovalLock(root, () => withReclaimLocks(root, async (lockedKeys) => {
@@ -3077,6 +3430,9 @@ const REMOVAL_OUTPUT = {
3077
3430
  process.exitCode = 1;
3078
3431
  }
3079
3432
  };
3433
+ function blockersOf(inspection, opts) {
3434
+ return opts.archiveDirty ? inspection.blockers.filter((blocker) => blocker !== DIRTY_BLOCKER) : inspection.blockers;
3435
+ }
3080
3436
  function removalPath(target, cwd = process.cwd()) {
3081
3437
  return canonicalExistingPath(target ?? cwd);
3082
3438
  }
@@ -3183,6 +3539,25 @@ function printRemovalCleanup(result, failed, output) {
3183
3539
  if (result.dereferenced.length) output.error(chalk.dim(phaseLine("workspace", `no longer referenced: ${result.dereferenced.join(", ")}`)));
3184
3540
  for (const skipped of result.skippedDevices) output.error((failed ? chalk.dim : chalk.yellow)(phaseLine("device", `kept ${describeKeptDevice(skipped)} (${skipped.reason})`)));
3185
3541
  }
3542
+ async function archiveBeforeRemoval(path, repository, branch, head, opts, output) {
3543
+ try {
3544
+ if (!head) throw new Error("its HEAD could not be read");
3545
+ return await archiveWorktreeChanges({
3546
+ path,
3547
+ repository,
3548
+ branch,
3549
+ head,
3550
+ pullRequest: opts.archiveDirty?.pullRequest ?? null,
3551
+ operation: opts.operation
3552
+ });
3553
+ } catch (error) {
3554
+ if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
3555
+ opts.operation?.signal.throwIfAborted();
3556
+ output.error(chalk.red(`Refusing to remove ${path}: could not archive its uncommitted changes: ${(error instanceof Error ? error.message : String(error)).split("\n")[0]}`));
3557
+ output.fail();
3558
+ return null;
3559
+ }
3560
+ }
3186
3561
  async function removeWorktreeTarget(target, opts = {}) {
3187
3562
  let removed = false;
3188
3563
  await runRemove(target, opts, () => {
@@ -3334,6 +3709,13 @@ async function removeOrRefuse(target, opts, onRemoved, output) {
3334
3709
  output.fail();
3335
3710
  return;
3336
3711
  }
3712
+ const cli = stimCliUseOf(path, nativeCanonicalPath, stimCliEntries(opts.operation?.env));
3713
+ if (cli.length) {
3714
+ output.error(chalk.red(`Refusing to remove ${path}: ${cli.join("; ")}.`));
3715
+ output.error(chalk.dim("Run stim from another checkout or install, or point PATH elsewhere, then retry."));
3716
+ output.fail();
3717
+ return;
3718
+ }
3337
3719
  const project = getProject(path);
3338
3720
  const branch = entry.branch;
3339
3721
  const ownsBranch = Boolean(branch && project?.worktreeBranchOwned === true && project.worktreeBranch === branch);
@@ -3349,13 +3731,15 @@ async function removeOrRefuse(target, opts, onRemoved, output) {
3349
3731
  output.error(chalk.dim(`HEAD is in the merged PR #${pr.number} (${pr.url}); its local-only commits stay on GitHub.`));
3350
3732
  }
3351
3733
  }
3352
- if (inspection.blockers.length && !opts.force) {
3734
+ if (blockersOf(inspection, opts).length && !opts.force) {
3353
3735
  await printRemovalRefusal(path, inspection, output, opts.operation);
3354
3736
  return;
3355
3737
  }
3356
3738
  const deleteOwnedBranch = Boolean(ownsBranch && approvedBranchSha && inspection.unpushed?.length === 0);
3357
3739
  const retainedBranchReason = !branch ? null : !ownsBranch ? "Stim did not create it" : inspection.unpushed === null ? "Stim could not verify whether it has unique commits" : inspection.unpushed.length > 0 ? `it has ${plural(inspection.unpushed.length, "unique commit")}` : null;
3358
3740
  const branchDeleteCwd = source.path;
3741
+ let archive = null;
3742
+ let gitRemoving = false;
3359
3743
  await withManagedRemoteWorktreeRemovalLock(path, () => withReclaimLocks(path, async (lockedKeys) => {
3360
3744
  options.signal?.throwIfAborted();
3361
3745
  if ((await opts.guard?.(lockedKeys))?.length) return;
@@ -3367,16 +3751,25 @@ async function removeOrRefuse(target, opts, onRemoved, output) {
3367
3751
  return;
3368
3752
  }
3369
3753
  const current = await inspectRemoval(path, mergedHead, opts.operation);
3370
- if (current.blockers.length && !opts.force) {
3754
+ if (blockersOf(current, opts).length && !opts.force) {
3371
3755
  await printRemovalRefusal(path, current, output, opts.operation);
3372
3756
  return;
3373
3757
  }
3758
+ if (opts.archiveDirty && !opts.force && current.dirtyLines.length) {
3759
+ const saved = await archiveBeforeRemoval(path, source.path, branch ?? null, inspectedHead, opts, output);
3760
+ if (!saved) return;
3761
+ archive = saved;
3762
+ }
3763
+ const discardArchive = async () => {
3764
+ if (archive) await discardWorktreeArchive(archive);
3765
+ };
3374
3766
  const result = await reclaimAll(path, lockedKeys, {
3375
3767
  preserveRootProject: true,
3376
3768
  removedBy: opts.removedBy ?? "worktree-remove",
3377
3769
  operation: opts.operation
3378
3770
  });
3379
3771
  if (result.keptEntries.length) {
3772
+ await discardArchive();
3380
3773
  reportRetainedResources(path, result, output);
3381
3774
  return;
3382
3775
  }
@@ -3387,12 +3780,14 @@ async function removeOrRefuse(target, opts, onRemoved, output) {
3387
3780
  if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
3388
3781
  opts.operation?.signal.throwIfAborted();
3389
3782
  if (!isTimeoutError(error)) throw error;
3783
+ await discardArchive();
3390
3784
  await printRemovalRefusal(path, timedOutInspection(error), output, opts.operation);
3391
3785
  output.error(chalk.dim(`The directory and Stim ownership record for ${path} were kept.`));
3392
3786
  printRemovalCleanup(result, true, output);
3393
3787
  return;
3394
3788
  }
3395
3789
  if (headMoved) {
3790
+ await discardArchive();
3396
3791
  output.error(chalk.red(`Refusing to remove ${path}: its HEAD moved while its environment was reclaimed.`));
3397
3792
  output.error(chalk.dim(`The directory and Stim ownership record for ${path} were kept.`));
3398
3793
  printRemovalCleanup(result, true, output);
@@ -3407,10 +3802,28 @@ async function removeOrRefuse(target, opts, onRemoved, output) {
3407
3802
  signal: options.signal,
3408
3803
  declareFor: opts.operation
3409
3804
  });
3805
+ let stale = null;
3806
+ if (archive) try {
3807
+ stale = await worktreeArchiveMismatch(path, archive, opts.operation);
3808
+ } catch (error) {
3809
+ if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
3810
+ stale = `its archive could not be checked: ${error instanceof Error ? error.message : String(error)}`;
3811
+ }
3812
+ if (stale) {
3813
+ const unrestored = trash ? await restoreTrashedTrees(trash) : [];
3814
+ if (trash && unrestored.length) output.error(chalk.yellow(`Could not move ${unrestored.join(", ")} back into ${path}; ${unrestored.length === 1 ? "it stays" : "they stay"} in ${trash.dir}, which stim gc keeps while ${path} exists.`));
3815
+ await discardArchive();
3816
+ output.error(chalk.red(`Refusing to remove ${path}: ${stale}.`));
3817
+ output.error(chalk.dim(`The directory and Stim ownership record for ${path} were kept.`));
3818
+ printRemovalCleanup(result, true, output);
3819
+ output.fail();
3820
+ return;
3821
+ }
3822
+ gitRemoving = true;
3410
3823
  try {
3411
3824
  await removeWorktreeAsync(path, {
3412
3825
  from: source.path,
3413
- force: opts.force,
3826
+ force: opts.force || archive !== null,
3414
3827
  env: options.env,
3415
3828
  signal: options.signal,
3416
3829
  declareFor: opts.operation
@@ -3423,10 +3836,15 @@ async function removeOrRefuse(target, opts, onRemoved, output) {
3423
3836
  output.error(chalk.red(`git worktree remove failed: ${message}`));
3424
3837
  if (process.platform === "win32" && /Permission denied/i.test(message)) output.error(chalk.dim("Another process holds a file or directory inside the worktree. An adb server or emulator started from it keeps its working directory open; `adb kill-server` releases the server, and Sysinternals handle.exe lists other holders."));
3425
3838
  output.error(chalk.dim(`The directory and Stim ownership record for ${path} were kept.`));
3839
+ if (archive) output.error(chalk.dim(`Its uncommitted changes stay archived in ${archive.dir}.`));
3426
3840
  printRemovalCleanup(result, true, output);
3427
3841
  output.fail();
3428
3842
  return;
3429
3843
  }
3844
+ if (archive) {
3845
+ opts.archiveDirty?.onArchived(archive);
3846
+ output.error(chalk.dim(phaseLine("archive", `saved ${plural(archive.tracked.length + archive.untracked.length, "uncommitted file")} to ${archive.dir}; restore: ${archive.restore.join(" && ")}`)));
3847
+ }
3430
3848
  onRemoved();
3431
3849
  try {
3432
3850
  forgetStatusMeasures(path, result.reclaimedKeys);
@@ -3489,6 +3907,11 @@ async function removeOrRefuse(target, opts, onRemoved, output) {
3489
3907
  }, opts.operation, opts.beforeRemove), {
3490
3908
  signal: opts.operation?.signal,
3491
3909
  declareSpawnsFor: opts.operation
3910
+ }).catch(async (error) => {
3911
+ if (archive && !gitRemoving) await discardWorktreeArchive(archive);
3912
+ throw error;
3913
+ }).finally(() => {
3914
+ if (archive) releaseWorktreeArchive(archive);
3492
3915
  });
3493
3916
  }
3494
3917
  function registerRemove(worktree) {
@@ -3502,4 +3925,4 @@ function worktreeCommand(program) {
3502
3925
  registerRemove(worktree);
3503
3926
  }
3504
3927
  //#endregion
3505
- export { measurePressureAsync as A, reclaimProject as C, measureDisk as D, cacheBlocked as E, levelStillShort as F, lowestFree as I, protectWindowMs as L, sizeScanDeferred as M, diskPressureLevel as N, measureDiskAsync as O, levelFloors as P, describeDereferenced as S, sweepArchiveStaging as T, worktreeCommand as _, matchWorktreeEntry as a, TRASH_MANIFEST as b, reclaimKeys as c, removalBlockers as d, removalPath as f, warmedWorkspace as g, warmWorktreeTarget as h, excludeWatchmanCookies as i, measureSizes as j, measurePressure as k, registerRemove as l, removeWorktreeTarget as m, dependencyInstallCommand as n, nestedWorktrees as o, removalRemedy as p, excludePodChurn as r, porcelainPath as s, customIosDirs as t, registerWarm as u, TRASH_CLAIMS as v, deleteSelectedArchives as w, deleteTrashEntry as x, TRASH_ENTRY_PREFIX as y };
3928
+ export { reclaimProject as A, measureSizes as C, stimCliEntries as D, serviceRunningFrom as E, sweepArchiveStaging as M, worktreeArchiveLimits as N, stimCliUseOf as O, measurePressureAsync as S, uncommittedActivityOf as T, worktreeCommand as _, matchWorktreeEntry as a, measureDiskAsync as b, reclaimKeys as c, removalBlockers as d, removalPath as f, warmedWorkspace as g, warmWorktreeTarget as h, excludeWatchmanCookies as i, deleteSelectedArchives as j, describeDereferenced as k, registerRemove as l, removeWorktreeTarget as m, dependencyInstallCommand as n, nestedWorktrees as o, removalRemedy as p, excludePodChurn as r, porcelainPath as s, customIosDirs as t, registerWarm as u, cacheBlocked as v, sizeScanDeferred as w, measurePressure as x, measureDisk as y };