stim 1.11.0 → 1.13.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 (82) hide show
  1. package/README.md +3 -1
  2. package/dist/{activity-BtG4Va9l.mjs → activity-DfVKZTEB.mjs} +44 -26
  3. package/dist/{android-BA2i_KAV.mjs → android-BHfAu2mf.mjs} +1 -1
  4. package/dist/{android-C1q4l523.mjs → android-CD8pr8Q2.mjs} +112 -54
  5. package/dist/{android-cas-DfJkwCXW.mjs → android-cas-B9Kv9aIy.mjs} +12 -10
  6. package/dist/{android-C4zG2Tuc.mjs → android-qx_31wJo.mjs} +414 -118
  7. package/dist/{app-install-DHut6JnV.mjs → app-install-CDFY2nUL.mjs} +1 -1
  8. package/dist/budget-E5o91NR1.mjs +1739 -0
  9. package/dist/{build-plan-CeAYgKAx.mjs → build-plan-BTa93o_r.mjs} +125 -107
  10. package/dist/{build-progress-CDwogz6R.mjs → build-progress-l7d-X-ZP.mjs} +76 -8
  11. package/dist/{cache-manifest-Pubfp8wV.mjs → cache-manifest-DWPF4RKJ.mjs} +1 -1
  12. package/dist/cache-manifest.mjs +1 -1
  13. package/dist/cdp-Dq_ypAlJ.mjs +115 -0
  14. package/dist/chrome-B5hWgQJS.mjs +61 -0
  15. package/dist/cli.mjs +17 -16
  16. package/dist/collector-run.mjs +15 -9
  17. package/dist/{command-output-D5z2_bcF.mjs → command-output-BIL7rlX7.mjs} +1 -1
  18. package/dist/{config-BEq0Qw_a.mjs → config-0jCcMM0E.mjs} +10 -1
  19. package/dist/created-devices-Cd558h31.mjs +37 -0
  20. package/dist/{dependency-state-r9UesA9a.mjs → dependency-state-DNN0yfx6.mjs} +1 -1
  21. package/dist/{deps-OF7CD5Dx.mjs → deps-2Wo81np9.mjs} +2 -2
  22. package/dist/{device-BwH2iI3O.mjs → device-Cf7vl-so.mjs} +7 -7
  23. package/dist/{device-lease-D83nqeMX.mjs → device-lease-CL9dZf73.mjs} +55 -6
  24. package/dist/{device-pool-DHj7cdPy.mjs → device-pool-MuoiyRcu.mjs} +4 -4
  25. package/dist/{device-remote-BQZSnnmE.mjs → device-remote-DT9ngZiD.mjs} +254 -579
  26. package/dist/{doctor-D8QdMRMf.mjs → doctor-D2iysNNH.mjs} +50 -20
  27. package/dist/{error-diagnostics-UYFSQwRy.mjs → error-diagnostics-BoiOhck8.mjs} +7 -7
  28. package/dist/{gc-COZteyiq.mjs → gc-eA-PRBX8.mjs} +1022 -131
  29. package/dist/{guide-NDvTmMi-.mjs → guide-BfZ0OmiP.mjs} +909 -132
  30. package/dist/{guide-status-CWvti2Lg.mjs → guide-status-BaMYu55M.mjs} +2 -2
  31. package/dist/{idle-NETbeNiQ.mjs → idle-DxLi5OkN.mjs} +43 -10
  32. package/dist/{in-use-DVhlSoyn.mjs → in-use-wYEaKQM9.mjs} +11 -7
  33. package/dist/{ios-CQLKpNfr.mjs → ios-CcfCeZ19.mjs} +103 -58
  34. package/dist/{ios-DArbmwer.mjs → ios-CmEzZiOj.mjs} +31 -22
  35. package/dist/{ios-device-CwDZo0X4.mjs → ios-device-BW-t_Lkv.mjs} +1 -1
  36. package/dist/{launch-verify-CuPDGXdr.mjs → launch-verify-CuJaBpEa.mjs} +33 -20
  37. package/dist/{logs-B44a1UeU.mjs → logs-DcSRbC2t.mjs} +12 -11
  38. package/dist/{merge-state-DDxS-XIZ.mjs → merge-state-ytFQCeh4.mjs} +38 -8
  39. package/dist/{metro-BWLOJ_Wr.mjs → metro-BPeY1YMh.mjs} +5 -5
  40. package/dist/{named-ports-Dc7neAlc.mjs → named-ports-n7tnJsI8.mjs} +78 -6
  41. package/dist/native-runtime-CiiFHt0e.mjs +74 -0
  42. package/dist/{ownership-D2xbLVoz.mjs → ownership-Bz2kETAS.mjs} +2 -2
  43. package/dist/{ownership-BiojdtAp.mjs → ownership-PUP4Dwai.mjs} +175 -217
  44. package/dist/page-BTu7I60K.mjs +29 -0
  45. package/dist/{ports-DwBaRFJs.mjs → ports-C5UutqpJ.mjs} +5 -5
  46. package/dist/{project-BXZzOABt.mjs → project-DGeclhvO.mjs} +55 -87
  47. package/dist/{reload-By9Dwsrt.mjs → reload-C-cV5yy_.mjs} +80 -21
  48. package/dist/remote-cache--aehr4Pr.mjs +526 -0
  49. package/dist/{server-bare-D_RK1RCj.mjs → server-bare-Bq8BpSIo.mjs} +1 -1
  50. package/dist/server-expo-BeYwP_Xy.mjs +2 -0
  51. package/dist/{server-expo-o6H5gcBc.mjs → server-expo-DUCg52-K.mjs} +2 -2
  52. package/dist/{settings-C0ugALSC.mjs → settings-ssVBToDR.mjs} +29 -17
  53. package/dist/{settings-B5oFZEri.mjs → settings-xB0Frkg6.mjs} +13 -4
  54. package/dist/settings.schema.json +74 -2
  55. package/dist/{simslim-CIR3IYJO.mjs → simslim-Bbg9UWrt.mjs} +13 -6
  56. package/dist/{slot-launch-rggO1Esw.mjs → slot-launch-BIWEW-wI.mjs} +4 -4
  57. package/dist/{spawn-claims-BJwXTMPt.mjs → spawn-claims-PT8aQpCa.mjs} +1 -1
  58. package/dist/{start-BSsf2tUs.mjs → start-BZhItkiL.mjs} +110 -18
  59. package/dist/{start-i8zc9VmS.mjs → start-Rw2GD3nV.mjs} +1 -1
  60. package/dist/{state-WGdIbi_d.mjs → state-DwAU2_-D.mjs} +15 -3
  61. package/dist/state-l1pYwOre.mjs +148 -0
  62. package/dist/{stats-DBYJ5bxL.mjs → stats-CaSwmsSZ.mjs} +4 -4
  63. package/dist/{stats-BAgJaLA4.mjs → stats-D8rUTPHd.mjs} +1 -1
  64. package/dist/{status-CitcBRsC.mjs → status-TJlL0HWo.mjs} +649 -116
  65. package/dist/{status-Q-WDEGCB.mjs → status-sszP4XKU.mjs} +130 -28
  66. package/dist/stim-desktop-CXt8-Z3J.mjs +40 -0
  67. package/dist/{stop-DIhrvu0B.mjs → stop-BZ2qMiBP.mjs} +50 -18
  68. package/dist/{stop-rst5mL14.mjs → stop-DskDKNTU.mjs} +2 -2
  69. package/dist/supervisor-run.mjs +11 -11
  70. package/dist/web-run.d.mts +22 -0
  71. package/dist/web-run.mjs +481 -0
  72. package/dist/web-sWXd5iek.mjs +394 -0
  73. package/dist/{worktree-DhxDTK44.mjs → worktree-3cBa-bQt.mjs} +1 -1
  74. package/dist/{worktree-KOjMX3zA.mjs → worktree-D4Do_XiQ.mjs} +229 -21
  75. package/dist/{worktree-BlizKDpZ.mjs → worktree-DeBQSVtR.mjs} +3 -1
  76. package/dist/{xcode-B5SPD--o.mjs → xcode-LM6A8cNZ.mjs} +7 -7
  77. package/package.json +5 -5
  78. package/dist/budget-De3P2rq_.mjs +0 -620
  79. package/dist/native-run-D7yB_AsB.mjs +0 -143
  80. package/dist/server-expo-B-CCUJZj.mjs +0 -2
  81. package/dist/workspace-process-lock-Cx74RSDZ.mjs +0 -57
  82. package/dist/workspaces-COD2xCz-.mjs +0 -1013
