stim 1.2.0 → 1.3.1

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 (59) hide show
  1. package/README.md +34 -3
  2. package/dist/{android-Bcmev2s2.mjs → android-CT-PkXUh.mjs} +157 -62
  3. package/dist/{android-cas-D6HPZ6UQ.mjs → android-cas-DXorPCi_.mjs} +5 -5
  4. package/dist/android-hMj83VpN.mjs +1374 -0
  5. package/dist/{app-install-NFxUahnH.mjs → app-install-DIp9riNx.mjs} +6 -6
  6. package/dist/{build-slots-D0b6sJyo.mjs → build-slots-C4AouyNz.mjs} +2 -2
  7. package/dist/{cache-manifest-Cu4_Znw9.mjs → cache-manifest-CXR-Y8pH.mjs} +1 -1
  8. package/dist/cache-manifest.mjs +1 -1
  9. package/dist/cli.mjs +13 -13
  10. package/dist/collector-run.d.mts +3 -1
  11. package/dist/collector-run.mjs +33 -8
  12. package/dist/{config-CMtIaTk9.mjs → config-CcefMY2Q.mjs} +93 -12
  13. package/dist/{deps-BLBsTNDS.mjs → deps-0Yt0Inly.mjs} +1 -1
  14. package/dist/{dev-client-RpvXxCXV.mjs → dev-client-VfSZNLT4.mjs} +186 -9
  15. package/dist/{device-CoCPmazS.mjs → device-Bbv45Z9Z.mjs} +19 -11
  16. package/dist/{device-lease-Dpik41RY.mjs → device-lease-CJwK7OZr.mjs} +107 -83
  17. package/dist/{device-pool-B5pEVa3t.mjs → device-pool-CcBNTvM7.mjs} +18 -12
  18. package/dist/{device-remote-E81KAdXf.mjs → device-remote-DAA0Zm6Z.mjs} +7 -7
  19. package/dist/{doctor-CqPeMsSm.mjs → doctor-B4EITgQB.mjs} +11 -12
  20. package/dist/{doctor-D_CX7ib5.mjs → doctor-BueDfLYG.mjs} +4 -4
  21. package/dist/{error-diagnostics-Dw8Gn3yw.mjs → error-diagnostics-DwhNBFDU.mjs} +62 -52
  22. package/dist/{gc-B7KkDinF.mjs → gc-BRu62v4o.mjs} +66 -57
  23. package/dist/{guide-BS2Xc24o.mjs → guide-D95E0u3i.mjs} +307 -74
  24. package/dist/{ios-B7KLgIp5.mjs → ios-DAju3TVy.mjs} +183 -54
  25. package/dist/{ios-D1h1Di39.mjs → ios-Deh5js5V.mjs} +78 -40
  26. package/dist/{ios-device-xNt0Lg6v.mjs → ios-device-CWOM_S44.mjs} +1 -1
  27. package/dist/{logs-DcX9AvRF.mjs → logs-CAdwW6c2.mjs} +34 -6
  28. package/dist/{logs-query-oqn47Ffx.mjs → logs-query-Cdp5aaCH.mjs} +23 -6
  29. package/dist/{metro-COslRK_F.mjs → metro-CORqXyTL.mjs} +2 -2
  30. package/dist/{ndjson-DcAtEx_K.mjs → ndjson-BZwI1NBv.mjs} +5 -2
  31. package/dist/{ownership-CGPart6T.mjs → ownership-8O073GIC.mjs} +3 -3
  32. package/dist/{ownership-CYOyqxUo.mjs → ownership-DXcol4oU.mjs} +3 -3
  33. package/dist/{ownership-claim-7zRxxTfZ.mjs → ownership-claim-BtPtJVmq.mjs} +38 -23
  34. package/dist/{project-BBTVABUz.mjs → project-fKmuIaMI.mjs} +2 -2
  35. package/dist/{reclaim-C5Jmuahx.mjs → reclaim-CUmwucWG.mjs} +80 -74
  36. package/dist/{reload-Z8nKifAk.mjs → reload-CWpXoOgN.mjs} +32 -24
  37. package/dist/{server-bare-DEk37Mz2.mjs → server-bare-DEzPZLvf.mjs} +2 -2
  38. package/dist/server-expo-44em5Pmg.mjs +2 -0
  39. package/dist/{server-expo-C3ktAwUG.mjs → server-expo-DizT4OMk.mjs} +4 -4
  40. package/dist/{settings-DwU1_U_q.mjs → settings-DEJf6_kJ.mjs} +2 -2
  41. package/dist/slot-launch-CZklomj6.mjs +19 -0
  42. package/dist/{start-BrY4EuIg.mjs → start-B5G4NjiA.mjs} +11 -11
  43. package/dist/{state-CZ-ER-8d.mjs → state-B3XA2wp2.mjs} +9 -9
  44. package/dist/{stats-BCWEVLrM.mjs → stats-BR-kwj0B.mjs} +1 -1
  45. package/dist/{stats-BsF15Bti.mjs → stats-CaaQr4yN.mjs} +3 -3
  46. package/dist/{status-DFI1Hfpg.mjs → status-CI9clRSb.mjs} +55 -24
  47. package/dist/{stop-B7DR4awK.mjs → stop-CQhffpmX.mjs} +2 -2
  48. package/dist/{stop-Btg770vV.mjs → stop-CjlgDk0a.mjs} +93 -42
  49. package/dist/supervisor-run.d.mts +1 -2
  50. package/dist/supervisor-run.mjs +5 -5
  51. package/dist/{remote-cache-DDmJ1Roq.mjs → teardown--gESs9ck.mjs} +985 -887
  52. package/dist/{workspace-process-lock-BKfdys1q.mjs → workspace-process-lock-IBj1Towr.mjs} +1 -1
  53. package/dist/{worktree-BPTbI-LN.mjs → worktree-DtQGO9-s.mjs} +121 -111
  54. package/dist/{worktree-C79xx_kM.mjs → worktree-Eq6TLH8N.mjs} +1 -1
  55. package/dist/{xcode-DeKCribl.mjs → xcode-xnxyvJkD.mjs} +159 -96
  56. package/package.json +5 -5
  57. package/dist/android-K63_0UfF.mjs +0 -727
  58. package/dist/build-lock-BsVoLDaq.mjs +0 -645
  59. package/dist/server-expo-Drd7ksHu.mjs +0 -2
