stim 1.3.0 → 1.4.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 (65) hide show
  1. package/README.md +17 -3
  2. package/dist/{android-CqcW1hrP.mjs → android-BTR1KTMP.mjs} +206 -120
  3. package/dist/{android-BqVIJoG7.mjs → android-BkM4qTaN.mjs} +76 -77
  4. package/dist/{android-cas-BR5YJBYv.mjs → android-cas-BjLRZyks.mjs} +15 -12
  5. package/dist/{app-install-DIp9riNx.mjs → app-install--PAocWvq.mjs} +2 -2
  6. package/dist/{build-slots-C3-R4XQr.mjs → build-slots-D6sDzOe-.mjs} +7 -8
  7. package/dist/{cache-manifest-CXR-Y8pH.mjs → cache-manifest-D02TpYjA.mjs} +1 -1
  8. package/dist/cache-manifest.mjs +1 -1
  9. package/dist/cli.mjs +14 -13
  10. package/dist/collector-run.mjs +4 -4
  11. package/dist/{command-output-8wHMEde0.mjs → command-output-BuIb_ZOI.mjs} +1 -4
  12. package/dist/{config-CcefMY2Q.mjs → config-6vdGngKa.mjs} +51 -51
  13. package/dist/dependency-state-BMirHEPD.mjs +76 -0
  14. package/dist/{deps-Spz8ozgo.mjs → deps-Ck4wuTwa.mjs} +1 -1
  15. package/dist/{dev-client-BjisHDB5.mjs → dev-client-Cd0v7iZk.mjs} +12 -12
  16. package/dist/{device-BkiW-gkk.mjs → device-CKXLnRRb.mjs} +7 -7
  17. package/dist/device-CMDFzx7C.mjs +1136 -0
  18. package/dist/{device-lease-BJYybF-2.mjs → device-lease-DDJr85OK.mjs} +3 -3
  19. package/dist/{device-pool-CPbLGfrB.mjs → device-pool-DJvbIBGW.mjs} +4 -4
  20. package/dist/{device-remote-AkYCKS8U.mjs → device-remote-CMfHhoc8.mjs} +7 -6
  21. package/dist/{doctor-Bmv86UUW.mjs → doctor-B_mfURdt.mjs} +536 -114
  22. package/dist/{error-diagnostics-DHC31rXA.mjs → error-diagnostics-_VmkqRZo.mjs} +8 -7
  23. package/dist/{gc-GGu-8ULN.mjs → gc-0AyxmECv.mjs} +61 -27
  24. package/dist/{guide-92CPA21s.mjs → guide-CGxdmQ0K.mjs} +236 -67
  25. package/dist/{ios-BSXFeO5H.mjs → ios-CtmGLx3X.mjs} +2667 -2607
  26. package/dist/{ios-device-CWOM_S44.mjs → ios-device-DJOi9FR5.mjs} +1 -1
  27. package/dist/{logs-DPNF4C9s.mjs → logs-BF3qPA7w.mjs} +32 -7
  28. package/dist/{logs-query-R-Cqc6Qv.mjs → logs-query-Cdp5aaCH.mjs} +1 -1
  29. package/dist/metro-DFLmaVz8.mjs +99 -0
  30. package/dist/named-ports-BO7Zpjp0.mjs +137 -0
  31. package/dist/{ownership-8O073GIC.mjs → ownership-BvSpZVBk.mjs} +5 -5
  32. package/dist/{ownership-BpCKiRlQ.mjs → ownership-C08Yzrp7.mjs} +6 -6
  33. package/dist/ports-BiKqP92J.mjs +39 -0
  34. package/dist/ports-ClqG8GPt.mjs +90 -0
  35. package/dist/{project-fKmuIaMI.mjs → project-CBFCwnN5.mjs} +2 -2
  36. package/dist/{reclaim-iW9Cad7k.mjs → reclaim-DpDVo-AP.mjs} +20 -13
  37. package/dist/{reload-DazHDSFA.mjs → reload-ByTqg6ZI.mjs} +8 -8
  38. package/dist/rolldown-runtime-BsOwBuO_.mjs +30 -0
  39. package/dist/{server-bare-DEzPZLvf.mjs → server-bare-Buk-k8tI.mjs} +3 -6
  40. package/dist/{server-expo-DizT4OMk.mjs → server-expo-D4P7iTvf.mjs} +3 -3
  41. package/dist/server-expo-Dho5Raiw.mjs +2 -0
  42. package/dist/{settings-DEJf6_kJ.mjs → settings-DwfltIXQ.mjs} +2 -2
  43. package/dist/{slot-launch-cci0xvmC.mjs → slot-launch-DeKen9e5.mjs} +4 -4
  44. package/dist/{start-mObjQH2W.mjs → start-BpKUHcU_.mjs} +16 -14
  45. package/dist/state-CDbvmZdt.mjs +131 -0
  46. package/dist/{stats-BR-kwj0B.mjs → stats-C59QLKm2.mjs} +1 -1
  47. package/dist/{stats-CaaQr4yN.mjs → stats-k5qMjyxo.mjs} +4 -4
  48. package/dist/{status-BxQNqu9t.mjs → status-Y2ij3dm6.mjs} +13 -12
  49. package/dist/{stop-DimGXEKY.mjs → stop-_P2OQwmZ.mjs} +15 -14
  50. package/dist/{stop-CrHGcjrV.mjs → stop-vKmmxhea.mjs} +2 -2
  51. package/dist/supervisor-run.d.mts +3 -27
  52. package/dist/supervisor-run.mjs +7 -6
  53. package/dist/{teardown-ClFp1Aax.mjs → teardown-KsPpvldq.mjs} +248 -110
  54. package/dist/{workspace-process-lock-3NcQnZ0X.mjs → workspace-process-lock-UEIrvZMJ.mjs} +4 -4
  55. package/dist/workspace-state-Do1e2mOH.mjs +146 -0
  56. package/dist/{worktree-DxTgN1CJ.mjs → worktree-BvcAIMdJ.mjs} +132 -123
  57. package/dist/{worktree-Eq6TLH8N.mjs → worktree-DWNx4tUg.mjs} +32 -2
  58. package/dist/xcode-D9tVItl-.mjs +860 -0
  59. package/package.json +7 -7
  60. package/dist/doctor-fAMXC4vl.mjs +0 -530
  61. package/dist/metro-CORqXyTL.mjs +0 -184
  62. package/dist/ownership-claim-Bf2CAfw3.mjs +0 -570
  63. package/dist/server-expo-44em5Pmg.mjs +0 -2
  64. package/dist/state-B3XA2wp2.mjs +0 -268
  65. package/dist/xcode-D7sAp-Zy.mjs +0 -1858