@@ -1,20 +1,23 @@
1
- import { D as process_identity_exports, H as projectDeviceSlots, O as acquireAvdClaim, b as removeProject, f as getProject, g as loadConfig, h as isPathPrefix, i as clearDevice, k as ownership_claim_exports, u as getConfigDir, w as upsertProject } from "./config-BEq0Qw_a.mjs";
1
+ import { A as ownership_claim_exports, O as process_identity_exports, T as upsertProject, U as projectDeviceSlots, _ as loadConfig, a as clearDevice, d as getConfigDir, g as isPathPrefix, k as acquireAvdClaim, p as getProject, x as removeProject } from "./config-0jCcMM0E.mjs";
2
2
  import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
- import { O as workspaceDir, c as signalProcessTree, h as recordWorkspaceUse, i as pidExists, m as readWorkspaceState, s as resolveProjectMetro, t as killMetroTree } from "./metro-BWLOJ_Wr.mjs";
4
- import { C as sourceCheckoutOf, S as restoreFile, T as warmWorktreePaths, _ as readWorktreeExclude, b as resolveFullRef, d as hasUncommittedWork, f as isPodInstallChurn, g as podsOutOfSync, h as locallyKnownUpstream, i as depsOutOfSync, l as hasPopulatedSubmodules, m as listWorktrees, n as cloneIgnoredEntries, o as dirtyPaths, r as deleteBranch, s as gitCommonDir, t as branchExists, u as hasRemote, v as removeWorktree, w as unpushedCommits } from "./worktree-BlizKDpZ.mjs";
5
- import { o as findProjectRoot } from "./project-BXZzOABt.mjs";
6
- import { a as clearRemoteSession, i as clearManagedMetroTunnel, l as readRemoteSession, s as readMetroTunnel, u as readRemoteSessionId } from "./state-WGdIbi_d.mjs";
7
- import { a as formatElapsed, h as shortUdid, i as formatDuration, l as phaseLine, p as releasedLeaseFact, u as plural } from "./command-output-D5z2_bcF.mjs";
8
- import { u as releaseWorkspaceLeases } from "./device-lease-D83nqeMX.mjs";
9
- import { L as unknownSettingKeys, P as settingShapeErrors, a as SETTING_SHAPE_REMEDY, j as resolveSettings } from "./settings-B5oFZEri.mjs";
10
- import { d as teardownOwnedAvd, f as teardownOwnedIosSim, h as POOL_SETTING_REMEDY, i as stopTunnel, l as withManagedTunnelRemovalLock, s as withManagedRemoteWorktreeRemovalLock, t as resolveSupervisorTarget, y as parkedMaxSetting } from "./ownership-BiojdtAp.mjs";
11
- import { n as withIdleWorkspace, t as emptyWorkspaceDir } from "./in-use-DVhlSoyn.mjs";
12
- import { n as verifyCollectorOwnership } from "./ownership-D2xbLVoz.mjs";
13
- import { i as endRecordedSession, z as resolveEasCliBin } from "./device-remote-BQZSnnmE.mjs";
14
- import { h as podInstallCommand, n as HEARTBEAT_INTERVAL_MS } from "./xcode-B5SPD--o.mjs";
15
- import { n as hasInstalledDependencies, r as installedNpmTreeIsValid, t as dependencyState } from "./dependency-state-r9UesA9a.mjs";
16
- import { t as clearNamedPorts } from "./named-ports-Dc7neAlc.mjs";
17
- import { a as runPodInstall, i as runCaptured, n as podsAreStale, r as readPodState, t as DEPS_ERROR } from "./deps-OF7CD5Dx.mjs";
3
+ import { a as pidExists, c as resolveProjectMetro, g as recordWorkspaceUse, h as readWorkspaceState, k as workspaceDir, l as signalProcessTree, n as killMetroTree } from "./metro-BPeY1YMh.mjs";
4
+ import { C as sourceCheckoutOf, S as restoreFile, T as warmWorktreePaths, _ as readWorktreeExclude, b as resolveFullRef, d as hasUncommittedWork, f as isPodInstallChurn, g as podsOutOfSync, h as locallyKnownUpstream, i as depsOutOfSync, l as hasPopulatedSubmodules, m as listWorktrees, n as cloneIgnoredEntries, o as dirtyPaths, r as deleteBranch, s as gitCommonDir, t as branchExists, u as hasRemote, v as removeWorktree, w as unpushedCommits } from "./worktree-DeBQSVtR.mjs";
5
+ import { a as findProjectRoot } from "./project-DGeclhvO.mjs";
6
+ import { a as clearRemoteSession, c as readMetroTunnel, d as readRemoteSessionId, i as clearManagedMetroTunnel, u as readRemoteSession } from "./state-DwAU2_-D.mjs";
7
+ import { t as clearNamedPorts } from "./named-ports-n7tnJsI8.mjs";
8
+ import { a as formatElapsed, h as shortUdid, i as formatDuration, l as phaseLine, p as releasedLeaseFact, u as plural } from "./command-output-BIL7rlX7.mjs";
9
+ import { u as releaseWorkspaceLeases } from "./device-lease-CL9dZf73.mjs";
10
+ import { ct as parkedMaxSetting, rt as POOL_SETTING_REMEDY } from "./android-qx_31wJo.mjs";
11
+ import { L as unknownSettingKeys, P as settingShapeErrors, a as SETTING_SHAPE_REMEDY, j as resolveSettings } from "./settings-xB0Frkg6.mjs";
12
+ import { f as teardownOwnedAvd, i as stopTunnel, l as withManagedTunnelRemovalLock, m as teardownOwnedIosSim, p as teardownOwnedBrowser, s as withManagedRemoteWorktreeRemovalLock, t as resolveSupervisorTarget } from "./ownership-PUP4Dwai.mjs";
13
+ import { a as readWebRecord } from "./state-l1pYwOre.mjs";
14
+ import { n as withIdleWorkspace, t as emptyWorkspaceDir } from "./in-use-wYEaKQM9.mjs";
15
+ import { n as verifyCollectorOwnership } from "./ownership-Bz2kETAS.mjs";
16
+ import { f as resolveEasCliBin } from "./remote-cache--aehr4Pr.mjs";
17
+ import { i as endRecordedSession } from "./device-remote-DT9ngZiD.mjs";
18
+ import { h as podInstallCommand, n as HEARTBEAT_INTERVAL_MS } from "./xcode-LM6A8cNZ.mjs";
19
+ import { n as hasInstalledDependencies, r as installedNpmTreeIsValid, t as dependencyState } from "./dependency-state-DNN0yfx6.mjs";
20
+ import { a as runPodInstall, i as runCaptured, n as podsAreStale, r as readPodState, t as DEPS_ERROR } from "./deps-2Wo81np9.mjs";
18
21
  import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
