stim 1.1.0 → 1.3.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 (64) hide show
  1. package/README.md +37 -0
  2. package/dist/{android-CC5bUer7.mjs → android-BW8i_YxU.mjs} +1 -1
  3. package/dist/android-BqVIJoG7.mjs +1368 -0
  4. package/dist/{android-DnZlT3aU.mjs → android-CqcW1hrP.mjs} +205 -58
  5. package/dist/{android-cas-DGlTStWu.mjs → android-cas-BR5YJBYv.mjs} +6 -6
  6. package/dist/android-cas-compiler.mjs +1 -1
  7. package/dist/{app-install-oZIQRbdD.mjs → app-install-DIp9riNx.mjs} +29 -21
  8. package/dist/{build-slots-CgacA9zQ.mjs → build-slots-C3-R4XQr.mjs} +3 -3
  9. package/dist/{cache-manifest-oeH18ERr.mjs → cache-manifest-CXR-Y8pH.mjs} +1 -1
  10. package/dist/cache-manifest.mjs +1 -1
  11. package/dist/cli.mjs +13 -13
  12. package/dist/collector-run.d.mts +4 -1
  13. package/dist/collector-run.mjs +36 -11
  14. package/dist/{config-D7uu8Gbq.mjs → config-CcefMY2Q.mjs} +94 -13
  15. package/dist/{deps-64wVnpZc.mjs → deps-Spz8ozgo.mjs} +2 -2
  16. package/dist/{dev-client-Bvky1hya.mjs → dev-client-BjisHDB5.mjs} +250 -10
  17. package/dist/{device-DJKv5Qej.mjs → device-BkiW-gkk.mjs} +19 -11
  18. package/dist/{device-lease-D-G3SgVe.mjs → device-lease-BJYybF-2.mjs} +107 -83
  19. package/dist/{device-pool-BlHXnYYs.mjs → device-pool-CPbLGfrB.mjs} +19 -13
  20. package/dist/{device-remote-CCgkG82J.mjs → device-remote-AkYCKS8U.mjs} +8 -8
  21. package/dist/{doctor-DlVxTtMq.mjs → doctor-Bmv86UUW.mjs} +18 -26
  22. package/dist/{doctor-BRF9aAJ3.mjs → doctor-fAMXC4vl.mjs} +10 -10
  23. package/dist/{error-diagnostics-B5IJwv3-.mjs → error-diagnostics-DHC31rXA.mjs} +65 -55
  24. package/dist/{exec-bsN9MJXb.mjs → exec-CyylIdq9.mjs} +4 -2
  25. package/dist/{gc-DhXdgSdt.mjs → gc-GGu-8ULN.mjs} +90 -67
  26. package/dist/{guide-DrhrDBo5.mjs → guide-92CPA21s.mjs} +414 -47
  27. package/dist/{ios-CK1Vrk6Q.mjs → ios-BSXFeO5H.mjs} +200 -59
  28. package/dist/{ios-BZxKPuqP.mjs → ios-Deh5js5V.mjs} +147 -40
  29. package/dist/{ios-device-CdSm0GDw.mjs → ios-device-CWOM_S44.mjs} +2 -2
  30. package/dist/{ios-device-CYbuRNXR.mjs → ios-device-D3pzTKQE.mjs} +1 -1
  31. package/dist/{logs-Dxoy16Qv.mjs → logs-DPNF4C9s.mjs} +9 -6
  32. package/dist/{logs-query-oqn47Ffx.mjs → logs-query-R-Cqc6Qv.mjs} +22 -5
  33. package/dist/{metro-B01H3EuD.mjs → metro-CORqXyTL.mjs} +3 -3
  34. package/dist/{ndjson-DcAtEx_K.mjs → ndjson-BZwI1NBv.mjs} +5 -2
  35. package/dist/{ownership-D8HmSv2E.mjs → ownership-8O073GIC.mjs} +3 -3
  36. package/dist/{ownership-B9QIdQOw.mjs → ownership-BpCKiRlQ.mjs} +4 -4
  37. package/dist/{ownership-claim-CeJLEpjD.mjs → ownership-claim-Bf2CAfw3.mjs} +4 -3
  38. package/dist/{project-COj6nzzh.mjs → project-fKmuIaMI.mjs} +2 -2
  39. package/dist/{reclaim-BPvN2HlP.mjs → reclaim-iW9Cad7k.mjs} +80 -73
  40. package/dist/{reload-DYZEBbZy.mjs → reload-DazHDSFA.mjs} +32 -24
  41. package/dist/{server-bare-BYHqEeyw.mjs → server-bare-DEzPZLvf.mjs} +7 -2
  42. package/dist/server-expo-44em5Pmg.mjs +2 -0
  43. package/dist/{server-expo-BEMr_m6p.mjs → server-expo-DizT4OMk.mjs} +5 -5
  44. package/dist/{settings-BSwrY4FZ.mjs → settings-DEJf6_kJ.mjs} +26 -5
  45. package/dist/slot-launch-cci0xvmC.mjs +19 -0
  46. package/dist/{start-ByCylYtB.mjs → start-mObjQH2W.mjs} +12 -12
  47. package/dist/{state-IYEY-zi8.mjs → state-B3XA2wp2.mjs} +9 -9
  48. package/dist/{stats-wqxPm7Tv.mjs → stats-BR-kwj0B.mjs} +1 -1
  49. package/dist/{stats-DZ4Q5J-7.mjs → stats-CaaQr4yN.mjs} +3 -3
  50. package/dist/{status-Dia0XTqG.mjs → status-BxQNqu9t.mjs} +86 -25
  51. package/dist/{stop-D6o5zAdm.mjs → stop-CrHGcjrV.mjs} +2 -2
  52. package/dist/{stop-BApyaF21.mjs → stop-DimGXEKY.mjs} +93 -42
  53. package/dist/supervisor-run.d.mts +1 -2
  54. package/dist/supervisor-run.mjs +5 -5
  55. package/dist/{remote-cache-DOi5-zLj.mjs → teardown-ClFp1Aax.mjs} +889 -830
  56. package/dist/{workspace-process-lock-CxOO9-pf.mjs → workspace-process-lock-3NcQnZ0X.mjs} +1 -1
  57. package/dist/{worktree-DxEmUI_n.mjs → worktree-DxTgN1CJ.mjs} +16 -16
  58. package/dist/{worktree-Dx3ZC5lS.mjs → worktree-Eq6TLH8N.mjs} +7 -3
  59. package/dist/{xcode-CDB6fytw.mjs → xcode-D7sAp-Zy.mjs} +256 -103
  60. package/package.json +5 -5
  61. package/shim/bundle-response.cjs +5 -0
  62. package/dist/android-xmyGBNy5.mjs +0 -665
  63. package/dist/build-lock-vE45ZzJZ.mjs +0 -645
  64. package/dist/server-expo-rb0k-0w7.mjs +0 -2