@@ -1,17 +1,16 @@
1
1
  import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { S as upsertProject, f as getProject, g as loadConfig, h as isPathPrefix, u as getConfigDir, v as removeProject, w as captureProcessIdentity } from "./config-CcefMY2Q.mjs";
3
- import { c as readMetroTunnel, u as readRemoteSession } from "./state-B3XA2wp2.mjs";
4
- import { _ as resolveFullRef, b as warmWorktreePaths, c as hasRemote, d as isPodInstallChurn, f as listWorktrees, h as removeWorktree, i as depsOutOfSync, l as hasUncommittedWork, m as readWorktreeExclude, n as cloneIgnoredEntries, o as dirtyPaths, p as podsOutOfSync, r as deleteBranch, s as gitCommonDir, t as branchExists, u as isMainWorkingTree, v as restoreFile, y as unpushedCommits } from "./worktree-Eq6TLH8N.mjs";
5
- import { a as findProjectRoot } from "./project-fKmuIaMI.mjs";
6
- import { a as formatElapsed, f as releasedLeaseFact, i as formatDuration, l as phaseLine, m as shortUdid, u as plural } from "./command-output-8wHMEde0.mjs";
7
- import { a as clearClaimChild, c as isClaimUnavailable, d as readClaimSet, f as releaseClaim, h as tryAcquireClaim, i as claimRemoveCommand, l as markClaimChildPending, m as settleClaim, p as setClaimChild, r as claimFailure, s as isClaimRefusal, u as processGroupAlive } from "./ownership-claim-Bf2CAfw3.mjs";
8
- import { A as resolveSettings, I as unknownSettingKeys, N as settingShapeErrors, a as SETTING_SHAPE_REMEDY } from "./settings-DEJf6_kJ.mjs";
9
- import { a as POOL_SETTING_REMEDY, c as parkedMaxSetting } from "./teardown-ClFp1Aax.mjs";
10
- import { g as podInstallCommand, r as HEARTBEAT_INTERVAL_MS } from "./xcode-D7sAp-Zy.mjs";
11
- import { a as installedNpmTreeIsValid, i as hasInstalledDependencies, o as locallyKnownUpstream, t as dependencyState } from "./doctor-Bmv86UUW.mjs";
12
- import { c as withManagedTunnelRemovalLock, o as withManagedRemoteWorktreeRemovalLock } from "./ownership-BpCKiRlQ.mjs";
13
- import { n as reclaimProject } from "./reclaim-iW9Cad7k.mjs";
14
- import { a as runPodInstall, i as runCaptured, n as podsAreStale, r as readPodState, t as DEPS_ERROR } from "./deps-Spz8ozgo.mjs";
2
+ import { E as ownership_claim_exports, S as upsertProject, f as getProject, g as loadConfig, h as isPathPrefix, u as getConfigDir, v as removeProject, w as process_identity_exports } from "./config-6vdGngKa.mjs";
3
+ import { b as unpushedCommits, c as hasRemote, d as isPodInstallChurn, f as listWorktrees, g as removeWorktree, h as readWorktreeExclude, i as depsOutOfSync, l as hasUncommittedWork, m as podsOutOfSync, n as cloneIgnoredEntries, o as dirtyPaths, p as locallyKnownUpstream, r as deleteBranch, s as gitCommonDir, t as branchExists, u as isMainWorkingTree, v as resolveFullRef, x as warmWorktreePaths, y as restoreFile } from "./worktree-DWNx4tUg.mjs";
4
+ import { a as findProjectRoot } from "./project-CBFCwnN5.mjs";
5
+ import { l as readRemoteSession, s as readMetroTunnel } from "./state-CDbvmZdt.mjs";
6
+ import { a as formatElapsed, f as releasedLeaseFact, i as formatDuration, l as phaseLine, m as shortUdid, u as plural } from "./command-output-BuIb_ZOI.mjs";
7
+ import { A as resolveSettings, I as unknownSettingKeys, N as settingShapeErrors, a as SETTING_SHAPE_REMEDY } from "./settings-DwfltIXQ.mjs";
8
+ import { a as POOL_SETTING_REMEDY, l as parkedMaxSetting } from "./teardown-KsPpvldq.mjs";
9
+ import { h as podInstallCommand, n as HEARTBEAT_INTERVAL_MS } from "./xcode-D9tVItl-.mjs";
10
+ import { n as hasInstalledDependencies, r as installedNpmTreeIsValid, t as dependencyState } from "./dependency-state-BMirHEPD.mjs";
11
+ import { c as withManagedTunnelRemovalLock, o as withManagedRemoteWorktreeRemovalLock } from "./ownership-C08Yzrp7.mjs";
12
+ import { n as reclaimProject } from "./reclaim-DpDVo-AP.mjs";
13
+ import { a as runPodInstall, i as runCaptured, n as podsAreStale, r as readPodState, t as DEPS_ERROR } from "./deps-Ck4wuTwa.mjs";
15
14
  import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