@@ -1,4 +1,4 @@
1
- import { t as ANDROID_AVD_CONFIG_HELP } from "./settings-DwU1_U_q.mjs";
1
+ import { t as ANDROID_AVD_CONFIG_HELP } from "./settings-DEJf6_kJ.mjs";
2
2
  import chalk from "chalk";
3
3
  //#endregion
4
4
  //#region src/guide/index.ts
@@ -31,6 +31,15 @@ branch that is not the default one -- only once the repository has at least
31
31
  one linked worktree, so read it from inside the worktree. A single-checkout
32
32
  session is never told that its own branch is a problem.
33
33
 
34
+ MULTIPLE DEVICES
35
+
36
+ Use ios/android --slot <name> to retain multiple devices in one workspace,
37
+ including several of the same model. Reuse the same slot name on subsequent
38
+ runs. Read guide lifecycle options for the complete slot workflow. Use the
39
+ reported device ID for UI interaction. All slots share Metro; reload can reach
40
+ multiple devices, and a shared bundle request does not prove a slot launched.
41
+ Use stop --slot <name> for one slot, or plain stop for the whole workspace.
42
+
34
43
  NORMAL WORKFLOW
35
44
 
36
45
  Work in the current checkout by default. When the task needs another branch or
@@ -53,6 +62,8 @@ warm checks for existing entries before copying, not during the copy. Do not
53
62
  edit files, install dependencies, or run another warm in that worktree until
54
63
  it finishes; concurrent files can be overwritten or removed.
55
64
  If warm fails or reports incomplete, resolve the reported failure first.
65
+ A warm that cannot record its ownership claim refuses before copying. Follow
66
+ the printed remedy and keep concurrent runs on the same STIM_HOME.
56
67
 
57
68
  Before native worktree work, run doctor for the platform in scope. It checks
58
69
  the source checkout from a linked worktree. Fix relevant findings and inspect the
@@ -66,9 +77,17 @@ For iOS Debug architecture findings, review the project's overrides and imported
66
77
  Podfile helpers using guide lifecycle options. Doctor --fix does not change them.
67
78
  For parallel iOS work, review the recommended optional SimSlim setup in
68
79
  stim guide lifecycle simslim. If simulator process startup times out, check
69
- host memory pressure and free memory before retrying; do not restart other
80
+ host memory pressure and free memory before retrying. Boot progress reports
81
+ current and highest observed pressure; a timeout does not establish OOM.
82
+ Avoid repeated reboots under unchanged pressure; do not restart other
70
83
  workspaces' devices or close their apps without asking. That guide covers
71
84
  the recovery steps and profile tradeoffs.
85
+ An Android boot timeout under observed elevated memory pressure gets one
86
+ extra wait while the emulator process this run started is alive. It never
87
+ launches a second emulator. For a timeout, read guide errors STIM_NO_DEVICE
88
+ and free memory by stopping only unneeded devices in workspaces you own;
89
+ ask before closing other apps or devices. Device count alone is not evidence
90
+ of memory pressure.
72
91
  For a linked native library carrying Git metadata, add the printed .git entries to
73
92
  .fingerprintignore only when the native build does not read Git state.
74
93
 
@@ -88,6 +107,16 @@ them. It preserves source, custom launcher settings, and the shared ccache.
88
107
  stim start
89
108
  stim ios # or: stim android
90
109
 
110
+ For a project using EAS development builds, read stim guide lifecycle eas to
111
+ select a profile from eas.json for the requested target, then run
112
+ stim ios --eas-profile <name> or stim android --eas-profile <name>.
113
+ Ask the user if the profile choice is ambiguous. A miss stops with
114
+ STIM_EAS_BUILD_MISSING and an EAS build command. Run that command only when the
115
+ session authorizes the potentially billable build, then retry Stim. The
116
+ presence of eas.json does not select EAS or authorize building.
117
+ Physical --device targets are supported. Follow EAS device-registration and
118
+ rebuild remedies only when the session authorizes those account changes.
119
+
91
120
  Read stim guide lifecycle concurrency when a build waits on another workspace
92
121
  or a build call times out. A native build can outlive a shell timeout; if the
93
122
  tool call timed out, retry the same command and follow its printed remedy if
@@ -159,8 +188,9 @@ RULES DURING THE LOOP
159
188
  - A clean logs --errors check requires exit code 0 AND no matching errors in
160
189
  captured logs. Exit code 0 alone means the query succeeded, even when errors
161
190
  were printed. Human output shows "No matching log records" on stderr for
162
- zero matches; JSON mode prints zero bytes. This does not prove launch or log
163
- capture succeeded. Do not read the NDJSON files directly.
191
+ zero matches; JSON mode prints zero bytes. A workspace with no captured
192
+ timeline refuses with STIM_NO_PROJECT and, in a monorepo, names the nearest
193
+ registered descendant app with logs. Do not read the NDJSON files directly.
164
194
  - Use stim status when resuming a workspace or recovering missing device,
165
195
  port, server, or build facts. A normal start and platform run already print
166
196
  them. Use stim doctor when a build is unexpectedly slow or the environment
@@ -175,6 +205,8 @@ the stim-<label> (<model> <runtime>) name. Never point Stim at a user-created
175
205
  emulator or simulator.
176
206
 
177
207
  worktree remove parks the workspace's simulator or emulator for later adoption.
208
+ Before owned-device teardown, Stim best-effort closes local agent-device sessions
209
+ on that exact device; failures warn and teardown continues (guide cleanup).
178
210
  A parked device is Stim-owned: never delete one by hand. gc --delete clears verified