@@ -1,6 +1,7 @@
1
+ import { I as deviceSlotKey } from "./config-CcefMY2Q.mjs";
1
2
  import { a as formatElapsed, r as clockTime } from "./command-output-8wHMEde0.mjs";
2
- import { A as APP_READINESS_TIMEOUT_MS, l as VERIFY_TIMEOUT_MS } from "./app-install-oZIQRbdD.mjs";
3
- import { a as parseLease, c as releaseRunLease, d as selectPoolDevice, f as takeLease, n as fileLeaseIo, r as leaseIsExpired, s as raiseLease, t as deviceLeasePath } from "./device-lease-D-G3SgVe.mjs";
3
+ import { A as APP_READINESS_TIMEOUT_MS, l as VERIFY_TIMEOUT_MS } from "./app-install-DIp9riNx.mjs";
4
+ import { a as parseLease, c as releaseRunLease, d as selectPoolDevice, f as takeLease, n as fileLeaseIo, r as leaseIsExpired, s as raiseLease, t as deviceLeasePath } from "./device-lease-BJYybF-2.mjs";
4
5
  //#region src/engine/device-lease-run.ts
5
6
  const LEASE_STEP_FLOOR_MS = 6e4;
6
7
  const DEBUG_VERIFY_STEP_MS = VERIFY_TIMEOUT_MS + APP_READINESS_TIMEOUT_MS;
@@ -36,7 +37,7 @@ function facts(lease) {
36
37
  };
37
38
  }
38
39
  function waitingLine(lease, now) {
39
- return `waiting for ${lease.holder} to release ${describeDevice(lease)}; its lease runs until ${leaseExpiryText(lease.expiresAt, now)}`;
40
+ return `waiting for ${lease.holder} to release ${describeDevice(lease)}; its lease runs until ${leaseExpiryText(lease.expiresAt, now)} -- stim guide lifecycle lease`;
40
41
  }
41
42
  function appIdMatch(ours, theirs) {
42
43
  if (!ours || !theirs) return "unknown";
@@ -101,7 +102,7 @@ function lostRefusal(holder, expiresAt, now) {
101
102
  lease: null
102
103
  };
103
104
  }