19
22
  import { existsSync as existsSync$1, realpathSync as realpathSync$1 } from "fs";
20
23
  import { basename, dirname, isAbsolute, relative, resolve } from "path";
@@ -295,7 +298,7 @@ async function reclaimProject(path, options = {}) {
295
298
  failedWorkspaceDirs: []
296
299
  };
297
300
  }
298
- async function reclaimIdleProject(path, workspaceExisted, { deleteOwnedDevices = false, parkOwnedDevices = false, preserveProjectRecord = false, stopSession = defaultStopSession, stopMetroTunnel = defaultStopMetroTunnel, releaseLeases = releaseWorkspaceLeases, verifyCollector = verifyCollectorOwnership }) {
301
+ async function reclaimIdleProject(path, workspaceExisted, { deleteOwnedDevices = false, parkOwnedDevices = false, preserveProjectRecord = false, stopSession = defaultStopSession, stopMetroTunnel = defaultStopMetroTunnel, releaseLeases = releaseWorkspaceLeases, verifyCollector = verifyCollectorOwnership, teardownBrowser = teardownOwnedBrowser }) {
299
302
  await clearNamedPorts(path, { stop: true });
300
303
  const project = getProject(path);
301
304
  for (const { platforms } of projectDeviceSlots(project)) if (platforms.android?.owned && platforms.android.avdName) (0, ownership_claim_exports.releaseClaim)(acquireAvdClaim(platforms.android.avdName));
@@ -343,10 +346,19 @@ async function reclaimIdleProject(path, workspaceExisted, { deleteOwnedDevices =
343
346
  verify: verifyCollector,
344
347
  collectors: initialState?.collectors ?? {}
345
348
  });
349
+ const browser = await teardownBrowser(path, { deleteProfile: true });
350
+ if (browser.status === "failed" || browser.status === "skipped") {
351
+ const entry = {
352
+ name: "owned Chrome",
353
+ reason: browser.reason ?? "it could not be closed"
354
+ };
355
+ skippedCollectors.push(entry);
356
+ failedCollectors.push(entry);
357
+ }
346
358
  function retainReplacementProcesses() {
347
359
  const currentState = readWorkspaceState(path);
348
360
  const currentProject = getProject(path);
349
- if (Boolean(currentState?.supervisor && !(0, process_identity_exports.sameProcessRecord)(currentState.supervisor, initialState?.supervisor) || currentProject?.supervisor && !(0, process_identity_exports.sameProcessRecord)(currentProject.supervisor, project?.supervisor) || Object.entries(currentState?.collectors ?? {}).some(([platform, record]) => !(0, process_identity_exports.sameProcessRecord)(record, initialState?.collectors?.[platform])))) {
361
+ if (Boolean(currentState?.supervisor && !(0, process_identity_exports.sameProcessRecord)(currentState.supervisor, initialState?.supervisor) || currentProject?.supervisor && !(0, process_identity_exports.sameProcessRecord)(currentProject.supervisor, project?.supervisor) || readWebRecord(path) !== null || Object.entries(currentState?.collectors ?? {}).some(([platform, record]) => !(0, process_identity_exports.sameProcessRecord)(record, initialState?.collectors?.[platform])))) {
350
362
  supervisorHeld = true;
351
363
  skippedMetro = "A replacement process appeared during cleanup; its ownership records are retained";
352
364
  }
@@ -413,6 +425,192 @@ async function reclaimIdleProject(path, workspaceExisted, { deleteOwnedDevices =
413
425
  };
414
426
  }
415
427
  //#endregion
428
+ //#region src/workspace/pull-request.ts
429
+ const GH_TIMEOUT_MS = 2e4;
430
+ const GH_ENV = {
431
+ GH_PROMPT_DISABLED: "1",
432
+ GH_NO_UPDATE_NOTIFIER: "1",
433
+ NO_COLOR: "1"
434
+ };
435
+ const GH_FIELDS = "number,state,url,headRefOid,mergedAt,closedAt,isCrossRepository";
436
+ const SIGNED_OUT_EXIT = 4;
437
+ function stateOf(state) {
438
+ if (state === "OPEN") return "open";
439
+ if (state === "MERGED") return "merged";
440
+ if (state === "CLOSED") return "closed";
441
+ return null;
442
+ }
443
+ function epoch(text) {
444
+ const at = text ? Date.parse(text) : NaN;
445
+ return Number.isFinite(at) ? at : null;
446
+ }
447
+ /**
448
+ * Picks the pull request that describes `head` among those `gh pr list --head <branch>` returned. One whose head is
449
+ * `head` wins, then one whose head contains `head`, then one whose head `head` contains. An open one wins a tier,
450
+ * then the newest. A pull request unrelated to `head` is from an earlier use of the branch name, and one from a fork
451
+ * only shares the branch name; both are ignored.
452
+ * `isAncestor(a, b)` answers whether commit `a` is an ancestor of commit `b`, and false when git cannot tell.
453
+ */
454
+ function selectPullRequest(pulls, head, isAncestor) {
455
+ const known = pulls.flatMap((pull) => {
456
+ const state = stateOf(pull.state);
457
+ return state && pull.headRefOid && !pull.isCrossRepository ? [{
458
+ pull,
459
+ state
460
+ }] : [];
461
+ });
462
+ const tiers = [
463
+ known.filter(({ pull }) => pull.headRefOid === head),
464
+ known.filter(({ pull }) => pull.headRefOid !== head && isAncestor(head, pull.headRefOid)),
465
+ known.filter(({ pull }) => pull.headRefOid !== head && isAncestor(pull.headRefOid, head))
466
+ ];
467
+ const tier = tiers.findIndex((entries) => entries.length > 0);
468
+ if (tier < 0) return null;
469
+ const { pull, state } = tiers[tier].toSorted((a, b) => Number(b.state === "open") - Number(a.state === "open") || b.pull.number - a.pull.number)[0];
470
+ return {
471
+ number: pull.number,
472
+ state,
473
+ url: pull.url,
474
+ head: pull.headRefOid,
475
+ containsHead: tier < 2,
476
+ endedAt: state === "merged" ? epoch(pull.mergedAt) : state === "closed" ? epoch(pull.closedAt) : null
477
+ };
478
+ }
479
+ function firstLine(error) {
480
+ const { stderr, message } = error;
481
+ const text = String(stderr ?? "").trim() || String(message ?? error);
482
+ return text.split("\n")[0] ?? text;
483
+ }
484
+ function ghFailure(error, command) {
485
+ if (error.status === SIGNED_OUT_EXIT) return {
486
+ unavailable: "gh is not signed in; run `gh auth login`",
487
+ sticky: true
488
+ };
489
+ if (error.code === "ETIMEDOUT") return {
490
+ unavailable: `${command} did not answer within ${GH_TIMEOUT_MS / 1e3}s`,
491
+ sticky: true
492
+ };
493
+ return {
494
+ unavailable: `${command} failed: ${firstLine(error)}`,
495
+ sticky: false
496
+ };
497
+ }
498
+ function ancestry(cwd) {
499
+ const exec = getExecutor();
500
+ return (ancestor, descendant) => exec.runFileQuiet("git", [
501
+ "-C",
502
+ cwd,
503
+ "merge-base",
504
+ "--is-ancestor",
505
+ ancestor,
506
+ descendant
507
+ ]) !== null;
508
+ }
509
+ /**
510
+ * Asks GitHub, through `gh`, which pull requests have `branch` as their head, run from `cwd` so `gh` picks the
511
+ * repository from its remotes. Each call is one fixed `gh pr list` invocation. Once `gh` is missing, signed out or
512
+ * times out, every later lookup through the same function answers `unavailable` without running it again.
513
+ */
514
+ function pullRequestLookup() {
515
+ let unavailable;
516
+ return (cwd, branch, head) => {
517
+ const exec = getExecutor();
518
+ if (unavailable === void 0) unavailable = exec.findExecutable("gh") ? null : "gh is not installed";
519
+ if (unavailable) return { unavailable };
520
+ let out;
521
+ try {
522
+ out = exec.runFile("gh", [
523
+ "pr",
524
+ "list",
525
+ "--head",
526
+ branch,
527
+ "--state",
528
+ "all",
529
+ "--limit",
530
+ "20",
531
+ "--json",
532
+ GH_FIELDS
533
+ ], {
534
+ cwd,
535
+ timeoutMs: GH_TIMEOUT_MS,
536
+ env: GH_ENV
537
+ });
538
+ } catch (error) {
539
+ const failure = ghFailure(error, "gh pr list");
540
+ if (failure.sticky) unavailable = failure.unavailable;
541
+ return { unavailable: failure.unavailable };
542
+ }
543
+ let pulls;
544
+ try {
545
+ pulls = JSON.parse(out);
546
+ } catch {
547
+ return { unavailable: "gh pr list printed no JSON" };
548
+ }
549
+ return { pullRequest: Array.isArray(pulls) ? selectPullRequest(pulls, head, ancestry(cwd)) : null };
550
+ };
551
+ }
552
+ function branchesQuery(count) {
553
+ return `query($owner: String!, $repo: String!${Array.from({ length: count }, (_, i) => `, $b${i}: String!`).join("")}) { repository(owner: $owner, name: $repo) {${Array.from({ length: count }, (_, i) => ` b${i}: pullRequests(headRefName: $b${i}, states: [OPEN, CLOSED, MERGED], first: 20, orderBy: {field: CREATED_AT, direction: DESC}) { nodes { ${GH_FIELDS.replaceAll(",", " ")} } }`).join("")} } }`;
554
+ }
555
+ /**
556
+ * {@link pullRequestLookup} for every branch of one repository in one fixed `gh api graphql` call run from `repo`,
557
+ * which asks for the same 20 newest pull requests of each head branch that `gh pr list --head` returns. `gh` fills
558
+ * `{owner}` and `{repo}` from the remotes of `repo`; branch names travel as variables. Once `gh` is missing, signed
559
+ * out or times out, every later call through the same function answers `unavailable` without running it again.
560
+ */
561
+ function pullRequestLookups() {
562
+ let unavailable;
563
+ return async (repo, queries) => {
564
+ const exec = getExecutor();
565
+ if (unavailable === void 0) unavailable = exec.findExecutable("gh") ? null : "gh is not installed";
566
+ const all = (lookup) => queries.map(() => lookup);
567
+ if (unavailable) return all({ unavailable });
568
+ if (!queries.length) return [];
569
+ const args = [
570
+ "api",
571
+ "graphql",
572
+ "-F",
573
+ "owner={owner}",
574
+ "-F",
575
+ "repo={repo}"
576
+ ];
577
+ queries.forEach(({ branch }, i) => args.push("-f", `b${i}=${branch}`));
578
+ args.push("-f", `query=${branchesQuery(queries.length)}`);
579
+ let out;
580
+ try {
581
+ out = await exec.runFileAsync("gh", args, {
582
+ cwd: repo,
583
+ timeoutMs: GH_TIMEOUT_MS,
584
+ env: GH_ENV
585
+ });
586
+ } catch (error) {
587
+ const failure = ghFailure(error, "gh api graphql");
588
+ if (failure.sticky) unavailable = failure.unavailable;
589
+ return all({ unavailable: failure.unavailable });
590
+ }
591
+ let found;
592
+ try {
593
+ found = JSON.parse(out).data?.repository;
594
+ } catch {
595
+ return all({ unavailable: "gh api graphql printed no JSON" });
596
+ }
597
+ if (!found) return all({ unavailable: "gh api graphql printed no repository" });
598
+ return queries.map(({ cwd, head }, i) => {
599
+ const pulls = found[`b${i}`]?.nodes;
600
+ return { pullRequest: Array.isArray(pulls) ? selectPullRequest(pulls, head, ancestry(cwd)) : null };
601
+ });
602
+ };
603
+ }
604
+ /** The merged or closed pull request whose commits include HEAD, which makes the worktree removable. */
605
+ function endedPullRequest(lookup) {
606
+ const pr = lookup && "pullRequest" in lookup ? lookup.pullRequest : null;
607
+ return pr && pr.state !== "open" && pr.containsHead ? pr : null;
608
+ }
609
+ /** "PR #123 merged", for reports. */
610
+ function describePullRequest(pr) {
611
+ return `PR #${pr.number} ${pr.state}`;
612
+ }
613
+ //#endregion
416
614
  //#region src/engine/warm-claim.ts
417
615
  const WARM_CLAIM_LABEL = "worktree warm";
418
616
  const WARM_CLAIM_POLL_MS = 250;
@@ -1707,7 +1905,17 @@ async function runRemove(target, opts, onRemoved) {
1707
1905
  const branch = entry.branch;
1708
1906
  const ownsBranch = Boolean(branch && project?.worktreeBranchOwned === true && project.worktreeBranch === branch);
1709
1907
  const approvedBranchSha = ownsBranch ? resolveFullRef(path, "HEAD") : null;
1710
- const inspection = inspectRemoval(path, opts.mergedHead);
1908
+ let mergedHead = opts.mergedHead;
1909
+ let inspection = inspectRemoval(path, mergedHead);
1910
+ if (inspection.blockers.length && !opts.force && !mergedHead && branch && inspection.unpushed?.length && !inspection.dirtyLines.length) {
1911
+ const head = resolveFullRef(path, "HEAD");
1912
+ const pr = head ? endedPullRequest(pullRequestLookup()(path, branch, head)) : null;
1913
+ if (head && pr?.state === "merged") {
1914
+ mergedHead = head;
1915
+ inspection = inspectRemoval(path, mergedHead);
1916
+ console.error(chalk.dim(`HEAD is in the merged PR #${pr.number} (${pr.url}); its local-only commits stay on GitHub.`));
1917
+ }
1918
+ }
1711
1919
  if (inspection.blockers.length && !opts.force) {
1712
1920
  printRemovalRefusal(path, inspection);
1713
1921
  return;
@@ -1718,7 +1926,7 @@ async function runRemove(target, opts, onRemoved) {
1718
1926
  await withManagedRemoteWorktreeRemovalLock(path, () => withReclaimLocks(path, async (lockedKeys) => {
1719
1927
  if (opts.guard?.(lockedKeys).length) return;
1720
1928
  const inspectedHead = resolveFullRef(path, "HEAD");
1721
- const current = inspectRemoval(path, opts.mergedHead);
1929
+ const current = inspectRemoval(path, mergedHead);
1722
1930
  if (current.blockers.length && !opts.force) {
1723
1931
  printRemovalRefusal(path, current);
1724
1932
  return;
@@ -1800,4 +2008,4 @@ function worktreeCommand(program) {
1800
2008
  registerRemove(worktree);
1801
2009
  }
1802
2010
  //#endregion
1803
- export { reclaimKeys as a, removalBlockers as c, removeWorktreeTarget as d, worktreeCommand as f, porcelainPath as i, removalPath as l, reclaimProject as m, excludePodChurn as n, registerRemove as o, describeDereferenced as p, matchWorktreeEntry as r, registerWarm as s, dependencyInstallCommand as t, removalRemedy as u };
2011
+ export { reclaimProject as _, reclaimKeys as a, removalBlockers as c, removeWorktreeTarget as d, worktreeCommand as f, describeDereferenced as g, pullRequestLookups as h, porcelainPath as i, removalPath as l, endedPullRequest as m, excludePodChurn as n, registerRemove as o, describePullRequest as p, matchWorktreeEntry as r, registerWarm as s, dependencyInstallCommand as t, removalRemedy as u };
@@ -1,4 +1,4 @@
1
- import { g as loadConfig, u as getConfigDir } from "./config-BEq0Qw_a.mjs";
1
+ import { _ as loadConfig, d as getConfigDir } from "./config-0jCcMM0E.mjs";
2
2
  import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
3
  import { accessSync, chmodSync, constants, lstatSync, mkdirSync, mkdtempSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
4
4
  import { chmodSync as chmodSync$1, existsSync as existsSync$1, lstatSync as lstatSync$1, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, realpathSync as realpathSync$1, statSync as statSync$1, utimesSync } from "fs";
@@ -481,6 +481,7 @@ function dirtyFingerprintFiles(root) {
481
481
  }
482
482
  function hasUncommittedWork(dir) {
483
483
  const out = getExecutor().runFileQuiet("git", [
484
+ "--no-optional-locks",
484
485
  "-C",
485
486
  dir,
486
487
  "status",
@@ -491,6 +492,7 @@ function hasUncommittedWork(dir) {
491
492
  }
492
493
  function dirtyPaths(dir, { limit = 10 } = {}) {
493
494
  const out = getExecutor().runFileQuiet("git", [
495
+ "--no-optional-locks",
494
496
  "-C",
495
497
  dir,
496
498
  "status",
@@ -1,12 +1,12 @@
1
1
  import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
2
- import { r as register } from "./cache-manifest-Pubfp8wV.mjs";
3
- import { D as workspaceDerivedData, w as sharedCompilationCache } from "./metro-BWLOJ_Wr.mjs";
4
- import { u as resolvePackageJson } from "./project-BXZzOABt.mjs";
5
- import { a as spawnDeclared } from "./spawn-claims-BJwXTMPt.mjs";
2
+ import { r as register } from "./cache-manifest-DWPF4RKJ.mjs";
3
+ import { O as workspaceDerivedData, T as sharedCompilationCache } from "./metro-BPeY1YMh.mjs";
4
+ import { f as resolvePackageJson } from "./project-DGeclhvO.mjs";
5
+ import { a as spawnDeclared } from "./spawn-claims-PT8aQpCa.mjs";
6
6
  import { t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
7
- import { a as formatElapsed, l as phaseLine } from "./command-output-D5z2_bcF.mjs";
8
- import { W as resolveOptimizations } from "./settings-B5oFZEri.mjs";
9
- import { t as cleanLine } from "./server-expo-o6H5gcBc.mjs";
7
+ import { a as formatElapsed, l as phaseLine } from "./command-output-BIL7rlX7.mjs";
8
+ import { G as resolveOptimizations } from "./settings-xB0Frkg6.mjs";
9
+ import { t as cleanLine } from "./server-expo-DUCg52-K.mjs";
10
10
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
11
11
  import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
12
12
  import { createHash } from "node:crypto";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stim",
3
- "version": "1.11.0",
3
+ "version": "1.13.0",
4
4
  "description": "Stim: fast, isolated React Native environments for coding agents",
5
5
  "keywords": [
6
6
  "agent",
@@ -43,9 +43,9 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "@expo/fingerprint": "^0.20.10",
46
- "@stim-cli/cache": "^1.11.0",
47
- "@stim-cli/core": "^1.11.0",
48
- "@stim-cli/metro": "^1.11.0",
46
+ "@stim-cli/cache": "^1.13.0",
47
+ "@stim-cli/core": "^1.13.0",
48
+ "@stim-cli/metro": "^1.13.0",
49
49
  "chalk": "^6.0.0",
50
50
  "commander": "^15.0.0",
51
51
  "cross-spawn": "^7.0.6",
@@ -53,7 +53,7 @@
53
53
  "ws": "^8.21.3"
54
54
  },
55
55
  "devDependencies": {
56
- "@stim-cli/expo-build-cache": "1.11.0",
56
+ "@stim-cli/expo-build-cache": "1.13.0",
57
57
  "@types/cross-spawn": "^6.0.6",
58
58
  "@types/which": "^3.0.4",
59
59
  "@types/ws": "^8.18.1",