16
15
  import { createHash, randomUUID } from "node:crypto";
17
16
  import { join, relative } from "node:path";
@@ -56,57 +55,10 @@ function asHolder(holder) {
56
55
  function waitingLine(holder, elapsedMs) {
57
56
  return phaseLine("lock", `waiting ${formatElapsed(elapsedMs)} for ${warmCommand(holder.phase)} (pid ${holder.pid}) -- stim guide lifecycle options`);
58
57
  }
59
- function warmClaimAcquiredLine(wait) {
60
- if (!wait.holder || wait.waitedMs <= 0) return phaseLine("lock", "acquired");
61
- return phaseLine("lock", `acquired (waited ${formatElapsed(wait.waitedMs)} for ${warmCommand(wait.holder.phase)} pid ${wait.holder.pid}) -- stim guide lifecycle options`);
62
- }
63
- function warmClaimUnavailableLine(reason) {
64
- return phaseLine("lock", `unavailable (${reason}); copying without it`);
65
- }
66
- /**
67
- * The reason a copy may proceed without the claim, or null when it must refuse. Plain `warm` worked on an
68
- * unwritable STIM_HOME before any claim existed and only reads, so the states in which no claim can be
69
- * recorded at all degrade to an unsynchronised copy: no process identity, a claim store the filesystem
70
- * rejects, and a claim store whose own path holds a file. A claim record Stim cannot resolve and a wait
71
- * that ran out are refusals: neither state can exist unless the claim does, so refusing them takes
72
- * nothing away.
73
- */
74
- function warmClaimDegradation(error) {
75
- if (isClaimUnavailable(error)) return error.message;
76
- if (isClaimRefusal(error)) return error.reason === "the claim path is a file, not a claim directory" ? `${error.claimPath}: ${error.reason}` : null;
77
- const code = error?.code;
78
- if (typeof code === "string" && code.startsWith("STIM_")) return null;
79
- return error?.message ?? String(error);
80
- }
81
- /**
82
- * What a copy that could not record a claim of its own would overlap if it went ahead anyway. Reading the
83
- * claim set classifies without writing anything and without refusing, so it works in exactly the states
84
- * that made the claim unrecordable -- a read-only STIM_HOME included. It is a read before a copy rather
85
- * than a held claim, so a refresh that starts after it is not covered; it closes the window in which one
86
- * is already installing, which is the window a refresh actually occupies.
87
- */
88
- function warmClaimBlocker(repositoryRoot) {
89
- const survey = readClaimSet(warmClaimPath(repositoryRoot));
90
- const refresh = survey.live.find((holder) => holder.mode === "exclusive");
91
- if (refresh) return {
92
- kind: "refresh",
93
- holder: asHolder(refresh)
94
- };
95
- const unresolved = survey.unresolved.find((problem) => problem.path.endsWith(".claim"));
96
- if (unresolved) return {
97
- kind: "unresolved",
98
- path: unresolved.path,
99
- reason: unresolved.reason
100
- };
101
- return null;
102
- }
103
- function warmClaimBlockedLine(reason, blocker) {
104
- const what = blocker.kind === "refresh" ? `${warmCommand(blocker.holder.phase)} (pid ${blocker.holder.pid}) holds this repository` : `a claim at ${blocker.path} cannot be resolved: ${blocker.reason}`;
105
- return phaseLine("lock", `unavailable (${reason}); ${what}`);
106
- }
107
- function warmClaimBlockedRefusal(blocker) {
108
- if (blocker.kind === "refresh") return "Refusing to copy from a source checkout a refresh is rewriting, even without a claim of its own. Wait for that refresh to finish, then run warm again.";
109
- return `Refusing to copy past a claim Stim cannot resolve. If nothing is warming, remove it and run warm again:\n ${claimRemoveCommand(blocker.path)}`;
58
+ function warmClaimAcquiredLine(wait, detail = "") {
59
+ const acquired = `acquired${detail ? ` ${detail}` : ""}`;
60
+ if (!wait.holder || wait.waitedMs < 1e3) return phaseLine("lock", acquired);
61
+ return phaseLine("lock", `${acquired} (waited ${formatElapsed(wait.waitedMs)} for ${warmCommand(wait.holder.phase)} pid ${wait.holder.pid}) -- stim guide lifecycle options`);
110
62
  }
111
63
  function installerWaitingLine(pid, elapsedMs) {
112
64
  return phaseLine("lock", `waiting on the install this refresh spawned (pid ${pid}, ${formatElapsed(elapsedMs)} elapsed) -- stim guide lifecycle options`);
@@ -119,22 +71,22 @@ function installerClaim(claim, { now, progressMs, out }) {
119
71
  const forget = () => {
120
72
  groups = [];
121
73
  try {
122
- clearClaimChild(claim);
74
+ (0, ownership_claim_exports.clearClaimChild)(claim);
123
75
  } catch {}
124
76
  };
125
- const alive = () => groups.find((pid) => processGroupAlive(pid)) ?? null;
77
+ const alive = () => groups.find((pid) => (0, ownership_claim_exports.processGroupAlive)(pid)) ?? null;
126
78
  return {
127
- declare: () => markClaimChildPending(claim),
79
+ declare: () => (0, ownership_claim_exports.markClaimChildPending)(claim),
128
80
  record: (pid) => {
129
81
  if (pid === null) {
130
82
  forget();
131
83
  return;
132
84
  }
133
85
  groups.push(pid);
134
- const captured = captureProcessIdentity(pid);
86
+ const captured = (0, process_identity_exports.captureProcessIdentity)(pid);
135
87
  let failure = captured.ok ? null : captured.reason;
136
88
  if (captured.ok) try {
137
- setClaimChild(claim, {
89
+ (0, ownership_claim_exports.setClaimChild)(claim, {
138
90
  pid,
139
91
  processToken: captured.token
140
92
  });
@@ -142,7 +94,7 @@ function installerClaim(claim, { now, progressMs, out }) {
142
94
  } catch (error) {
143
95
  failure = error?.message ?? String(error);
144
96
  }
145
- if (!processGroupAlive(pid)) {
97
+ if (!(0, ownership_claim_exports.processGroupAlive)(pid)) {
146
98
  forget();
147
99
  return;
148
100
  }
@@ -179,11 +131,11 @@ function installerClaim(claim, { now, progressMs, out }) {
179
131
  }
180
132
  function timeoutError(holder, elapsedMs, root) {
181
133
  const who = holder ? `${warmCommand(holder.phase)} (pid ${holder.pid})` : "another stim worktree warm";
182
- const error = /* @__PURE__ */ new Error(`Waited ${formatElapsed(elapsedMs)} for ${who} to release the warm lock on this repository. The lock is ${root}; remove it if nothing is really warming:\n ${claimRemoveCommand(root)}`);
134
+ const error = /* @__PURE__ */ new Error(`Waited ${formatElapsed(elapsedMs)} for ${who} to release the warm lock on this repository. The lock is ${root}; remove it if nothing is really warming:\n ${(0, ownership_claim_exports.claimRemoveCommand)(root)}`);
183
135
  error.code = "STIM_LOCK_TIMEOUT";
184
136
  return error;
185
137
  }
186
- async function acquireWarmClaim({ repositoryRoot, phase, now = Date.now, sleep = defaultSleep, pollMs = WARM_CLAIM_POLL_MS, progressMs = WARM_CLAIM_PROGRESS_MS, ceilingMs = WARM_CLAIM_CEILING_MS, out = () => {} }) {
138
+ async function acquireWarmClaim({ repositoryRoot, phase, mode = phase === "refresh" ? "exclusive" : "shared", now = Date.now, sleep = defaultSleep, pollMs = WARM_CLAIM_POLL_MS, progressMs = WARM_CLAIM_PROGRESS_MS, ceilingMs = WARM_CLAIM_CEILING_MS, out = () => {} }) {
187
139
  const root = warmClaimPath(repositoryRoot);
188
140
  const started = now();
189
141
  let lastProgress = started;
@@ -191,9 +143,9 @@ async function acquireWarmClaim({ repositoryRoot, phase, now = Date.now, sleep =
191
143
  let pending = null;
192
144
  try {
193
145
  for (;;) {
194
- const attempt = pending ? settleClaim(pending) : tryAcquireClaim({
146
+ const attempt = pending ? (0, ownership_claim_exports.settleClaim)(pending) : (0, ownership_claim_exports.tryAcquireClaim)({
195
147
  root,
196
- mode: phase === "refresh" ? "exclusive" : "shared",
148
+ mode,
197
149
  label: WARM_CLAIM_LABEL
198
150
  });
199
151
  const acquired = attempt.acquired;
@@ -211,7 +163,7 @@ async function acquireWarmClaim({ repositoryRoot, phase, now = Date.now, sleep =
211
163
  installer,
212
164
  release: () => {
213
165
  if (installer.holding()) return;
214
- releaseClaim(acquired);
166
+ (0, ownership_claim_exports.releaseClaim)(acquired);
215
167
  }
216
168
  };
217
169
  }
@@ -227,7 +179,13 @@ async function acquireWarmClaim({ repositoryRoot, phase, now = Date.now, sleep =
227
179
  await sleep(pollMs);
228
180
  }
229
181
  } catch (error) {
230
- releaseClaim(pending);
182
+ (0, ownership_claim_exports.releaseClaim)(pending);
183
+ if ([
184
+ "EACCES",
185
+ "EPERM",
186
+ "EROFS",
187
+ "ENOENT"
188
+ ].includes(error?.code ?? "")) throw new ownership_claim_exports.ClaimUnavailableError(error.message, { claimPath: root });
231
189
  throw error;
232
190
  }
233
191
  }
@@ -521,6 +479,38 @@ function branchRemote(root, branch) {
521
479
  `branch.${branch}.remote`
522
480
  ])?.trim() || "origin";
523
481
  }
482
+ function remoteUpstreamIsCurrent(root, branch) {
483
+ const exec = getExecutor();
484
+ const merge = exec.runFileQuiet("git", [
485
+ "-C",
486
+ root,
487
+ "config",
488
+ "--get-all",
489
+ `branch.${branch}.merge`
490
+ ])?.trim();
491
+ if (!merge) return true;
492
+ if (!merge.startsWith("refs/") || merge.includes("\n")) return false;
493
+ const remote = branchRemote(root, branch);
494
+ if (remote === ".") return true;
495
+ const local = resolveFullRef(root, "@{upstream}");
496
+ if (!local) return false;
497
+ const advertised = exec.runFileQuiet("git", [
498
+ "-C",
499
+ root,
500
+ "ls-remote",
501
+ "--exit-code",
502
+ "--refs",
503
+ "--",
504
+ remote,
505
+ merge
506
+ ], {
507
+ timeoutMs: FETCH_TIMEOUT_MS,
508
+ env: { GIT_TERMINAL_PROMPT: "0" }
509
+ });
510
+ if (advertised === null) return false;
511
+ const refs = advertised.trim().split("\n").map((line) => line.split(/\s+/));
512
+ return refs.length === 1 && refs[0]?.[0] === local && refs[0]?.[1] === merge;
513
+ }
524
514
  function gitPath(...parts) {
525
515
  return parts.filter((part) => part && part !== ".").join("/");
526
516
  }
@@ -603,7 +593,7 @@ function fastForward(root, upstream) {
603
593
  };
604
594
  }
605
595
  }
606
- async function refreshMainCheckout({ root, appDir, settings, emit, spawnFn = null, now = Date.now, heartbeatMs = HEARTBEAT_INTERVAL_MS, installer = NO_INSTALLER_CLAIM }) {
596
+ async function refreshMainCheckout({ root, appDir, settings, emit, spawnFn = null, now = Date.now, heartbeatMs = HEARTBEAT_INTERVAL_MS, installer = NO_INSTALLER_CLAIM, inspectOnly = false }) {
607
597
  const state = readMainCheckoutState(root);
608
598
  const refusal = mainCheckoutRefusal(root, state);
609
599
  if (refusal) return refusal;
@@ -640,7 +630,9 @@ async function refreshMainCheckout({ root, appDir, settings, emit, spawnFn = nul
640
630
  };
641
631
  };
642
632
  const before = resolveFullRef(root, "HEAD") ?? "";
643
- if (getExecutor().runFileQuiet("git", [
633
+ if (inspectOnly) {
634
+ if (!remoteUpstreamIsCurrent(root, branch)) return { mutation: "fetch upstream" };
635
+ } else if (getExecutor().runFileQuiet("git", [
644
636
  "-C",
645
637
  root,
646
638
  "fetch",
@@ -652,6 +644,7 @@ async function refreshMainCheckout({ root, appDir, settings, emit, spawnFn = nul
652
644
  }) === null) emit(phaseLine("checkout", "could not fetch; continuing with the local state"));
653
645
  const plan = checkoutPlan(locallyKnownUpstream(root));
654
646
  if (plan.kind === "diverged") return divergedRefusal(root, branch, plan);
647
+ if (inspectOnly && plan.kind === "fast-forward") return { mutation: `fast-forward ${plan.behind} commit${plan.behind === 1 ? "" : "s"}` };
655
648
  const depsBefore = dependencyState(existsSync(appDir) ? appDir : root);
656
649
  const priorInstall = depsBefore ? readInstallEvidence(depsBefore.root) : null;
657
650
  const marked = plan.kind === "fast-forward" ? depsBefore : null;
@@ -683,6 +676,7 @@ async function refreshMainCheckout({ root, appDir, settings, emit, spawnFn = nul
683
676
  });
684
677
  const depsSubject = `source ${dependencies?.root ?? root}: ${deps.reason}`;
685
678
  if (deps.run && dependencies) {
679
+ if (inspectOnly) return { mutation: deps.reason };
686
680
  writeInstallEvidence(dependencies.root, dependencies.lock, consumed, false);
687
681
  const install = await runInstallCommand({
688
682
  command: dependencies.command,
@@ -719,6 +713,7 @@ async function refreshMainCheckout({ root, appDir, settings, emit, spawnFn = nul
719
713
  emit(stepLine("pods", `${where}${pods.reason}`, "skipped"));
720
714
  return null;
721
715
  }
716
+ if (inspectOnly) return { mutation: pods.reason };
722
717
  const result = await runPodInstall(appDir, null, {
723
718
  spawnFn: spawn,
724
719
  now,
@@ -793,31 +788,6 @@ function registerWarm(worktree) {
793
788
  for (const key of unknownSettingKeys(settings)) console.error(chalk.yellow(`Warning: setting "${key}" is not read by Stim and will be ignored.`));
794
789
  return settings;
795
790
  };
796
- if (opts.refresh) {
797
- const settings = readSettings();
798
- if (!settings) {
799
- process.exitCode = 1;
800
- return;
801
- }
802
- const failure = await withWarmClaim({
803
- repositoryRoot: root,
804
- phase: "refresh",
805
- out: console.error
806
- }, (hold) => {
807
- console.error(warmClaimAcquiredLine(hold.wait));
808
- return refreshMainCheckout({
809
- root,
810
- appDir: mainCheckoutAppDir(root, target),
811
- settings,
812
- emit: (line) => console.error(line),
813
- installer: hold.installer
814
- });
815
- });
816
- if (failure) {
817
- reportRefreshFailure(failure);
818
- return;
819
- }
820
- }
821
791
  const copy = (wait) => {
822
792
  if (wait?.holder) console.error(warmClaimAcquiredLine(wait));
823
793
  const incomplete = incompleteInstallRefusal(root, mainCheckoutAppDir(root, target));
@@ -846,34 +816,73 @@ function registerWarm(worktree) {
846
816
  console.error(phaseLine("carry", `${result.failed.length ? "incomplete" : "complete"}: ${result.copied.length} ignored entries copied, ${result.skipped.length} kept, ${result.failed.length} failed`));
847
817
  if (result.failed.length) process.exitCode = 1;
848
818
  };
849
- let hold = null;
850
- try {
851
- hold = await acquireWarmClaim({
819
+ if (opts.refresh) {
820
+ const inspect = await acquireWarmClaim({
852
821
  repositoryRoot: root,
853
- phase: "copy",
822
+ phase: "refresh",
823
+ mode: "shared",
854
824
  out: console.error
855
825
  });
856
- } catch (error) {
857
- const degraded = opts.refresh ? null : warmClaimDegradation(error);
858
- if (degraded === null) throw error;
859
- const blocker = warmClaimBlocker(root);
860
- if (blocker) {
861
- console.error(chalk.dim(warmClaimBlockedLine(degraded, blocker)));
862
- console.error(chalk.red(warmClaimBlockedRefusal(blocker)));
863
- throw error;
826
+ let mutation;
827
+ try {
828
+ const settings = readSettings();
829
+ if (!settings) {
830
+ process.exitCode = 1;
831
+ return;
832
+ }
833
+ const lines = [];
834
+ const result = await refreshMainCheckout({
835
+ root,
836
+ appDir: mainCheckoutAppDir(root, target),
837
+ settings,
838
+ emit: (line) => lines.push(line),
839
+ inspectOnly: true
840
+ });
841
+ if (!result || !("mutation" in result)) {
842
+ console.error(warmClaimAcquiredLine(inspect.wait, result ? "shared" : "shared (seed current)"));
843
+ for (const line of lines) console.error(line);
844
+ if (result) reportRefreshFailure(result);
845
+ else copy(null);
846
+ return;
847
+ }
848
+ mutation = result.mutation;
849
+ } finally {
850
+ inspect.release();
864
851
  }
865
- console.error(chalk.dim(warmClaimUnavailableLine(degraded)));
866
- }
867
- if (!hold) copy(null);
868
- else try {
869
- copy(hold.wait);
870
- } finally {
871
- hold.release();
852
+ const failure = await withWarmClaim({
853
+ repositoryRoot: root,
854
+ phase: "refresh",
855
+ out: console.error
856
+ }, async (hold) => {
857
+ console.error(warmClaimAcquiredLine(hold.wait, `exclusive (${mutation})`));
858
+ const settings = readSettings();
859
+ if (!settings) {
860
+ process.exitCode = 1;
861
+ return null;
862
+ }
863
+ return refreshMainCheckout({
864
+ root,
865
+ appDir: mainCheckoutAppDir(root, target),
866
+ settings,
867
+ emit: (line) => console.error(line),
868
+ installer: hold.installer
869
+ });
870
+ });
871
+ if (failure) {
872
+ reportRefreshFailure(failure);
873
+ return;
874
+ }
875
+ if (process.exitCode) return;
872
876
  }
877
+ await withWarmClaim({
878
+ repositoryRoot: root,
879
+ phase: "copy",
880
+ out: console.error
881
+ }, (hold) => copy(hold.wait));
873
882
  } catch (error) {
874
883
  const code = error?.code;
875
884
  console.error(chalk.red(`Could not warm this worktree: ${error.message}`));
876
- const claim = claimFailure(error, `stim worktree warm${opts.refresh ? " --refresh" : ""}`);
885
+ const claim = (0, ownership_claim_exports.claimFailure)(error, `stim worktree warm${opts.refresh ? " --refresh" : ""}`);
877
886
  if (claim) console.error(chalk.dim(claim.remedy));
878
887
  if (typeof code === "string" && code.startsWith("STIM_")) console.error(chalk.red(`failed: ${code}`));
879
888
  process.exitCode = 1;
@@ -1,5 +1,5 @@
1
1
  import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { g as loadConfig, u as getConfigDir } from "./config-CcefMY2Q.mjs";
2
+ import { g as loadConfig, u as getConfigDir } from "./config-6vdGngKa.mjs";
3
3
  import { accessSync, chmodSync, constants, lstatSync, mkdirSync, mkdtempSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
4
4
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
5
5
  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, utimesSync } from "fs";
@@ -125,6 +125,36 @@ function repoRoot(cwd) {
125
125
  ]);
126
126
  return out ? out.trim() : null;
127
127
  }
128
+ function locallyKnownUpstream(projectRoot) {
129
+ try {
130
+ const name = getExecutor().runFile("git", [
131
+ "-C",
132
+ projectRoot,
133
+ "rev-parse",
134
+ "--abbrev-ref",
135
+ "--symbolic-full-name",
136
+ "@{upstream}"
137
+ ], { timeoutMs: 5e3 });
138
+ const counts = getExecutor().runFile("git", [
139
+ "-C",
140
+ projectRoot,
141
+ "rev-list",
142
+ "--left-right",
143
+ "--count",
144
+ "HEAD...@{upstream}"
145
+ ], { timeoutMs: 5e3 }).trim().split(/\s+/).map(Number);
146
+ const ahead = counts[0] ?? NaN;
147
+ const behind = counts[1] ?? NaN;
148
+ if (!name || !Number.isInteger(ahead) || !Number.isInteger(behind)) return null;
149
+ return {
150
+ name,
151
+ ahead,
152
+ behind
153
+ };
154
+ } catch {
155
+ return null;
156
+ }
157
+ }
128
158
  function warmWorktreePaths(cwd) {
129
159
  const currentRoot = repoRoot(cwd);
130
160
  if (!currentRoot) throw new Error("Not a git repository.");
@@ -540,4 +570,4 @@ function listWorktrees(cwd) {
540
570
  return out ? parseWorktrees(out) : [];
541
571
  }
542
572
  //#endregion
543
- export { removeTemporaryEntry as C, makeTemporaryDirectory as S, resolveFullRef as _, dirtyFingerprintFiles as a, warmWorktreePaths as b, hasRemote as c, isPodInstallChurn as d, listWorktrees as f, repoRoot as g, removeWorktree as h, depsOutOfSync as i, hasUncommittedWork as l, readWorktreeExclude as m, cloneIgnoredEntries as n, dirtyPaths as o, podsOutOfSync as p, deleteBranch as r, gitCommonDir as s, branchExists as t, isMainWorkingTree as u, restoreFile as v, temporaryRoot as w, filesystemDevice as x, unpushedCommits as y };
573
+ export { makeTemporaryDirectory as C, filesystemDevice as S, temporaryRoot as T, repoRoot as _, dirtyFingerprintFiles as a, unpushedCommits as b, hasRemote as c, isPodInstallChurn as d, listWorktrees as f, removeWorktree as g, readWorktreeExclude as h, depsOutOfSync as i, hasUncommittedWork as l, podsOutOfSync as m, cloneIgnoredEntries as n, dirtyPaths as o, locallyKnownUpstream as p, deleteBranch as r, gitCommonDir as s, branchExists as t, isMainWorkingTree as u, resolveFullRef as v, removeTemporaryEntry as w, warmWorktreePaths as x, restoreFile as y };