104
- function runLease({ root, platform, kind, expiresAt, io = fileLeaseIo }) {
105
+ function runLease({ root, platform, slot = "default", kind, expiresAt, io = fileLeaseIo }) {
105
106
  const handle = {
106
107
  kind,
107
108
  expiresAt,
@@ -115,6 +116,7 @@ function runLease({ root, platform, kind, expiresAt, io = fileLeaseIo }) {
115
116
  const result = raiseLease({
116
117
  root,
117
118
  platform,
119
+ slot,
118
120
  minMs: leaseStepMs(boundMs)
119
121
  }, io);
120
122
  if (result.status === "raised") {
@@ -137,7 +139,8 @@ function runLease({ root, platform, kind, expiresAt, io = fileLeaseIo }) {
137
139
  if (handle.kind === null) return;
138
140
  releaseRunLease({
139
141
  root,
140
- platform
142
+ platform,
143
+ slot
141
144
  }, io);
142
145
  },
143
146
  facts() {
@@ -173,7 +176,7 @@ function releaseLeaseOnSignal(release, { on = (signal, handler) => {
173
176
  installed.length = 0;
174
177
  };
175
178
  }
176
- async function waitForDevice({ root, platform, id, idLabel, waitSeconds, noWait = false, appId = null, holderAppId = () => null, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)), warn = () => {}, io = fileLeaseIo, deadline, lastLine }) {
179
+ async function waitForDevice({ root, platform, slot = "default", id, idLabel, waitSeconds, noWait = false, appId = null, holderAppId = () => null, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)), warn = () => {}, io = fileLeaseIo, deadline, lastLine }) {
177
180
  const path = deviceLeasePath(platform, id);
178
181
  for (;;) {
179
182
  const raw = io.readLease(path);
@@ -183,7 +186,7 @@ async function waitForDevice({ root, platform, id, idLabel, waitSeconds, noWait
183
186
  refusal: unreadableRefusal(path)
184
187
  };
185
188
  if (!current || leaseIsExpired(current, now())) return { status: "free" };
186
- if (current.holder === root) return {
189
+ if (current.holder === root && (current.slot ?? "default") === slot) return {
187
190
  status: "ours",
188
191
  lease: current
189
192
  };
@@ -210,8 +213,8 @@ async function waitForDevice({ root, platform, id, idLabel, waitSeconds, noWait
210
213
  await sleep(DEVICE_WAIT_POLL_MS);
211
214
  }
212
215
  }
213
- async function acquireRunLease({ root, platform, id, deviceName = null, idLabel, waitSeconds, noWait, installBoundMs, appId = null, holderAppId = () => null, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)), warn = () => {}, io = fileLeaseIo }) {
214
- const recorded = io.readHolder(root)[platform];
216
+ async function acquireRunLease({ root, platform, slot = "default", id, deviceName = null, idLabel, waitSeconds, noWait, installBoundMs, appId = null, holderAppId = () => null, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)), warn = () => {}, io = fileLeaseIo }) {
217
+ const recorded = io.readHolder(root)[deviceSlotKey(platform, slot)];
215
218
  let held = recorded;
216
219
  if (recorded && recorded.id !== id) {
217
220
  const other = parseLease(io.readLease(deviceLeasePath(platform, recorded.id)));
@@ -234,6 +237,7 @@ async function acquireRunLease({ root, platform, id, deviceName = null, idLabel,
234
237
  const raised = raiseLease({
235
238
  root,
236
239
  platform,
240
+ slot,
237
241
  minMs: leaseStepMs(installBoundMs)
238
242
  }, io);
239
243
  if (raised.status === "raised") return {
@@ -245,6 +249,7 @@ async function acquireRunLease({ root, platform, id, deviceName = null, idLabel,
245
249
  const outcome = await waitForDevice({
246
250
  root,
247
251
  platform,
252
+ slot,
248
253
  id,
249
254
  idLabel,
250
255
  waitSeconds,
@@ -267,6 +272,7 @@ async function acquireRunLease({ root, platform, id, deviceName = null, idLabel,
267
272
  const taken = takeLease({
268
273
  root,
269
274
  platform,
275
+ slot,
270
276
  id,
271
277
  deviceName,
272
278
  kind: "run",
@@ -285,8 +291,8 @@ async function acquireRunLease({ root, platform, id, deviceName = null, idLabel,
285
291
  }
286
292
  //#endregion
287
293
  //#region src/engine/device-pool.ts
288
- function heldPoolId(root, platform, now, io = fileLeaseIo) {
289
- const record = io.readHolder(root)[platform];
294
+ function heldPoolId(root, platform, now, io = fileLeaseIo, slot = "default") {
295
+ const record = io.readHolder(root)[deviceSlotKey(platform, slot)];
290
296
  if (!record) return null;
291
297
  const lease = parseLease(io.readLease(deviceLeasePath(platform, record.id)));
292
298
  if (!lease || lease.token !== record.token || leaseIsExpired(lease, now)) return null;
@@ -352,8 +358,8 @@ function poolBusyRefusal(holders, leases, unreadable, now, waitSeconds) {
352
358
  }
353
359
  };
354
360
  }
355
- async function selectFromPool({ root, platform, idLabel, list, noCandidates, waitSeconds, deadline, noWait = false, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)), warn = () => {}, io = fileLeaseIo }) {
356
- const held = heldPoolId(root, platform, now(), io);
361
+ async function selectFromPool({ root, platform, slot = "default", idLabel, list, noCandidates, waitSeconds, deadline, noWait = false, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)), warn = () => {}, io = fileLeaseIo }) {
362
+ const held = heldPoolId(root, platform, now(), io, slot);
357
363
  const until = deadline ?? now() + waitSeconds * 1e3;
358
364
  let lastLine = null;
359
365
  for (;;) {
@@ -1,11 +1,11 @@
1
- import { t as getExecutor } from "./exec-bsN9MJXb.mjs";
2
- import { k as withDirLock, u as getConfigDir } from "./config-D7uu8Gbq.mjs";
3
- import { D as workspaceLogsDir, O as workspaceName, T as workspaceDir, a as clearRemoteSession, c as readMetroTunnel, k as workspaceStateFile, u as readRemoteSession } from "./state-IYEY-zi8.mjs";
4
- import { r as pidExists } from "./metro-B01H3EuD.mjs";
5
- import { R as planMetroReach } from "./settings-BSwrY4FZ.mjs";
6
- import { T as readMetroRecords, a as LAUNCH_ERROR, b as isBundleProof, p as devClientDeepLink, r as INSTALL_ERROR } from "./app-install-oZIQRbdD.mjs";
7
- import { x as sanitizeDeviceLabel } from "./ios-BZxKPuqP.mjs";
8
- import { t as withWorkspaceProcessLock } from "./workspace-process-lock-CxOO9-pf.mjs";
1
+ import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
+ import { k as withDirLock, u as getConfigDir } from "./config-CcefMY2Q.mjs";
3
+ import { D as workspaceLogsDir, O as workspaceName, T as workspaceDir, a as clearRemoteSession, c as readMetroTunnel, k as workspaceStateFile, u as readRemoteSession } from "./state-B3XA2wp2.mjs";
4
+ import { r as pidExists } from "./metro-CORqXyTL.mjs";
5
+ import { z as planMetroReach } from "./settings-DEJf6_kJ.mjs";
6
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-3NcQnZ0X.mjs";
7
+ import { T as readMetroRecords, a as LAUNCH_ERROR, b as isBundleProof, p as devClientDeepLink, r as INSTALL_ERROR } from "./app-install-DIp9riNx.mjs";
8
+ import { S as sanitizeDeviceLabel } from "./ios-Deh5js5V.mjs";
9
9
  import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
10
10
  import { dirname, isAbsolute, join, relative, resolve } from "node:path";
11
11
  import { homedir } from "node:os";
@@ -1,17 +1,16 @@
1
- import { t as getExecutor } from "./exec-bsN9MJXb.mjs";
2
- import { g as loadConfig, l as getConcurrencyLimits, o as configCorruptRepair } from "./config-D7uu8Gbq.mjs";
3
- import { b as sharedBuildCache, w as workspaceDerivedData } from "./state-IYEY-zi8.mjs";
4
- import { S as makeTemporaryDirectory, a as dirtyFingerprintFiles, f as listWorktrees, g as repoRoot, s as gitCommonDir, w as temporaryRoot, x as filesystemDevice } from "./worktree-Dx3ZC5lS.mjs";
5
- import { i as detectIsExpo, t as appProjectProblem } from "./project-COj6nzzh.mjs";
6
- import { d as resolveEasCliBin, i as checkEasAuth$1, l as ownerFromConfig, u as providerFromConfig, v as POOL_SETTING_REMEDY, x as parkedMaxSetting } from "./remote-cache-DOi5-zLj.mjs";
7
- import { B as compilerCacheFallbackMessage, C as projectOptimizations, D as remoteIosSetting, F as unknownSettingKeys, H as resolveOptimizations, M as settingShapeErrors, N as settingsLayers, T as remoteAndroidSetting, a as SETTING_SHAPE_REMEDY, i as SETTINGS_WITH_RESOLVE_TIME_FALLBACK, j as settingOrigin, k as resolveSettings, n as PATH_SETTINGS, v as iosSimSlimProfileSetting, y as mergeSettingsLayers } from "./settings-BSwrY4FZ.mjs";
1
+ import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
+ import { g as loadConfig, l as getConcurrencyLimits, o as configCorruptRepair } from "./config-CcefMY2Q.mjs";
3
+ import { b as sharedBuildCache, w as workspaceDerivedData } from "./state-B3XA2wp2.mjs";
4
+ import { S as makeTemporaryDirectory, a as dirtyFingerprintFiles, f as listWorktrees, g as repoRoot, s as gitCommonDir, w as temporaryRoot, x as filesystemDevice } from "./worktree-Eq6TLH8N.mjs";
5
+ import { i as detectIsExpo, t as appProjectProblem } from "./project-fKmuIaMI.mjs";
8
6
  import { d as quotedPath, u as plural } from "./command-output-8wHMEde0.mjs";
9
- import { l as listAllIosSims } from "./ios-BZxKPuqP.mjs";
10
- import { m as fingerprintProject, r as listBuildLocks, u as diffFingerprintSources } from "./build-lock-vE45ZzJZ.mjs";
11
- import { n as listBuildSlots } from "./build-slots-CgacA9zQ.mjs";
12
- import { _ as listAdbDevices } from "./android-xmyGBNy5.mjs";
13
- import { T as simslimIsOnPath, a as ccacheEnabled, g as podInstallCommand, x as liveOwnedDeviceCount } from "./xcode-CDB6fytw.mjs";
14
- import { d as projectCmakeLauncher, i as apkOutputsDir, r as resolveAndroidCompilerCache, t as readAndroidCasToolchain } from "./android-cas-DGlTStWu.mjs";
7
+ import { B as listBuildLocks, Z as fingerprintProject, q as diffFingerprintSources, y as listAdbDevices } from "./android-BqVIJoG7.mjs";
8
+ import { A as resolveSettings, E as remoteAndroidSetting, I as unknownSettingKeys, M as settingOrigin, N as settingShapeErrors, O as remoteIosSetting, P as settingsLayers, U as resolveOptimizations, V as compilerCacheFallbackMessage, a as SETTING_SHAPE_REMEDY, i as SETTINGS_WITH_RESOLVE_TIME_FALLBACK, n as PATH_SETTINGS, v as iosSimSlimProfileSetting, w as projectOptimizations, y as mergeSettingsLayers } from "./settings-DEJf6_kJ.mjs";
9
+ import { a as POOL_SETTING_REMEDY, b as providerFromConfig, c as parkedMaxSetting, m as checkEasAuth$1, x as resolveEasCliBin, y as ownerFromConfig } from "./teardown-ClFp1Aax.mjs";
10
+ import { E as readHostMemoryPressure, T as hostMemoryPressureAdvice, u as listAllIosSims } from "./ios-Deh5js5V.mjs";
11
+ import { n as listBuildSlots } from "./build-slots-C3-R4XQr.mjs";
12
+ import { E as simslimIsOnPath, S as liveOwnedDeviceCount, a as ccacheEnabled, g as podInstallCommand } from "./xcode-D7sAp-Zy.mjs";
13
+ import { d as projectCmakeLauncher, i as apkOutputsDir, r as resolveAndroidCompilerCache, t as readAndroidCasToolchain } from "./android-cas-BR5YJBYv.mjs";
15
14
  import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync, rmSync } from "node:fs";
16
15
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
17
16
  import { existsSync as existsSync$1, lstatSync as lstatSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, realpathSync as realpathSync$1, rmSync as rmSync$1 } from "fs";
@@ -699,15 +698,6 @@ function checkMainCheckout(projectRoot, { npmTreeValid, brokenPods = void 0, ups
699
698
  if (linkedWorktrees === void 0 ? hasLinkedWorktree(projectRoot) : linkedWorktrees) findings.push(...seedFindings(mainRoot, upstream === void 0 ? locallyKnownUpstream(mainRoot) : upstream));
700
699
  return findings;
701
700
  }
702
- function checkDevClient(pkg, isExpo = true) {
703
- const deps = {
704
- ...pkg?.dependencies,
705
- ...pkg?.devDependencies
706
- };
707
- if (deps["expo-dev-client"]) return null;
708
- if (!isExpo || !deps.expo) return null;
709
- return finding("cost", "expo-dev-client is not installed", "A Metro port reserved by Stim cannot reach the app without it: the port travels in the dev-client deep link `stim ios` opens, and without the dev client nothing handles that URL. The app falls back to port 8081 and shows \"No script URL provided\".", "npx expo install expo-dev-client, then rebuild with `stim ios` / `stim android`. It is a NATIVE dependency: an app already on the device will not pick it up, and the first build after installing it is a cache miss by design because the native fingerprint moved. Do not solve this by compiling the port in (RCT_METRO_PORT, or the dev client defaultLaunchURL) -- the build cache does not key on the port, so a binary built for one workspace would silently talk to another workspace bundler.");
710
- }
711
701
  function checkMetroCache(metroConfigSource) {
712
702
  if (metroConfigSource == null) return null;
713
703
  const lines = String(metroConfigSource).split("\n");
@@ -811,10 +801,11 @@ function checkRemoteDevice({ configured = null, daemonInEnv = false, agentDevice
811
801
  }
812
802
  function checkSimSlim({ configured = false, profileError = null, onPath = false } = {}) {
813
803
  if (profileError) return finding("cost", "The SimSlim profile is invalid", profileError, "Set ios.simslimProfile to a readable JSON profile inside the repository.");
814
- if (!configured || onPath) return null;
804
+ if (!configured) return finding("note", "SimSlim is recommended for parallel iOS simulator work", "A reviewed profile can reduce background simulator services and memory use. It is optional: disabling services can affect tests that depend on them, and installing the binary alone does not apply a profile.", "Run `stim guide lifecycle simslim` to install SimSlim and configure ios.simslimProfile. Stim does not enable a profile through doctor --fix.");
805
+ if (onPath) return null;
815
806
  return finding("cost", "A SimSlim profile is configured, but SimSlim is not installed", "`stim ios` needs the `simslim` command to apply the profile to its owned simulator.", "brew install mobai-app/tap/simslim");
816
807
  }
817
- function runDoctor(projectRoot, { readFile = readFileSync$1, xcodeMajor = null, easAuth = checkEasAuth$1, concurrency = getConcurrencyLimits, liveDevices = null, activeBuilds = null, remoteEnv = process.env, lookupAgentDevice = null, lookupEasCli = null, lookupSimSlim = null, lookupCcache = null, platform } = {}) {
808
+ function runDoctor(projectRoot, { readFile = readFileSync$1, xcodeMajor = null, easAuth = checkEasAuth$1, concurrency = getConcurrencyLimits, liveDevices = null, activeBuilds = null, remoteEnv = process.env, lookupAgentDevice = null, lookupEasCli = null, lookupSimSlim = null, memoryPressure = readHostMemoryPressure, lookupCcache = null, platform } = {}) {
818
809
  const read = (rel) => {
819
810
  const p = join$1(projectRoot, rel);
820
811
  if (!existsSync$1(p)) return null;
@@ -886,7 +877,7 @@ function runDoctor(projectRoot, { readFile = readFileSync$1, xcodeMajor = null,
886
877
  } catch (error) {
887
878
  simslimProfileError = String(error?.message || error);
888
879
  }
889
- const simslimFinding = platform === "android" ? null : checkSimSlim({
880
+ const simslimFinding = platform === "android" || !simslimProfile && !simslimProfileError && remoteIosSetting(projectSettings) ? null : checkSimSlim({
890
881
  configured: Boolean(simslimProfile),
891
882
  profileError: simslimProfileError,
892
883
  onPath: simslimProfile ? lookupSimSlim ? lookupSimSlim() : simslimIsOnPath() : false
@@ -901,12 +892,12 @@ function runDoctor(projectRoot, { readFile = readFileSync$1, xcodeMajor = null,
901
892
  agentDeviceOnPath,
902
893
  easCliResolvable
903
894
  })).filter((remoteFinding) => remoteFinding !== null);
895
+ const memoryAdvice = platform !== "android" && !remoteIosSetting(projectSettings) ? hostMemoryPressureAdvice(memoryPressure()) : null;
904
896
  return [
905
897
  checkAppProject(projectRoot),
906
898
  ...checkMainCheckout(projectRoot, { platform }),
907
899
  ...platform === "android" ? [] : inspectIosDebugArchitectures(mainCheckoutProjectRoot(projectRoot)),
908
900
  ...checkStorageLayout(projectRoot, { platform }),
909
- checkDevClient(pkg, isExpo),
910
901
  optimizations?.metroSharedCache ? checkMetroCache(metroConfig) : null,
911
902
  platform === "android" || !optimizations?.ios.compilationCache ? null : checkCompilationCache(podfile, xcodeMajor),
912
903
  platform === "android" || !optimizations?.ios.compilationCache ? null : checkCcacheConflict(podfile, podfileProperties),
@@ -914,6 +905,7 @@ function runDoctor(projectRoot, { readFile = readFileSync$1, xcodeMajor = null,
914
905
  remoteBuildCache ? checkBuildCacheProvider(appConfig, sdkMajor, isExpo, dynamicConfig) : null,
915
906
  easFinding,
916
907
  concurrencyFinding,
908
+ memoryAdvice ? finding("cost", "Host memory pressure can stall the iOS simulator", memoryAdvice, "Free host memory, then retry. Run `stim guide lifecycle simslim` for the optional memory reduction setup.") : null,
917
909
  simslimFinding,
918
910
  ...remoteFindings,
919
911
  ...settingShapeFindings,
@@ -1,9 +1,9 @@
1
- import { t as getExecutor } from "./exec-bsN9MJXb.mjs";
2
- import { g as repoRoot } from "./worktree-Dx3ZC5lS.mjs";
3
- import { a as findProjectRoot } from "./project-COj6nzzh.mjs";
1
+ import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
+ import { g as repoRoot } from "./worktree-Eq6TLH8N.mjs";
3
+ import { a as findProjectRoot } from "./project-fKmuIaMI.mjs";
4
4
  import { l as phaseLine } from "./command-output-8wHMEde0.mjs";
5
- import { s as detectXcodeMajor } from "./xcode-CDB6fytw.mjs";
6
- import { c as repairCxxLauncherState, n as detectFingerprintParity, r as detectLinkedLibraryGitMetadata, s as runDoctor } from "./doctor-DlVxTtMq.mjs";
5
+ import { s as detectXcodeMajor } from "./xcode-D7sAp-Zy.mjs";
6
+ import { c as repairCxxLauncherState, n as detectFingerprintParity, r as detectLinkedLibraryGitMetadata, s as runDoctor } from "./doctor-Bmv86UUW.mjs";
7
7
  import { accessSync, constants, realpathSync, statSync } from "node:fs";
8
8
  import { InvalidArgumentError } from "commander";
9
9
  import { delimiter, join, resolve } from "node:path";
@@ -209,7 +209,7 @@ function sandboxFinding(settingsRoot, { env = process.env, home = homedir(), sti
209
209
  level: "cost",
210
210
  title,
211
211
  detail: `${symptoms} Missing: ${missing.join(", ")}.`,
212
- fix: `Run \`stim doctor --fix\` to add them to ${paths[0]}, or add them by hand. See \`stim guide errors sandbox\`.`
212
+ fix: `Run \`stim doctor --fix\` to write sandbox.filesystem.allowWrite, sandbox.network.allowMachLookup, and sandbox.network.allowLocalBinding into ${paths[0]}, merged with what is there, or add the missing keys by hand. See \`stim guide errors sandbox\`.`
213
213
  };
214
214
  }
215
215
  //#endregion
@@ -395,13 +395,13 @@ function doctorSuccessLines(platform, stim) {
395
395
  ];
396
396
  if (platform !== "android") {
397
397
  lines.push("", "iOS");
398
- lines.push(phaseLine("setup", "CocoaPods, warm state, dev client, effective Debug simulator architectures"));
398
+ lines.push(phaseLine("setup", "CocoaPods, warm state, effective Debug simulator architectures"));
399
399
  lines.push(phaseLine("caches", "Metro, Xcode compilation, ccache, build provider"));
400
400
  lines.push(phaseLine("devices", "remote device, SimSlim profile"));
401
401
  }
402
402
  if (platform !== "ios") {
403
403
  lines.push("", "Android");
404
- lines.push(phaseLine("setup", "warm state, dev client"));
404
+ lines.push(phaseLine("setup", "warm state"));
405
405
  lines.push(phaseLine("caches", "Metro, Gradle, ccache, build provider"));
406
406
  lines.push(phaseLine("devices", "remote device"));
407
407
  }
@@ -464,7 +464,7 @@ function applySandboxFix(root, env = process.env) {
464
464
  console.error(chalk.dim("Added writes to Stim's state directory, the simulator XPC service, and local port binding. Claude Code reads project settings from the directory a session starts in, so this file only counts for sessions rooted there. Restart the session for it to take effect."));
465
465
  }
466
466
  function doctorCommand(program, version, inspectVersions = inspectStimVersions) {
467
- program.command("doctor").description("Inspect the source checkout and report project state that can make native worktrees slow or invalid. Read-only unless --fix is passed; --platform filters native findings.").option("--json", "print the findings as JSON").option("--platform <platform>", "report shared findings plus only this native platform: ios or android", parseDoctorPlatform).option("--fix", "repair the sandbox allowance and stale Android .cxx configurations in this checkout. Stop native builds first. Generated CMake output must be ignored and untracked; custom launcher settings and source files are preserved.").action(async (opts) => {
467
+ program.command("doctor").description("Inspect the source checkout and report project state that can make native worktrees slow or invalid. Read-only unless --fix is passed; --platform filters native findings.").option("--json", "print the findings as JSON").option("--platform <platform>", "report shared findings plus only this native platform: ios or android", parseDoctorPlatform).option("--fix", "repair the sandbox allowance when the report names it, and stale Android .cxx configurations in this checkout. Stop native builds first. Generated CMake output must be ignored and untracked; custom launcher settings and source files are preserved.").action(async (opts) => {
468
468
  const root = findProjectRoot(process.cwd());
469
469
  if (!root) {
470
470
  console.error(chalk.red("Not in a React Native project (no package.json found)."));
@@ -472,7 +472,7 @@ function doctorCommand(program, version, inspectVersions = inspectStimVersions)
472
472
  return;
473
473
  }
474
474
  if (opts.fix) {
475
- if (detectHarness() !== "codex" || sandboxFinding(repoRoot(root) ?? root)) applySandboxFix(root);
475
+ if (sandboxFinding(repoRoot(root) ?? root)) applySandboxFix(root);
476
476
  if (opts.platform !== "ios") try {
477
477
  const repair = repairCxxLauncherState(root);
478
478
  for (const path of repair.removed) console.error(phaseLine("cache", `removed ${path}; next build reconfigures`));
@@ -1,13 +1,13 @@
1
- import { t as getExecutor } from "./exec-bsN9MJXb.mjs";
2
- import { f as getProject, k as withDirLock } from "./config-D7uu8Gbq.mjs";
3
- import { f as readWorkspaceLaunches, p as readWorkspaceState } from "./state-IYEY-zi8.mjs";
4
- import { n as deviceConsoleLevel } from "./ios-device-CYbuRNXR.mjs";
5
- import { i as resolveProjectMetro } from "./metro-B01H3EuD.mjs";
6
- import { n as parseLogcatLine, t as androidClockOffset } from "./android-CC5bUer7.mjs";
1
+ import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
+ import { I as deviceSlotKey, L as deviceSlotPlatforms, f as getProject, k as withDirLock } from "./config-CcefMY2Q.mjs";
3
+ import { f as readWorkspaceLaunches, p as readWorkspaceState } from "./state-B3XA2wp2.mjs";
4
+ import { n as deviceConsoleLevel } from "./ios-device-D3pzTKQE.mjs";
5
+ import { i as resolveProjectMetro } from "./metro-CORqXyTL.mjs";
6
+ import { n as parseLogcatLine, t as androidClockOffset } from "./android-BW8i_YxU.mjs";
7
7
  import { _ as launchErrorPreview } from "./command-output-8wHMEde0.mjs";
8
- import { a as parseLease, n as fileLeaseIo, t as deviceLeasePath } from "./device-lease-D-G3SgVe.mjs";
9
- import { n as androidHome } from "./android-xmyGBNy5.mjs";
10
- import { c as readLogRecords, t as attachExpoErrorContext } from "./logs-query-oqn47Ffx.mjs";
8
+ import { n as androidHome } from "./android-BqVIJoG7.mjs";
9
+ import { a as parseLease, n as fileLeaseIo, t as deviceLeasePath } from "./device-lease-BJYybF-2.mjs";
10
+ import { c as readLogRecords, t as attachExpoErrorContext } from "./logs-query-R-Cqc6Qv.mjs";
11
11
  import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
12
12
  import { createHash, randomUUID } from "node:crypto";
13
13
  import { basename, dirname, join, resolve, sep } from "node:path";
@@ -333,14 +333,18 @@ function captureNativeCrashes(target, logsDir) {
333
333
  if (text) records = parseAndroidCrashes(text, target, offset).map((record) => symbolicateAndroid(record, target.root, deadline));
334
334
  }
335
335
  } catch {}
336
- for (const record of records) try {
337
- const key = createHash("sha256").update(JSON.stringify([
338
- record.deviceId,
339
- record.incident ?? record.deviceTs,
340
- record.rawReport
341
- ])).digest("hex");
342
- writeDiagnosticOnce(join(logsDir, `native-crash-${key}.ndjson`), `${JSON.stringify(record)}\n`);
343
- } catch {}
336
+ for (const record of records) {
337
+ if (target.slot && target.slot !== "default") record.slot = target.slot;
338
+ try {
339
+ const key = createHash("sha256").update(JSON.stringify([
340
+ record.slot,
341
+ record.deviceId,
342
+ record.incident ?? record.deviceTs,
343
+ record.rawReport
344
+ ])).digest("hex");
345
+ writeDiagnosticOnce(join(logsDir, `native-crash-${key}.ndjson`), `${JSON.stringify(record)}\n`);
346
+ } catch {}
347
+ }
344
348
  return records;
345
349
  }
346
350
  function printNativeCrashReport(target, logsDir, emit, remote) {
@@ -350,45 +354,51 @@ function printNativeCrashReport(target, logsDir, emit, remote) {
350
354
  function captureWorkspaceCrashes(root, logsDir) {
351
355
  const attempts = readLogRecords(logsDir).filter((record) => record.event === "launch_attempt");
352
356
  const launches = readWorkspaceLaunches(root);
353
- for (const platform of ["ios", "android"]) try {
354
- const attempt = attempts.findLast((record) => record.platform === platform);
355
- if (!attempt || attempt.remote) continue;
356
- const { deviceId, appId } = attempt;
357
- const since = attempt.ts;
358
- const until = Date.now();
359
- if (typeof deviceId !== "string" || typeof appId !== "string" || typeof since !== "number" || !Number.isFinite(since)) continue;
360
- if (attempt.physical) {
361
- const holder = fileLeaseIo.readHolder(root)[platform];
362
- const lease = parseLease(fileLeaseIo.readLease(deviceLeasePath(platform, deviceId)));
363
- if (holder?.id !== deviceId || !lease || lease.platform !== platform || lease.id !== deviceId || lease.holder !== root || lease.token !== holder.token || Date.parse(lease.expiresAt) <= until || !lease.grantedAt || !Number.isFinite(Date.parse(lease.grantedAt)) || Date.parse(lease.grantedAt) > since) continue;
364
- } else {
365
- const launch = launches[platform];
366
- if (!launch || launch.deviceId !== deviceId || launch.appId !== appId || Date.parse(launch.launchedAt) !== since) continue;
367
- const configured = getProject(root)?.platforms?.[platform];
368
- if (!configured?.owned) continue;
369
- if (platform === "ios" && configured.deviceUdid !== deviceId) continue;
370
- if (platform === "android") {
371
- if (!configured.avdName) continue;
372
- if (getExecutor().runFileQuiet("adb", [
373
- "-s",
374
- deviceId,
375
- "emu",
376
- "avd",
377
- "name"
378
- ], { timeoutMs: 2e3 })?.split("\n")[0]?.trim() !== configured.avdName) continue;
357
+ const latest = /* @__PURE__ */ new Map();
358
+ for (const attempt of attempts) if (attempt.platform === "ios" || attempt.platform === "android") latest.set(deviceSlotKey(attempt.platform, String(attempt.slot ?? "default")), attempt);
359
+ for (const [key, attempt] of latest) {
360
+ const platform = attempt.platform;
361
+ const slot = String(attempt.slot ?? "default");
362
+ try {
363
+ if (!attempt || attempt.remote) continue;
364
+ const { deviceId, appId } = attempt;
365
+ const since = attempt.ts;
366
+ const until = Date.now();
367
+ if (typeof deviceId !== "string" || typeof appId !== "string" || typeof since !== "number" || !Number.isFinite(since)) continue;
368
+ if (attempt.physical) {
369
+ const holder = fileLeaseIo.readHolder(root)[key];
370
+ const lease = parseLease(fileLeaseIo.readLease(deviceLeasePath(platform, deviceId)));
371
+ if (holder?.id !== deviceId || !lease || lease.platform !== platform || lease.id !== deviceId || lease.holder !== root || lease.token !== holder.token || Date.parse(lease.expiresAt) <= until || !lease.grantedAt || !Number.isFinite(Date.parse(lease.grantedAt)) || Date.parse(lease.grantedAt) > since) continue;
372
+ } else {
373
+ const launch = launches[key];
374
+ if (!launch || launch.deviceId !== deviceId || launch.appId !== appId || Date.parse(launch.launchedAt) !== since) continue;
375
+ const configured = deviceSlotPlatforms(getProject(root), slot)?.[platform];
376
+ if (!configured?.owned) continue;
377
+ if (platform === "ios" && configured.deviceUdid !== deviceId) continue;
378
+ if (platform === "android") {
379
+ if (!configured.avdName) continue;
380
+ if (getExecutor().runFileQuiet("adb", [
381
+ "-s",
382
+ deviceId,
383
+ "emu",
384
+ "avd",
385
+ "name"
386
+ ], { timeoutMs: 2e3 })?.split("\n")[0]?.trim() !== configured.avdName) continue;
387
+ }
379
388
  }
380
- }
381
- captureNativeCrashes({
382
- root,
383
- platform,
384
- deviceId,
385
- appId,
386
- since: Number(since),
387
- until,
388
- physical: attempt?.physical === true,
389
- appPath: attempt?.appPath ?? readWorkspaceState(root)?.lastBuild?.appPath
390
- }, logsDir);
391
- } catch {}
389
+ captureNativeCrashes({
390
+ root,
391
+ slot,
392
+ platform,
393
+ deviceId,
394
+ appId,
395
+ since: Number(since),
396
+ until,
397
+ physical: attempt?.physical === true,
398
+ appPath: attempt?.appPath ?? readWorkspaceState(root)?.lastBuild?.appPath
399
+ }, logsDir);
400
+ } catch {}
401
+ }
392
402
  }
393
403
  //#endregion
394
404
  //#region src/error-symbolication.ts
@@ -2,7 +2,7 @@ import { execFileSync, execSync, spawn } from "child_process";
2
2
  //#region src/exec.ts
3
3
  const MAX_BUFFER = 67108864;
4
4
  let active = {
5
- run(cmd, { timeoutMs, cwd } = {}) {
5
+ run(cmd, { timeoutMs, killSignal, cwd } = {}) {
6
6
  const opts = {
7
7
  encoding: "utf-8",
8
8
  stdio: [
@@ -13,10 +13,11 @@ let active = {
13
13
  maxBuffer: MAX_BUFFER
14
14
  };
15
15
  if (timeoutMs) opts.timeout = timeoutMs;
16
+ if (killSignal) opts.killSignal = killSignal;
16
17
  if (cwd) opts.cwd = cwd;
17
18
  return String(execSync(cmd, opts)).trim();
18
19
  },
19
- runFile(file, args = [], { timeoutMs, cwd, env, omitEnv } = {}) {
20
+ runFile(file, args = [], { timeoutMs, killSignal, cwd, env, omitEnv } = {}) {
20
21
  const opts = {
21
22
  encoding: "utf-8",
22
23
  stdio: [
@@ -27,6 +28,7 @@ let active = {
27
28
  maxBuffer: MAX_BUFFER
28
29
  };
29
30
  if (timeoutMs) opts.timeout = timeoutMs;
31
+ if (killSignal) opts.killSignal = killSignal;
30
32
  if (cwd) opts.cwd = cwd;
31
33
  if (env || omitEnv?.length) {
32
34
  const childEnv = {