179
211
  entries and keeps failures; see guide lifecycle pool. First launch on a
180
212
  physical iPhone can need the one-time taps named by the remedy.
@@ -277,7 +309,13 @@ FULL TOPIC LIST
277
309
  },
278
310
  facts: {
279
311
  summary: "The --json payloads: `start`, `ios`, `android`, `reload`, `stop`, `status`, `doctor`, `device lock`/`unlock`, and the error contract",
280
- preamble: () => `FACTS CONTRACT
312
+ preamble: () => `SLOTS
313
+ Named ios/android runs add slot to their JSON facts. Default-run fields remain
314
+ compatible. status adds a slots array per environment with each named slot's
315
+ ios/android device facts; top-level ios/android still describe default.
316
+ Named collector, lease-holder, and launch keys use platform:slot internally.
317
+
318
+ FACTS CONTRACT
281
319
 
282
320
  \`start\`, \`ios\`, \`android\`, \`reload\`, \`stop\`, \`status\`, \`stats\`, \`doctor\`,
283
321
  and \`device lock\`/\`device unlock\` each print exactly ONE line of JSON on
@@ -341,6 +379,11 @@ line by design (see \`guide logs\`), not this single-payload contract.`,
341
379
  absent for automatic selection; not the app URL scheme
342
380
  cacheKey the shared-build-cache key derived from it (the
343
381
  configuration is part of it: -release-sim vs -debug-sim)
382
+ With --eas-profile, fingerprint is computed by EAS CLI using the selected
383
+ profile/environment, and cacheKey identifies the EAS project and build ID
384
+ separately from local native builds. cacheHit is "remote" for the EAS
385
+ source, including when EAS CLI reuses its own downloaded artifact cache.
386
+
344
387
  cacheHit WHICH LEVEL answered, not a boolean:
345
388
  "local" this machine's shared cache (free, instant)
346
389
  "remote" the project's own Expo buildCacheProvider (a
@@ -866,15 +909,23 @@ discovered, not enumerated.
866
909
 
867
910
  EXIT 0 MEANS THE QUERY SUCCEEDED, whether or not records matched. A clean
868
911
  \`stim logs --errors\` check requires exit code 0 AND no matching errors in
869
- captured logs. An empty result does not prove launch or log capture succeeded;
870
- a workspace with no log directory also returns an empty result.
912
+ captured logs. A workspace that has never produced a timeline refuses with
913
+ STIM_NO_PROJECT instead of passing with an empty result. In a monorepo the
914
+ refusal names the nearest registered descendant app with logs, when there is one.
871
915
 
872
- For zero matches: STDOUT IS EMPTY, exit code 0, and
916
+ Once a timeline file exists, zero matches after filtering means STDOUT IS EMPTY,
917
+ exit code 0, and
873
918
  one dim note on STDERR reading \`No matching log records in <logs dir>\`
874
919
  (human mode only -- \`--json\` prints nothing at all, on either stream).
875
920
  The only exit-1 paths are a malformed query and no project.
876
921
 
922
+ Device collectors and build records carry their named slot. Use --slot phone
923
+ for that slot's timeline; its launch marker cannot hide a sibling's errors.
924
+ Untagged legacy records belong to default. Metro/client records are shared and
925
+ usually untagged: use an unfiltered workspace query to inspect those errors.
926
+
877
927
  FLAGS
928
+ --slot <name> only this slot's records (default includes untagged records)
878
929
  --source <s...> metro, client, device, build (one or more), or all. An
879
930
  unknown value is REJECTED rather than quietly matching
880
931
  nothing.
@@ -1138,6 +1189,24 @@ WHAT WRITES WHAT
1138
1189
  Every refusal from \`ios\` / \`android\` carries a stable CODE. Branch on the
1139
1190
  code, never on the message.`,
1140
1191
  sections: {
1192
+ STIM_EAS_BUILD_MISSING: {
1193
+ summary: "no completed EAS development build matches; build only with session authorization",
1194
+ body: () => `STIM_EAS_BUILD_MISSING
1195
+ No compatible build matches the selected EAS project, profile, platform and
1196
+ native fingerprint. No device was acquired and no local or cloud build was
1197
+ started. The remedy prints the exact npx eas-cli build command. Check session
1198
+ authorization for its potential cost before running it, then retry Stim.
1199
+ See stim guide lifecycle eas.`
1200
+ },
1201
+ STIM_EAS_UNAVAILABLE: {
1202
+ summary: "EAS lookup/download failed, or another run holds the artifact claim",
1203
+ body: () => `STIM_EAS_UNAVAILABLE
1204
+ EAS CLI is unavailable, a profile/fingerprint/list/download operation failed,
1205
+ the response could not be validated, or another run holds the artifact claim.
1206
+ Follow the printed remedy: inspect the named EAS command or retry once the
1207
+ holder finishes. This is not proof that a build is missing. No native build
1208
+ is started. See stim guide lifecycle eas.`
1209
+ },
1141
1210
  STIM_WORKSPACE_STATE: {
1142
1211
  summary: "$STIM_HOME/workspaces could not be prepared, or the digest directory belongs to another project",
1143
1212
  aliases: ["STIM_WORKSPACE_COLLISION"],
@@ -1348,14 +1417,13 @@ code, never on the message.`,
1348
1417
  \`worktree warm\` reports it for the repository-wide warm claim under
1349
1418
  ~/.stim/warm-locks on both paths, including a \`--refresh\` that spawned its
1350
1419
  install and was killed before recording which process: nothing was refreshed
1351
- and nothing was copied. One case is NOT this code for a plain warm: a
1352
- STIM_HOME (or a warm-locks path under it) that is a file rather than a
1353
- directory. No claim can be stored there at all, which is a filesystem state
1354
- that predates claims, so plain \`warm\` degrades to the unsynchronised copy it
1355
- performed before them and \`--refresh\` still refuses.`
1420
+ and nothing was copied. A STIM_HOME or warm-locks ancestor that is a file
1421
+ also refuses. The message names that blocking path and prints a shell-quoted
1422
+ move-aside command. Inspect it first: the file may contain unrelated data.
1423
+ An existing backup prompts before overwriting; preserve both files.`
1356
1424
  },
1357
1425
  STIM_CLAIM_UNAVAILABLE: {
1358
- summary: "this process has no recordable identity, so no build lock or build slot can be taken at all",
1426
+ summary: "a process identity or warm claim store is unavailable, so the protected operation refuses",
1359
1427
  body: () => `STIM_CLAIM_UNAVAILABLE
1360
1428
  Every ownership claim records the holder's process identity, captured through
1361
1429
  the \`unique-pid\` native module. This code is that capture failing: no
@@ -1367,16 +1435,15 @@ code, never on the message.`,
1367
1435
  the same fingerprint while each believed it was alone. Reinstall Stim so the
1368
1436
  module for this platform is present, then run the command again. Nothing was
1369
1437
  built, installed or removed.
1370
- \`worktree warm --refresh\` refuses for the same reason, because it writes to
1371
- the source checkout. Plain \`worktree warm\` does NOT: it prints one dim
1372
- \`lock unavailable (...)\` line and copies unsynchronised. A copy only
1373
- reads, so running it with no claim is what it did before the lock existed,
1374
- while refusing it would break a warm that works today -- an unwritable
1375
- STIM_HOME included.
1376
- It still reads the claim set first, which takes no claim: a live \`--refresh\`
1377
- claim, or a claim it cannot resolve, makes even the unsynchronised copy
1378
- refuse, and the line names what it found. Only a repository nothing is
1379
- warming is copied without a claim.`
1438
+ Both plain \`worktree warm\` and \`worktree warm --refresh\` refuse before
1439
+ copying for the same reason. A copy without a claim would be invisible to a
1440
+ refresh starting after it, allowing the source dependencies to change while
1441
+ they are read.
1442
+ Warm also reports this code for EACCES, EPERM, EROFS and ENOENT while taking a claim.
1443
+ The message names the claim path and original filesystem error. Restore write
1444
+ access to the existing claim store, checking parent permissions, symlink
1445
+ targets, mount access and sandbox rules, then retry. Do not switch STIM_HOME to evade a claim:
1446
+ concurrent runs must coordinate through the same store.`
1380
1447
  },
1381
1448
  STIM_INSTALL_FAILED: {
1382
1449
  summary: "simctl, adb, or devicectl refused the artifact; the one signer-conflict retry",
@@ -1464,7 +1531,10 @@ spending a build or a bundle, that the check can succeed.`,
1464
1531
  profile;
1465
1532
  - it is a development or ad hoc profile whose device list does not name
1466
1533
  this UDID. Register the UDID at developer.apple.com, regenerate the
1467
- profile, and build once from Xcode.`
1534
+ profile, and build once from Xcode.
1535
+ With --eas-profile, follow the EAS device:create and build commands in the
1536
+ refusal instead. Registration, signing changes and cloud builds need session
1537
+ authorization. See stim guide lifecycle eas.`
1468
1538
  },
1469
1539
  STIM_NO_SIGNING_IDENTITY: {
1470
1540
  summary: "no single keychain identity resolves; ios.signingIdentitySha1 for two certificates",
@@ -1642,8 +1712,8 @@ so a Debug run on one is wired to a LAN origin instead of localhost.`,
1642
1712
  and \`adb devices\`, then retry after any other run finishes. Keep the AVD and
1643
1713
  its process locks while its state is unverified.
1644
1714
  On iOS a slow first boot is waited out for up to ten minutes while the
1645
- simulator still reports Booting -- a long silent wait on a loaded machine
1646
- is patience, not a hang. The failure names the udid and the wait.
1715
+ simulator reports Booting or Booted but bootstatus has not completed.
1716
+ Booted alone does not end that wait. The failure names the udid and the wait.
1647
1717
  After boot, a process-spawn probe must finish within 30 seconds before
1648
1718
  installation. If it fails, the refusal includes observed host memory pressure
1649
1719
  when available. Free memory before retrying under pressure; see
@@ -1658,6 +1728,20 @@ so a Debug run on one is wired to a LAN origin instead of localhost.`,
1658
1728
  normally live under ~/.android/avd, and a booted AVD can use several GB. A
1659
1729
  boot whose emulator process exited is also reported at once rather than after
1660
1730
  the full cold-boot timeout.
1731
+ Before a local Android emulator boot, a memory phase line reports observed
1732
+ warning or critical macOS host memory pressure. If the initial boot wait
1733
+ times out under that pressure and the process this run started is still
1734
+ alive, Stim retries the WAIT once for up to 240 seconds more on the same
1735
+ serial; it never starts a second emulator. New-device preparation normally
1736
+ waits 120 seconds first; the later boot check waits 240 seconds. There is
1737
+ no extra wait for normal or unavailable pressure, a process that exited,
1738
+ or a process whose liveness this run cannot check. adb probes are bounded;
1739
+ final diagnostic queries share a separate budget of up to five seconds.
1740
+ A timeout remedy reports observed pressure and the running owned-device
1741
+ count when available. Device count alone does not establish memory pressure
1742
+ or trigger the extra wait. Stop an unneeded device with \`stim stop\` only
1743
+ in a workspace you own, then retry \`stim android\`; ask before closing
1744
+ other apps or devices. Specific emulator log errors retain their remedies.
1661
1745
 
1662
1746
  "this project's sim is X, but --device-type asked for Y"
1663
1747
  The project already owns a simulator of a different model, and Stim will
@@ -1925,7 +2009,9 @@ captured" (in metro.ndjson, bare RN)
1925
2009
  with no package.json above it, or one whose nearest package.json does not
1926
2010
  parse or depends on neither react-native nor expo, so the directory is not
1927
2011
  an app (the refusal names that package.json and says which of the two it
1928
- is; \`doctor\` reports the same directory as a finding), an
2012
+ is; \`doctor\` reports the same directory as a finding), a \`logs\` query in
2013
+ a workspace that has never produced a log timeline (the refusal names the
2014
+ nearest registered descendant app with logs when one exists), an
1929
2015
  android/app/build.gradle that declares product flavors with
1930
2016
  no variant selected (the refusal names the debug variants), or a
1931
2017
  \`--device-type\`, \`--runtime\` or \`--system-image\` name that is BLANK or
@@ -2083,24 +2169,14 @@ not on any remote" (worktree remove)
2083
2169
  which is usually the repository root, so every app of it reads the same one.`
2084
2170
  },
2085
2171
  warm: {
2086
- summary: "two warm refusals whose text is incomplete, known and not fixed",
2087
- body: () => `"Could not warm this worktree: EACCES: permission denied, mkdir
2088
- '<home>/warm-locks/<name>.lock'" (worktree warm --refresh)
2089
- A STIM_HOME that \`--refresh\` cannot write. The refusal itself is right -- it
2090
- writes to the source checkout, so it will not run without a claim -- but it
2091
- carries no \`failed: <CODE>\` line, because EACCES is not a Stim code, and no
2092
- fix line. Make STIM_HOME writable, or set STIM_HOME to somewhere writable,
2093
- then run it again. A plain warm degrades in this state rather than refusing.
2094
-
2095
- "... the claim path is a file, not a claim directory", with a \`rm -rf\` that
2096
- changes nothing (worktree warm --refresh)
2097
- When \`$STIM_HOME/warm-locks\` or STIM_HOME itself is a regular FILE, the
2098
- refusal names \`warm-locks/<name>.lock\` -- a path that cannot exist under a
2099
- file -- so running the printed removal does nothing and the next run refuses
2100
- identically. Remove the file that is in the way and run it again. A plain
2101
- warm copies unsynchronised in this state.
2102
-
2103
- Both are documented rather than fixed: appandflow/stim#696.`
2172
+ summary: "worktree warm claim storage refusals and recovery",
2173
+ body: () => `Both plain warm and --refresh require an ownership claim.
2174
+ Permission-denied or read-only claim storage reports STIM_CLAIM_UNAVAILABLE
2175
+ and names the path. Restore access to the existing claim store and retry.
2176
+ A non-directory claim ancestor reports STIM_CLAIM_REFUSED and names the
2177
+ blocking file. Inspect it and use the printed move-aside command to preserve
2178
+ its contents before retrying. See guide errors STIM_CLAIM_UNAVAILABLE and
2179
+ guide errors STIM_CLAIM_REFUSED for the recovery details.`
2104
2180
  },
2105
2181
  carry: {
2106
2182
  summary: "worktree warm copy results, lockfile mismatches, and remedies",
@@ -2281,6 +2357,7 @@ a seed belongs to this workflow.
2281
2357
  # For a clean check, require exit 0 AND no matching errors.
2282
2358
  # Human mode prints "No matching log records" on stderr for zero matches.
2283
2359
  # Exit 0 alone means the query succeeded, even when it printed errors.
2360
+ # A workspace with no captured timeline refuses with STIM_NO_PROJECT.
2284
2361
  stim logs --errors
2285
2362
 
2286
2363
  # 5. Edit the JS. Fast Refresh applies it; no Stim command is involved.
@@ -2376,6 +2453,12 @@ ANDROID EMULATOR RESTARTS
2376
2453
  Rerun \`stim android\` with the same build options in that workspace to restore Metro
2377
2454
  forwarding, then reopen agent-device on the serial that run reports.
2378
2455
  Other workspaces' simulators and automation sessions remain theirs.
2456
+ A local emulator boot reports observed warning or critical macOS memory
2457
+ pressure before starting. A timeout under elevated pressure gets one
2458
+ additional wait of up to 240 seconds only while the process this run
2459
+ started is alive. Stim keeps waiting on the same emulator; it never
2460
+ launches a duplicate. See \`stim guide errors STIM_NO_DEVICE\` for the
2461
+ wait windows, diagnostics, and recovery steps.
2379
2462
 
2380
2463
  DESTRUCTIVE COMMANDS -- ask the user first
2381
2464
  gc --delete deletes orphaned stim-* devices, tens of GB
@@ -2403,6 +2486,82 @@ TWO REPORTS, TWO QUESTIONS
2403
2486
  the machine, with a hit rate and an estimate of the time saved (see
2404
2487
  \`guide facts stats\`).`,
2405
2488
  sections: {
2489
+ eas: {
2490
+ summary: "download a matching EAS development build; explicit profile, costs, cache and miss remedies",
2491
+ body: () => `EAS DEVELOPMENT BUILDS
2492
+
2493
+ stim ios --eas-profile ios-simulator
2494
+ stim android --eas-profile development
2495
+ stim ios --eas-profile development-device --device <udid>
2496
+ stim android --eas-profile development --device <serial>
2497
+
2498
+ An eas.json file does not select EAS automatically. The flag names the profile
2499
+ and selects EAS Build as the artifact source. The profile must resolve to
2500
+ "developmentClient": true and "distribution": "internal". For iOS, set
2501
+ "ios.simulator": true for a simulator, or false (or omit it) for --device.
2502
+ An explicit ios.buildConfiguration must be Debug;
2503
+ android.gradleCommand must be a single :app:assemble<Variant>Debug task producing
2504
+ an APK. Install eas-cli and authenticate with eas login
2505
+ or EXPO_TOKEN. The Expo app must already be linked to the intended EAS project.
2506
+
2507
+ Use the profile the user names. Otherwise inspect eas.json, including extends
2508
+ and platform overrides, for the requested platform, simulator or physical
2509
+ device, and app variant or environment. Choose a profile only when those
2510
+ requirements identify one compatible development profile. If there is no
2511
+ compatible profile or the choice is ambiguous, ask the user. Profile names
2512
+ alone do not establish compatibility. Confirm the resolved settings with
2513
+ npx eas-cli config --platform <ios|android> --profile <name> --json --non-interactive.
2514
+ Pass the selected name to --eas-profile; the CLI does not infer it.
2515
+
2516
+ Stim delegates profile inheritance, environment resolution and fingerprinting
2517
+ to EAS CLI. fingerprint:generate uploads fingerprint metadata to EAS. It does
2518
+ not start a native build. EAS access is needed even when the artifact is
2519
+ already cached, because the current profile and fingerprint must be resolved.
2520
+
2521
+ Stim matches the EAS project, profile, native fingerprint, platform and
2522
+ simulator/internal distribution target against a completed build. It downloads
2523
+ an iOS .app or Android .apk with EAS CLI, then uses its existing installation,
2524
+ Metro connection, log capture and launch verification on the selected device.
2525
+ The flag also works with --remote; remote EAS Simulator sessions have their
2526
+ own costs, independent of this download path. Local --scheme, --configuration,
2527
+ --variant and --no-build-cache selectors
2528
+ cannot be combined with --eas-profile. Local configuration/variant defaults
2529
+ are ignored for this development-build run.
2530
+
2531
+ Physical devices use Stim's usual selection and lease rules. An iOS app must
2532
+ have a development-client URL scheme and a valid embedded provisioning profile
2533
+ that includes the target UDID. Stim installs the signed app without re-signing
2534
+ it. If the profile does not admit the device, the remedy points to
2535
+ npx eas-cli device:create and npx eas-cli build --platform ios --profile <name>.
2536
+ Registration, signing changes and cloud builds need session authorization.
2537
+ Run the EAS build interactively when its provisioning profile needs refreshing,
2538
+ then retry the same Stim command.
2539
+
2540
+ Start Metro with stim start as usual. Metro uses the local workspace's
2541
+ environment; arrange the appropriate local variables before starting it.
2542
+ EAS environment resolution for native fingerprinting does not configure Metro.
2543
+
2544
+ EAS CLI caches extracted artifacts by project and build ID in its own temporary
2545
+ cache. Stim calls build:download and uses the returned artifact without making
2546
+ another cache copy. Every run queries the latest matching build, so a rebuild
2547
+ with a refreshed provisioning profile is selected even if its native fingerprint
2548
+ is unchanged. Stim coordinates concurrent downloads by project and build ID.
2549
+ cacheHit: "remote" identifies the EAS source, including when EAS CLI reuses its
2550
+ disk cache. The fingerprint fact is the EAS native fingerprint. Stim's local
2551
+ buildCache and remoteBuildCache settings do not control EAS CLI's cache.
2552
+ EAS cache files are managed by EAS CLI and are outside Stim gc.
2553
+
2554
+ On STIM_EAS_BUILD_MISSING, Stim stops before acquiring a device and prints:
2555
+
2556
+ npx eas-cli build --platform ios --profile ios-simulator
2557
+
2558
+ Run that command only when the session authorizes the potentially billable
2559
+ cloud build. Once it completes, retry the same Stim command. No build-on-miss
2560
+ flag exists, and Stim never starts a cloud build or falls back to local
2561
+ compilation in this mode. Authentication, network, invalid output or download
2562
+ failures produce STIM_EAS_UNAVAILABLE, with the failed EAS command to inspect.
2563
+ If another run holds the artifact claim, wait for it to finish and retry.`
2564
+ },
2406
2565
  readiness: {
2407
2566
  summary: "implement optional pending/ready app logs, deadlines, errors, and platform isolation",
2408
2567
  body: () => `OPTIONAL APP READINESS
@@ -2572,9 +2731,11 @@ result as proof instead of requiring an unrelated screenshot.`
2572
2731
 
2573
2732
  \`--refresh\` always prints a \`lock\` line, then its own facts, one per
2574
2733
  step, before those. A plain warm prints the \`lock\` line only when its copy
2575
- actually waited for another warm:
2734
+ actually waited for another warm. A current refresh reports
2735
+ \`lock acquired shared (seed current)\`; a refresh needing changes
2736
+ reports \`acquired exclusive\` with the reason from its initial check:
2576
2737
 
2577
- lock acquired (waited 12s for stim worktree warm --refresh pid 41233) -- stim guide lifecycle options
2738
+ lock acquired exclusive (fast-forward 2 commits) (waited 12s for stim worktree warm --refresh pid 41233) -- stim guide lifecycle options
2578
2739
  checkout janic/wip 2 commits behind origin/janic/wip -> fast-forwarded to 4b81e0c
2579
2740
  not the default branch (main); worktrees seeded from this copy
2580
2741
  carry janic/wip's dependencies
@@ -2994,6 +3155,20 @@ WHAT MAKES THE CACHE ACTUALLY HIT: .FINGERPRINTIGNORE
2994
3155
  Never ignore a real native input -- a Podfile, a gradle file, the app config
2995
3156
  -- to force a hit: that trades a slow build for a wrong one.
2996
3157
 
3158
+
3159
+ React Native Test App can generate checkout-specific Android output under
3160
+ node_modules/react-native-test-app/android/support/build. If fingerprint
3161
+ differences point only there, add these project-specific entries:
3162
+
3163
+ node_modules/react-native-test-app/android/support/build
3164
+ node_modules/react-native-test-app/android/support/build/**/*
3165
+
3166
+ Put them in the app root's .fingerprintignore and use the path as it appears
3167
+ in the fingerprint sources; a hoisted or linked dependency may differ.
3168
+ Keep android/support/build.gradle and native sources included. Confirm equal
3169
+ fingerprints across built worktrees, and confirm a real native input edit
3170
+ still changes the hash. These exclusions are not Stim defaults.
3171
+
2997
3172
  \`stim doctor\` measures this directly rather than reading the file: it
2998
3173
  fingerprints HEAD in a temporary clean worktree, compares, and reports a
2999
3174
  mismatch naming the differing sources. Untracked, non-gitignored files under
@@ -3081,13 +3256,13 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
3081
3256
  summary: "every flag per command, Android variants and flavors, the per-run simulator model, runtime and system image",
3082
3257
  body: () => `THE OPTION SURFACE, IN FULL
3083
3258
  start --json --wait <seconds> --remote --reset-cache
3084
- ios --json --no-metro-check --no-build-cache --scheme <name> --configuration <name> --device-type <name> --runtime <version> --device [udid] --wait <seconds> --no-wait --remote <proxy|eas>
3085
- android --json --no-metro-check --no-build-cache --variant <name> --system-image <id> --device [serial] --wait <seconds> --no-wait --remote <proxy|eas>
3259
+ ios --slot <name> --json --no-metro-check --no-build-cache --scheme <name> --configuration <name> --device-type <name> --runtime <version> --device [udid] --wait <seconds> --no-wait --remote <proxy|eas>
3260
+ android --slot <name> --json --no-metro-check --no-build-cache --variant <name> --system-image <id> --device [serial] --wait <seconds> --no-wait --remote <proxy|eas>
3086
3261
  reload [ios|android] --json
3087
- device lock <ios|android> [id] --for <duration> --wait <seconds> --json;
3088
- unlock [ios|android] --json
3089
- logs --source --level --since --grep --tail --follow --errors --json
3090
- stop --json
3262
+ device lock <ios|android> [id] --slot <name> --for <duration> --wait <seconds> --json;
3263
+ unlock [ios|android] --slot <name> --json
3264
+ logs --slot <name> --source --level --since --grep --tail --follow --errors --json
3265
+ stop --slot <name> --json
3091
3266
  status --json (already machine-wide)
3092
3267
  stats --json (this project and machine-wide)
3093
3268
  doctor --json --fix --platform <ios|android>
@@ -3095,6 +3270,38 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
3095
3270
  gc --delete --older-than <days> --cache <name|all>
3096
3271
  worktree warm --refresh; remove [path] --force
3097
3272
 
3273
+ DEVICE SLOTS
3274
+ Use --slot <name> on ios or android to keep any number of simulators,
3275
+ emulators, or physical devices in the same workspace. Names are reusable
3276
+ identities, not models: two slots can request identical models. A slot has
3277
+ one target per platform. Omitting the flag selects default and preserves
3278
+ the workspace's existing assignment.
3279
+
3280
+ stim ios --slot phone
3281
+ stim ios --slot tablet --device-type "iPad Pro 13-inch (M4)"
3282
+ stim ios --slot hardware --device <udid>
3283
+ stim android --slot second-phone
3284
+ stim logs --slot tablet --source device
3285
+ stim stop --slot tablet
3286
+
3287
+ All slots share this workspace's Metro server and build cache. Native CLI
3288
+ runs serialize workspace mutations; a waiting run can wait up to 30 minutes.
3289
+ Named slots support local devices; remote sessions use the default slot.
3290
+ Names use 1-64 letters, digits, underscores or hyphens, starting with a letter
3291
+ or digit. Reserved object-property names are refused.
3292
+
3293
+ stop --slot shuts down that slot's owned devices and releases its leases,
3294
+ retaining Metro and sibling slots. Plain stop handles the whole workspace.
3295
+ status reports named devices under slots and counts their memory. Device
3296
+ caps count every slot. Recycling uses the same model/runtime-matched pool
3297
+ for every slot, with one shared cap per platform and oldest-first eviction.
3298
+
3299
+ Metro cannot reliably identify a particular simulator's bundle request.
3300
+ When slots coexist, a shared bundle event is not proof that a particular
3301
+ slot launched: Debug launch can report unverified. Check the reported device
3302
+ directly. Release verification still checks its process. reload ios/android
3303
+ addresses matching Metro peers across slots; it is not a single-slot reload.
3304
+
3098
3305
  That is the whole surface today, and it is deliberately small. It can grow
3099
3306
  when a flag is genuinely the best answer -- but project-specific knowledge
3100
3307
  (release builds, variants, device targets) belongs in a script the repo owns,
@@ -3107,8 +3314,9 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
3107
3314
  Running it in the source checkout refuses.
3108
3315
 
3109
3316
  \`--refresh\` WRITES TO THE SOURCE CHECKOUT before the copy, which is why it
3110
- is opt-in: it fetches, fast-forwards whatever branch is checked out there to
3111
- its \`@{upstream}\`, and installs what the new commits moved. It refuses a
3317
+ is opt-in: it checks the upstream, fetches changes when needed, and fast-forwards
3318
+ whatever branch is checked out there to its \`@{upstream}\`, and installs
3319
+ what the new commits moved. It refuses a
3112
3320
  source checkout it cannot move -- uncommitted changes to tracked files or a
3113
3321
  rebase or merge in progress (STIM_MAIN_DIRTY), a detached HEAD
3114
3322
  (STIM_MAIN_DETACHED), or a branch that is both ahead and behind
@@ -3142,9 +3350,15 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
3142
3350
  writes no failure anywhere -- is recovered the same way a failed one is.
3143
3351
 
3144
3352
  One lock per repository serialises this, whether or not you pass the flag:
3145
- \`--refresh\` holds it exclusively, and every copy holds it shared, so a copy
3146
- can never read a node_modules a refresh is rewriting. Two plain warms of the
3147
- same repository run together. The lock is keyed on the repository root, so
3353
+ \`--refresh\` first checks under a shared claim. It uses \`git ls-remote\`
3354
+ to compare the upstream's advertised commit with the local tracking ref without
3355
+ updating Git refs or FETCH_HEAD. When the checkout, dependencies and Pods are
3356
+ current, it keeps that shared claim through the copy, overlapping other copies.
3357
+ A changed or unconfirmed upstream, fast-forward, dependency install or Pods
3358
+ install requires an exclusive claim. It releases shared before waiting for
3359
+ exclusive, then checks the checkout again before fetching or writing. Every
3360
+ copy holds shared, so no copy reads a node_modules a refresh is rewriting.
3361
+ The lock is keyed on the repository root, so
3148
3362
  two apps of one monorepo share it. It is the same ownership claim the build
3149
3363
  locks take: the holder's process IDENTITY decides, so a holder that dies frees
3150
3364
  it, a recycled pid cannot keep it, and a refresh whose install runs in a
@@ -3152,20 +3366,13 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
3152
3366
  holder every 30s and gives up with STIM_LOCK_TIMEOUT; a claim Stim cannot
3153
3367
  resolve refuses with STIM_CLAIM_REFUSED and names the removal rather than
3154
3368
  waiting on it or removing it.
3155
- A plain warm that cannot record a claim at all -- an unwritable STIM_HOME, a
3156
- STIM_HOME on a read-only mount or under a dangling symlink, a STIM_HOME that
3157
- is a file, or no \`unique-pid\` build for this platform
3158
- (STIM_CLAIM_UNAVAILABLE) -- says so in one dim line and copies without it,
3159
- exactly as it did before the lock existed, because a copy only reads.
3160
- \`--refresh\` refuses instead, because it writes. Before that unsynchronised
3161
- copy starts, warm still READS the claim set, which needs no claim of its own:
3162
- if a refresh holds this repository, or a claim in it cannot be resolved, the
3163
- copy refuses rather than reading a tree that is being rewritten. That read is
3164
- not a lock, so a refresh that starts after it is not covered; it closes the
3165
- window in which one is already installing. Closing the rest needs a claim,
3166
- which is the one thing that state cannot record, so the residual is
3167
- documented rather than fixed (appandflow/stim#696), as is the
3168
- \`acquired (waited 0s ...)\` a contended-but-fast wait prints.
3369
+ Both plain warm and --refresh refuse before copying when no claim can be
3370
+ recorded. Missing process identity, denied write access and read-only storage
3371
+ report STIM_CLAIM_UNAVAILABLE with recovery instructions. Restore access to the
3372
+ same claim store before retrying; choosing a different STIM_HOME would hide
3373
+ concurrent warm operations. A non-directory claim path reports
3374
+ STIM_CLAIM_REFUSED and names the blocking file with a move-aside command that
3375
+ preserves its contents. Sub-second waits omit the elapsed-time suffix.
3169
3376
 
3170
3377
  Wait for warm to exit successfully (exit code 0) before running start,
3171
3378
  ios, android, or a dependency install in that worktree. If a shell tool
@@ -3600,6 +3807,13 @@ HOST MEMORY PRESSURE AND STALLED SIMULATORS
3600
3807
  a final boot-state query can take 30 seconds. Opening the Simulator app after
3601
3808
  boot is best-effort and takes at most 5 seconds. A timeout is not proof of an
3602
3809
  app crash or OOM.
3810
+ During boot, Stim reports elapsed time, the simulator name, last boot output,
3811
+ current pressure and the highest observed pressure roughly every 15 seconds.
3812
+ Failure diagnostics retain the highest pressure and unavailable sample count;
3813
+ they do not infer the cause of a timeout. Monitoring stops when boot ends.
3814
+ Monitoring and timeout handling are best-effort: synchronous CLI work can
3815
+ delay them. Diagnostics report observation gaps over 30 seconds; pressure
3816
+ during a gap is unobserved, even when the surrounding readings are normal.
3603
3817
  Doctor and failure diagnostics report macOS memory pressure when available;
3604
3818
  a failed query remains unknown. Existing swap or low free RAM alone is not
3605
3819
  enough to diagnose pressure.
@@ -3614,7 +3828,16 @@ HOST MEMORY PRESSURE AND STALLED SIMULATORS
3614
3828
  },
3615
3829
  cleanup: {
3616
3830
  summary: "Where simulators come from, and how they get reclaimed",
3617
- preamble: () => `CLEANUP AND DISK
3831
+ preamble: () => `DEVICE SLOTS
3832
+
3833
+ Cleanup enumerates every slot. stop --slot <name> keeps the shared server and
3834
+ other slots; plain stop and worktree remove handle the whole workspace.
3835
+ An upgrade retains existing device assignments as the default slot. Do not
3836
+ wipe state to upgrade: it records ownership needed for safe teardown. Use the
3837
+ same slot-aware CLI for all commands while named assignments exist; older
3838
+ versions cannot reliably manage their assignments.
3839
+
3840
+ CLEANUP AND DISK
3618
3841
 
3619
3842
  WHAT RECLAIMS AN OWNED DEVICE
3620
3843
  stim worktree remove parks eligible owned simulators and emulators
@@ -3631,6 +3854,16 @@ Those are the only two commands that delete. \`stim stop\` shuts a device
3631
3854
  DOWN and leaves it assigned, which is what makes returning to a branch cost a
3632
3855
  boot rather than a create, a provision and a reinstall.
3633
3856
 
3857
+ Before shutting down, parking or deleting an owned device, Stim best-effort
3858
+ closes local agent-device sessions bound to its exact iOS UDID or live Android
3859
+ serial. It rechecks device ownership and uses agent-device's rejecting session
3860
+ target guard; sessions on other devices stay open. Physical devices are outside
3861
+ this cleanup. agent-device is optional: a missing binary skips this step, and a
3862
+ failed or timed-out list/close prints a device line on stderr while teardown
3863
+ continues. Cleanup allows at most 15 seconds of agent-device calls per device.
3864
+ A local daemon that cannot use socket transport or a CLI without the target guard
3865
+ skips cleanup with a warning; no remote daemon is used.
3866
+
3634
3867
  Neither touches $STIM_HOME/stats.json: \`gc\` never reports or trims the run
3635
3868
  counters \`stats\` prints, and there is no reset flag. Delete that one file to
3636
3869
  start the counters over. A file this version cannot read -- unparseable, or