@tablation/crew 0.128.3 → 0.129.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.
package/dist/cli.js CHANGED
@@ -797,7 +797,7 @@ function shellInitScript(shell) {
797
797
  }
798
798
 
799
799
  // src/config.ts
800
- import { readFileSync as readFileSync15, existsSync as existsSync15 } from "fs";
800
+ import { readFileSync as readFileSync15, existsSync as existsSync14 } from "fs";
801
801
  import { resolve as resolve2, dirname as dirname8, isAbsolute as isAbsolute2, join as join17, basename as basename4 } from "path";
802
802
  import { homedir as homedir6, tmpdir } from "os";
803
803
  import { parse as parse2 } from "yaml";
@@ -2664,7 +2664,64 @@ function explain(ship, required) {
2664
2664
  }
2665
2665
 
2666
2666
  // src/repo-config.ts
2667
- import { readFileSync as readFileSync14, existsSync as existsSync14 } from "fs";
2667
+ import { readFileSync as readFileSync14, existsSync as hostExists } from "fs";
2668
+
2669
+ // src/remote-dir.ts
2670
+ import { execFileSync as execFileSync5 } from "child_process";
2671
+ import { posix } from "path";
2672
+ var REMOTE_ROOT = "/.crew-sandbox";
2673
+ var registry = /* @__PURE__ */ new Map();
2674
+ function registerRemoteDir(r) {
2675
+ const dir = `${REMOTE_ROOT}/${r.sandbox}`;
2676
+ registry.set(dir, r);
2677
+ return dir;
2678
+ }
2679
+ function unregisterRemoteDir(dir) {
2680
+ registry.delete(dir);
2681
+ }
2682
+ function remoteOf(path) {
2683
+ if (!path.startsWith(`${REMOTE_ROOT}/`)) return null;
2684
+ for (const [dir, remote] of registry) {
2685
+ if (path === dir) return { remote, path: remote.workDir };
2686
+ if (path.startsWith(`${dir}/`)) return { remote, path: posix.join(remote.workDir, path.slice(dir.length + 1)) };
2687
+ }
2688
+ return null;
2689
+ }
2690
+ function sandboxExecArgs(r, cwd, argv2) {
2691
+ return ["sandbox", "exec", "-n", r.sandbox, "--no-login-shell", "--workdir", cwd, "--", ...argv2];
2692
+ }
2693
+ function execRemoteSync(hit, argv2, o = {}) {
2694
+ return execFileSync5(hit.remote.bin, sandboxExecArgs(hit.remote, o.cwd ?? hit.path, argv2), {
2695
+ encoding: "utf8",
2696
+ stdio: [o.input === void 0 ? "ignore" : "pipe", "pipe", "pipe"],
2697
+ maxBuffer: 256 * 1024 * 1024,
2698
+ ...o.input === void 0 ? {} : { input: o.input }
2699
+ });
2700
+ }
2701
+ function existsAt(path, hostExists2) {
2702
+ const hit = remoteOf(path);
2703
+ if (!hit) return hostExists2(path);
2704
+ try {
2705
+ execRemoteSync(hit, ["test", "-e", hit.path], { cwd: hit.remote.workDir });
2706
+ return true;
2707
+ } catch {
2708
+ return false;
2709
+ }
2710
+ }
2711
+ function readTextAt(path, hostRead) {
2712
+ const hit = remoteOf(path);
2713
+ return hit ? execRemoteSync(hit, ["cat", "--", hit.path], { cwd: hit.remote.workDir }) : hostRead(path);
2714
+ }
2715
+ function writeTextAt(path, text2, hostWrite) {
2716
+ const hit = remoteOf(path);
2717
+ if (!hit) {
2718
+ hostWrite(path, text2);
2719
+ return;
2720
+ }
2721
+ execRemoteSync(hit, ["sh", "-c", 'cat > "$1"', "sh", hit.path], { input: text2, cwd: hit.remote.workDir });
2722
+ }
2723
+
2724
+ // src/repo-config.ts
2668
2725
  import { join as join16, basename as basename3, dirname as dirname7 } from "path";
2669
2726
  import { parse } from "yaml";
2670
2727
 
@@ -2964,7 +3021,7 @@ var PROVIDERS = ["github", "buildkite", "other", "none"];
2964
3021
  function findRepoConfig(dir) {
2965
3022
  for (const name of REPO_CONFIG_FILENAMES) {
2966
3023
  const p = join16(dir, name);
2967
- if (existsSync14(p)) return p;
3024
+ if (existsAt(p, hostExists)) return p;
2968
3025
  }
2969
3026
  return null;
2970
3027
  }
@@ -3218,10 +3275,10 @@ function parseRepoConfig(text2, file) {
3218
3275
  if (versionFilesGiven) {
3219
3276
  for (const name of versionFiles) {
3220
3277
  const path = join16(dirname7(file), name);
3221
- if (!existsSync14(path)) continue;
3278
+ if (!existsAt(path, hostExists)) continue;
3222
3279
  let text3;
3223
3280
  try {
3224
- text3 = readFileSync14(path, "utf8");
3281
+ text3 = readTextAt(path, (p) => readFileSync14(p, "utf8"));
3225
3282
  } catch (e) {
3226
3283
  throw new RepoConfigError(
3227
3284
  `${file}: release.versionFiles entry "${name}" cannot be read (${e.code ?? "read error"})`
@@ -3266,7 +3323,7 @@ function parseRepoConfig(text2, file) {
3266
3323
  function loadRepoConfig(dir) {
3267
3324
  const file = findRepoConfig(dir);
3268
3325
  if (!file) return null;
3269
- return parseRepoConfig(readFileSync14(file, "utf8"), file);
3326
+ return parseRepoConfig(readTextAt(file, (p) => readFileSync14(p, "utf8")), file);
3270
3327
  }
3271
3328
  function loadRepoConfigTolerant(dir, refused) {
3272
3329
  try {
@@ -3849,7 +3906,7 @@ function configSearchPath(crewHome) {
3849
3906
  }
3850
3907
  function findConfigFile(crewHome) {
3851
3908
  const candidates2 = configSearchPath(crewHome);
3852
- return candidates2.find((c) => existsSync15(c)) ?? candidates2[candidates2.length - 1];
3909
+ return candidates2.find((c) => existsSync14(c)) ?? candidates2[candidates2.length - 1];
3853
3910
  }
3854
3911
  var defaultConfigPath = () => join17(process.env.XDG_CONFIG_HOME ?? join17(homedir6(), ".config"), "crew", "crew.yaml");
3855
3912
  function routeSlug(route2) {
@@ -3872,7 +3929,7 @@ function splitRoute(route2) {
3872
3929
  }
3873
3930
  function readResolvedFile(stateDir, route2) {
3874
3931
  const path = resolvedPathFor(stateDir, route2);
3875
- if (!existsSync15(path)) return void 0;
3932
+ if (!existsSync14(path)) return void 0;
3876
3933
  try {
3877
3934
  return JSON.parse(readFileSync15(path, "utf8"));
3878
3935
  } catch (e) {
@@ -3935,7 +3992,7 @@ function parseResolved(raw, m, where) {
3935
3992
  }
3936
3993
  function loadConfig(crewHome, configFile, opts = {}) {
3937
3994
  const file = configFile ?? findConfigFile(crewHome);
3938
- if (opts.text === void 0 && !existsSync15(file)) {
3995
+ if (opts.text === void 0 && !existsSync14(file)) {
3939
3996
  throw new ConfigError(
3940
3997
  `no config found. Looked in:
3941
3998
  ` + configSearchPath(crewHome).map((c) => ` - ${c}`).join("\n") + `
@@ -4353,7 +4410,7 @@ function repoIdForName(r, name) {
4353
4410
  }
4354
4411
  function resolveApiKey(r) {
4355
4412
  if (r.apiKey) return r.apiKey;
4356
- if (r.apiKeyFile && r.apiKeyVar && existsSync15(r.apiKeyFile)) {
4413
+ if (r.apiKeyFile && r.apiKeyVar && existsSync14(r.apiKeyFile)) {
4357
4414
  for (const line of readFileSync15(r.apiKeyFile, "utf8").split("\n")) {
4358
4415
  const m = line.match(new RegExp(`^${r.apiKeyVar}=(.*)$`));
4359
4416
  if (m) return (m[1] ?? "").trim().replace(/^["']|["']$/g, "");
@@ -4367,7 +4424,7 @@ function resolveApiKey(r) {
4367
4424
  }
4368
4425
  function credentialSource(r) {
4369
4426
  if (r.apiKey) return KEYCHAIN_ELIGIBLE_ROUTES.has(r) ? "keychain" : "inline";
4370
- if (r.apiKeyFile && r.apiKeyVar && existsSync15(r.apiKeyFile)) {
4427
+ if (r.apiKeyFile && r.apiKeyVar && existsSync14(r.apiKeyFile)) {
4371
4428
  const re = new RegExp(`^${r.apiKeyVar}=`);
4372
4429
  if (readFileSync15(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
4373
4430
  }
@@ -4586,11 +4643,18 @@ function resolveShell(configured2) {
4586
4643
  if (/cmd(\.exe)?$/i.test(bin)) return { bin, args: ["/d", "/s", "/c"] };
4587
4644
  return { bin, args: ["-c"] };
4588
4645
  }
4646
+ function remoteScript(script, prelude, env) {
4647
+ const q2 = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
4648
+ const exports = Object.entries(env ?? {}).filter(([, v]) => v !== void 0).map(([k, v]) => `${k}=${q2(String(v))}; export ${k}; `).join("");
4649
+ return `${prelude ?? ""}${exports}${script}`;
4650
+ }
4589
4651
  function runScript(script, opts) {
4590
4652
  const shell = opts.shell ?? defaultShell();
4653
+ const hit = remoteOf(opts.cwd);
4654
+ const [bin, args, cwd] = hit ? [hit.remote.bin, sandboxExecArgs(hit.remote, hit.path, [shell.bin, ...shell.args, remoteScript(script, hit.remote.prelude, opts.env)]), void 0] : [shell.bin, [...shell.args, script], opts.cwd];
4591
4655
  return new Promise((resolvePromise) => {
4592
- const child = spawn2(shell.bin, [...shell.args, script], {
4593
- cwd: opts.cwd,
4656
+ const child = spawn2(bin, args, {
4657
+ cwd,
4594
4658
  env: { ...process.env, ...opts.env },
4595
4659
  stdio: ["ignore", "pipe", "pipe"]
4596
4660
  });
@@ -4762,7 +4826,7 @@ function recoveredFailureHeadlines(o, route2) {
4762
4826
  // src/install.ts
4763
4827
  import { dirname as dirname9, join as join18, extname } from "path";
4764
4828
  import { homedir as homedir7, userInfo } from "os";
4765
- import { accessSync as accessSync2, constants as constants2, existsSync as existsSync16, mkdirSync as mkdirSync11, readdirSync as readdirSync3, readFileSync as readFileSync16, statSync as statSync5, writeFileSync as writeFileSync12, unlinkSync, realpathSync } from "fs";
4829
+ import { accessSync as accessSync2, constants as constants2, existsSync as existsSync15, mkdirSync as mkdirSync11, readdirSync as readdirSync3, readFileSync as readFileSync16, statSync as statSync5, writeFileSync as writeFileSync12, unlinkSync, realpathSync } from "fs";
4766
4830
  import { spawnSync as spawnSync5 } from "child_process";
4767
4831
  import { createHash as createHash3 } from "crypto";
4768
4832
 
@@ -5047,7 +5111,7 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
5047
5111
  };
5048
5112
  }
5049
5113
  if (hostShip === "linux" && hasSystemd) {
5050
- const system = existsSync16(systemUnitPathFor(label));
5114
+ const system = existsSync15(systemUnitPathFor(label));
5051
5115
  const unitDir = system ? SYSTEM_UNIT_DIR : join18(homedir7(), ".config", "systemd", "user");
5052
5116
  const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
5053
5117
  if (job === "run") {
@@ -5088,7 +5152,7 @@ function planDaemonControl(crewHome, hostShip, hasSystemd) {
5088
5152
  }
5089
5153
  if (hostShip === "linux" && hasSystemd) {
5090
5154
  const unit = `${label}.service`;
5091
- const ctl = existsSync16(systemUnitPathFor(label)) ? ["systemctl"] : ["systemctl", "--user"];
5155
+ const ctl = existsSync15(systemUnitPathFor(label)) ? ["systemctl"] : ["systemctl", "--user"];
5092
5156
  return {
5093
5157
  mechanism: "systemd",
5094
5158
  startCommand: [...ctl, "start", unit],
@@ -5120,7 +5184,7 @@ async function applyInstall(plan, crewHome, dryRun, log) {
5120
5184
  log.emit(`would write ${path}`);
5121
5185
  continue;
5122
5186
  }
5123
- const before = existsSync16(path) ? readFileSync16(path, "utf8") : null;
5187
+ const before = existsSync15(path) ? readFileSync16(path, "utf8") : null;
5124
5188
  if (before !== plan.unitContent[path]) changed2 = true;
5125
5189
  mkdirSync11(dirname9(path), { recursive: true });
5126
5190
  writeFileSync12(path, plan.unitContent[path]);
@@ -5187,7 +5251,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
5187
5251
  }
5188
5252
  }
5189
5253
  for (const path of plan.unitPaths) {
5190
- if (!existsSync16(path)) continue;
5254
+ if (!existsSync15(path)) continue;
5191
5255
  if (dryRun) {
5192
5256
  log.emit(`would remove ${path}`);
5193
5257
  continue;
@@ -5223,7 +5287,7 @@ function loadedCrewLabels() {
5223
5287
  return [...parseLaunchctlList(list.stdout ?? "").keys()].filter((l) => l.startsWith(CREW_LAUNCHD_PREFIX));
5224
5288
  }
5225
5289
  function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
5226
- const files = existsSync16(dir) ? readdirSync3(dir) : [];
5290
+ const files = existsSync15(dir) ? readdirSync3(dir) : [];
5227
5291
  const units = files.filter((f) => f.startsWith(CREW_LAUNCHD_PREFIX) && f.endsWith(".plist")).sort().map((f) => {
5228
5292
  const plistPath = join18(dir, f);
5229
5293
  let program = null;
@@ -5235,7 +5299,7 @@ function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
5235
5299
  label: f.slice(0, -".plist".length),
5236
5300
  plistPath,
5237
5301
  program,
5238
- programExists: program !== null && existsSync16(program)
5302
+ programExists: program !== null && existsSync15(program)
5239
5303
  };
5240
5304
  });
5241
5305
  const seen = new Set(units.map((u) => u.label));
@@ -5254,9 +5318,9 @@ function removeLaunchdUnit(unit, dryRun, log) {
5254
5318
  log.emit(`would unload and remove ${unit.label}`);
5255
5319
  return;
5256
5320
  }
5257
- if (existsSync16(unit.plistPath)) spawnSync5("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
5321
+ if (existsSync15(unit.plistPath)) spawnSync5("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
5258
5322
  spawnSync5("launchctl", ["remove", unit.label], { encoding: "utf8" });
5259
- if (existsSync16(unit.plistPath)) unlinkSync(unit.plistPath);
5323
+ if (existsSync15(unit.plistPath)) unlinkSync(unit.plistPath);
5260
5324
  log.emit(`removed ${unit.label} (${unit.plistPath})`);
5261
5325
  }
5262
5326
  function clearForeignLaunchdUnits(crewHome, replace, dryRun, log, dir, loaded) {
@@ -5334,12 +5398,12 @@ function detectSystemd() {
5334
5398
  }
5335
5399
 
5336
5400
  // src/ship-attention.ts
5337
- import { existsSync as existsSync18, statfsSync } from "fs";
5401
+ import { existsSync as existsSync17, statfsSync } from "fs";
5338
5402
 
5339
5403
  // src/git.ts
5340
- import { execFileSync as execFileSync5 } from "child_process";
5404
+ import { execFileSync as execFileSync6 } from "child_process";
5341
5405
  import {
5342
- existsSync as existsSync17,
5406
+ existsSync as existsSync16,
5343
5407
  mkdirSync as mkdirSync12,
5344
5408
  openSync as openSync3,
5345
5409
  closeSync as closeSync3,
@@ -5354,12 +5418,22 @@ import {
5354
5418
  import { dirname as dirname10, basename as basename5, join as join19 } from "path";
5355
5419
  var GitError = class extends Error {
5356
5420
  };
5421
+ function execGit(cwd, args, input) {
5422
+ const hit = remoteOf(cwd);
5423
+ if (hit) return execRemoteSync(hit, ["git", ...args], { input });
5424
+ return execFileSync6("git", args, {
5425
+ cwd,
5426
+ encoding: "utf8",
5427
+ maxBuffer: 256 * 1024 * 1024,
5428
+ ...input === void 0 ? { stdio: ["ignore", "pipe", "pipe"] } : { input }
5429
+ });
5430
+ }
5357
5431
  function streamText(x) {
5358
5432
  return String(x ?? "").trim();
5359
5433
  }
5360
5434
  function gitRaw(cwd, args) {
5361
5435
  try {
5362
- return execFileSync5("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
5436
+ return execGit(cwd, args);
5363
5437
  } catch (e) {
5364
5438
  const err3 = e;
5365
5439
  const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
@@ -5368,7 +5442,7 @@ function gitRaw(cwd, args) {
5368
5442
  }
5369
5443
  function git(cwd, args) {
5370
5444
  try {
5371
- return execFileSync5("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
5445
+ return execGit(cwd, args).trim();
5372
5446
  } catch (e) {
5373
5447
  const err3 = e;
5374
5448
  const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
@@ -5516,6 +5590,10 @@ function pushTag(cwd, remote, tag) {
5516
5590
  git(cwd, ["push", remote, tag]);
5517
5591
  }
5518
5592
  function fetchRemote(cwd, remote = "origin") {
5593
+ if (remoteOf(cwd)) {
5594
+ const depth = gitOk(cwd, ["config", "--get", "crew.releaseDepth"]);
5595
+ return gitOk(cwd, ["fetch", "--quiet", ...depth ? ["--depth", depth] : [], remote]) !== null;
5596
+ }
5519
5597
  if (gitOk(cwd, ["fetch", "--prune", "--quiet", remote]) === null) return false;
5520
5598
  gitOk(cwd, ["fetch", "--quiet", remote, "refs/tags/*:refs/tags/*"]);
5521
5599
  return true;
@@ -5539,9 +5617,9 @@ function keyCommitsInRange(cwd, key, range) {
5539
5617
  }
5540
5618
  function patchIdOf(cwd, from, to) {
5541
5619
  try {
5542
- const diff = execFileSync5("git", ["diff", from, to], { cwd, encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
5620
+ const diff = execGit(cwd, ["diff", from, to]);
5543
5621
  if (!diff) return null;
5544
- const out = execFileSync5("git", ["patch-id", "--stable"], { cwd, encoding: "utf8", input: diff });
5622
+ const out = execGit(cwd, ["patch-id", "--stable"], diff);
5545
5623
  return out.split(" ")[0]?.trim() || null;
5546
5624
  } catch {
5547
5625
  return null;
@@ -5753,7 +5831,7 @@ function sweepStaleReleaseRoots(tmp, now = Date.now(), maxAgeMs = 60 * 60 * 1e3)
5753
5831
  try {
5754
5832
  const st = statSync6(root);
5755
5833
  if (!st.isDirectory() || now - st.mtimeMs < maxAgeMs) continue;
5756
- if (existsSync17(join19(root, "checkout"))) continue;
5834
+ if (existsSync16(join19(root, "checkout"))) continue;
5757
5835
  rmSync9(root, { recursive: true, force: true });
5758
5836
  removed.push(name);
5759
5837
  } catch {
@@ -5805,7 +5883,7 @@ function originUrlOf(dir, remoteName) {
5805
5883
  }
5806
5884
  }
5807
5885
  function ensureRepoCheckout(dir, remote, opts = {}) {
5808
- if (existsSync17(`${dir}/.git`)) return false;
5886
+ if (existsSync16(`${dir}/.git`)) return false;
5809
5887
  if (!remote) {
5810
5888
  throw new GitError(
5811
5889
  `${dir} does not exist and this repo has no discovered remote to clone from \u2014 re-run \`crew connect\`, or check out the repository yourself and point \`repos:\` at it`
@@ -5814,7 +5892,7 @@ function ensureRepoCheckout(dir, remote, opts = {}) {
5814
5892
  mkdirSync12(dirname10(dir), { recursive: true });
5815
5893
  const url = cloneUrlFor(remote, opts);
5816
5894
  try {
5817
- execFileSync5("git", ["clone", url, dir], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
5895
+ execFileSync6("git", ["clone", url, dir], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
5818
5896
  } catch (e) {
5819
5897
  const err3 = e;
5820
5898
  const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
@@ -5873,10 +5951,10 @@ function acquireCheckoutLock(dir, timeoutMs = 3e4) {
5873
5951
  function syncPassengerCheckout(dir, githubRemote, gitRemoteName, base, clone = {}) {
5874
5952
  const release = acquireCheckoutLock(dir);
5875
5953
  try {
5876
- if (!existsSync17(`${dir}/.git`) && githubRemote) {
5954
+ if (!existsSync16(`${dir}/.git`) && githubRemote) {
5877
5955
  const url = cloneUrlFor(githubRemote, clone);
5878
5956
  const marker = cloneFailureMarker(dir);
5879
- if (existsSync17(marker) && readFileSync17(marker, "utf8").split("\n")[0] === url) {
5957
+ if (existsSync16(marker) && readFileSync17(marker, "utf8").split("\n")[0] === url) {
5880
5958
  return { action: "clone-failed", detail: readFileSync17(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
5881
5959
  }
5882
5960
  try {
@@ -6190,7 +6268,7 @@ function noteHeldWorktree(state, h, nowMs = Date.now()) {
6190
6268
  function reconcileHeldWorktrees(state, repo, log, stillRefused) {
6191
6269
  for (const i of state.shipAttention().previous()) {
6192
6270
  if (i.kind !== "worktree_held" || !i.held || i.held.repo !== repo) continue;
6193
- const gone = !existsSync18(i.held.path);
6271
+ const gone = !existsSync17(i.held.path);
6194
6272
  if (!gone && !(stillRefused && !stillRefused.has(i.held.path))) continue;
6195
6273
  clearShipAttention(state, i.key);
6196
6274
  log.emit(
@@ -6356,14 +6434,14 @@ function attentionProblems(items, already) {
6356
6434
  // src/sandbox-image.ts
6357
6435
  import { createHash as createHash7 } from "crypto";
6358
6436
  import { spawn as spawn5 } from "child_process";
6359
- import { execFileSync as execFileSync8 } from "child_process";
6360
- import { existsSync as existsSync30, mkdirSync as mkdirSync19, readFileSync as readFileSync28, renameSync as renameSync3, writeFileSync as writeFileSync20, rmSync as rmSync15 } from "fs";
6437
+ import { execFileSync as execFileSync9 } from "child_process";
6438
+ import { existsSync as existsSync29, mkdirSync as mkdirSync19, readFileSync as readFileSync28, renameSync as renameSync3, writeFileSync as writeFileSync20, rmSync as rmSync15 } from "fs";
6361
6439
  import { dirname as dirname15, join as join32 } from "path";
6362
6440
  import { fileURLToPath } from "url";
6363
6441
  import { tmpdir as tmpdir3 } from "os";
6364
6442
 
6365
6443
  // src/passenger-containers.ts
6366
- import { execFileSync as execFileSync7 } from "child_process";
6444
+ import { execFileSync as execFileSync8 } from "child_process";
6367
6445
  import { createHash as createHash6 } from "crypto";
6368
6446
 
6369
6447
  // src/sandbox-cache.ts
@@ -6372,22 +6450,22 @@ import { promisify as promisify2 } from "util";
6372
6450
 
6373
6451
  // src/deprovision-host.ts
6374
6452
  import { spawnSync as spawnSync7 } from "child_process";
6375
- import { existsSync as existsSync29, readdirSync as readdirSync11 } from "fs";
6453
+ import { existsSync as existsSync28, readdirSync as readdirSync11 } from "fs";
6376
6454
 
6377
6455
  // src/interrupted.ts
6378
- import { existsSync as existsSync27, readdirSync as readdirSync9, readFileSync as readFileSync27 } from "fs";
6456
+ import { existsSync as existsSync26, readdirSync as readdirSync9, readFileSync as readFileSync27 } from "fs";
6379
6457
  import { join as join30 } from "path";
6380
6458
 
6381
6459
  // src/agent.ts
6382
6460
  import { spawnSync as spawnSync6 } from "child_process";
6383
- import { readFileSync as readFileSync26, existsSync as existsSync26, statSync as statSync8, mkdirSync as mkdirSync18, writeFileSync as writeFileSync19, createWriteStream, openSync as openSync4, closeSync as closeSync4, readSync } from "fs";
6461
+ import { readFileSync as readFileSync26, existsSync as existsSync25, statSync as statSync8, mkdirSync as mkdirSync18, writeFileSync as writeFileSync19, createWriteStream, openSync as openSync4, closeSync as closeSync4, readSync } from "fs";
6384
6462
  import { StringDecoder } from "string_decoder";
6385
6463
  import { finished } from "stream/promises";
6386
6464
  import { join as join29, dirname as dirname14, basename as basename9 } from "path";
6387
6465
  import { createHash as createHash5 } from "crypto";
6388
6466
 
6389
6467
  // src/state.ts
6390
- import { readFileSync as readFileSync19, writeFileSync as writeFileSync14, existsSync as existsSync19, mkdirSync as mkdirSync13, rmSync as rmSync10, statSync as statSync7, readdirSync as readdirSync5 } from "fs";
6468
+ import { readFileSync as readFileSync19, writeFileSync as writeFileSync14, existsSync as existsSync18, mkdirSync as mkdirSync13, rmSync as rmSync10, statSync as statSync7, readdirSync as readdirSync5 } from "fs";
6391
6469
  import { join as join21 } from "path";
6392
6470
  var EPOCH = "1970-01-01T00:00:00.000Z";
6393
6471
  var State = class _State {
@@ -6433,10 +6511,10 @@ var State = class _State {
6433
6511
  }
6434
6512
  /** Whole-crew pause, and the per-role sentinels one level down. */
6435
6513
  isPaused() {
6436
- return existsSync19(this.path(".crew-paused"));
6514
+ return existsSync18(this.path(".crew-paused"));
6437
6515
  }
6438
6516
  isRolePaused(role) {
6439
- return existsSync19(this.path(`.role-paused-${role}`));
6517
+ return existsSync18(this.path(`.role-paused-${role}`));
6440
6518
  }
6441
6519
  pausedRoles(roles) {
6442
6520
  return new Set(roles.filter((r) => this.isRolePaused(r)));
@@ -6453,7 +6531,7 @@ var State = class _State {
6453
6531
  * while agents keep working.
6454
6532
  */
6455
6533
  isReleasePaused() {
6456
- return existsSync19(this.path(".release-paused"));
6534
+ return existsSync18(this.path(".release-paused"));
6457
6535
  }
6458
6536
  setReleasePaused(paused) {
6459
6537
  if (paused) writeFileSync14(this.path(".release-paused"), "");
@@ -7874,7 +7952,7 @@ function renderEnvironment(i) {
7874
7952
  }
7875
7953
 
7876
7954
  // src/intel-bridge.ts
7877
- import { existsSync as existsSync20, mkdirSync as mkdirSync14, readFileSync as readFileSync21, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "fs";
7955
+ import { existsSync as existsSync19, mkdirSync as mkdirSync14, readFileSync as readFileSync21, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "fs";
7878
7956
  import { dirname as dirname11, join as join22 } from "path";
7879
7957
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
7880
7958
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
@@ -8023,7 +8101,7 @@ function createBridge(o) {
8023
8101
  tryAttach,
8024
8102
  async start(transport) {
8025
8103
  await server.connect(transport);
8026
- if (existsSync20(o.attachFile)) await tryAttach();
8104
+ if (existsSync19(o.attachFile)) await tryAttach();
8027
8105
  timer = setInterval(() => {
8028
8106
  void tryAttach();
8029
8107
  }, o.pollMs ?? 1e3);
@@ -8280,7 +8358,7 @@ var BeforeFirstEditTracker = class {
8280
8358
  };
8281
8359
 
8282
8360
  // src/agent-log.ts
8283
- import { existsSync as existsSync23, mkdirSync as mkdirSync16, readdirSync as readdirSync7, readFileSync as readFileSync23, rmSync as rmSync13, writeFileSync as writeFileSync17 } from "fs";
8361
+ import { existsSync as existsSync22, mkdirSync as mkdirSync16, readdirSync as readdirSync7, readFileSync as readFileSync23, rmSync as rmSync13, writeFileSync as writeFileSync17 } from "fs";
8284
8362
  import { join as join25 } from "path";
8285
8363
 
8286
8364
  // src/pending-backoff.ts
@@ -9347,7 +9425,7 @@ function materializeBranchForTicket(dir, cfg, t, role, onProblem, onReset) {
9347
9425
  }
9348
9426
 
9349
9427
  // src/pending-handoff.ts
9350
- import { existsSync as existsSync21, mkdirSync as mkdirSync15, readdirSync as readdirSync6, readFileSync as readFileSync22, rmSync as rmSync12, writeFileSync as writeFileSync16 } from "fs";
9428
+ import { existsSync as existsSync20, mkdirSync as mkdirSync15, readdirSync as readdirSync6, readFileSync as readFileSync22, rmSync as rmSync12, writeFileSync as writeFileSync16 } from "fs";
9351
9429
  import { join as join23 } from "path";
9352
9430
  var PENDING_MAX_AGE_MS = 48 * 36e5;
9353
9431
  var pendingDirFor = (stateDir) => join23(stateDir, "pending-handoffs");
@@ -9359,7 +9437,7 @@ function writePendingHandoff(stateDir, rec) {
9359
9437
  }
9360
9438
  function listPendingHandoffs(stateDir, route2) {
9361
9439
  const dir = pendingDirFor(stateDir);
9362
- if (!existsSync21(dir)) return [];
9440
+ if (!existsSync20(dir)) return [];
9363
9441
  const out = [];
9364
9442
  for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
9365
9443
  try {
@@ -9540,7 +9618,7 @@ function holdWarnings(tickets, ships, contract, humanHolds, now = Date.now()) {
9540
9618
  }
9541
9619
 
9542
9620
  // src/run-worktree.ts
9543
- import { existsSync as existsSync22 } from "fs";
9621
+ import { existsSync as existsSync21 } from "fs";
9544
9622
  import { dirname as dirname12, join as join24 } from "path";
9545
9623
  var describeRunWorktreePlan = (p) => {
9546
9624
  switch (p.action) {
@@ -9582,7 +9660,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
9582
9660
  const existing = worktreeForNumber(dir, num);
9583
9661
  const located = locateBranchForTicket(dir, cfg, t, role);
9584
9662
  const baseRef = hasRemote ? `${remote}/${base}` : base;
9585
- if (existing && !existing.prunable && existsSync22(existing.path)) {
9663
+ if (existing && !existing.prunable && existsSync21(existing.path)) {
9586
9664
  const branch = existing.branch;
9587
9665
  if (status(existing.path).length > 0) {
9588
9666
  return { action: opts.resumeInterrupted ? "resume-dirty" : "leave-dirty", path: existing.path, branch };
@@ -9652,7 +9730,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
9652
9730
  if (remoteConfigured(dir, remote) && remoteBranchExists(dir, remote, branch)) return false;
9653
9731
  if (countCommits(dir, `${baseRef}..${branch}`) !== 0) return false;
9654
9732
  const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
9655
- if (wt && existsSync22(wt.path)) {
9733
+ if (wt && existsSync21(wt.path)) {
9656
9734
  if (status(wt.path).length > 0) return false;
9657
9735
  if (gitOk(dir, ["worktree", "remove", wt.path]) === null) return false;
9658
9736
  }
@@ -9660,7 +9738,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
9660
9738
  }
9661
9739
  async function finishRunWorktree(dir, cfg, branch) {
9662
9740
  const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
9663
- if (!wt || !existsSync22(wt.path)) return { kind: "none" };
9741
+ if (!wt || !existsSync21(wt.path)) return { kind: "none" };
9664
9742
  if (cfg.hooks.handoff) return { kind: "kept-handoff", path: wt.path };
9665
9743
  if (status(wt.path).length > 0) return { kind: "kept-dirty", path: wt.path };
9666
9744
  if (remoteConfigured(dir, cfg.branch.remote) && worktreeHoldsUnpushed(wt.path, cfg.branch.remote)) {
@@ -9672,7 +9750,7 @@ async function finishRunWorktree(dir, cfg, branch) {
9672
9750
  if (pids.length) await killGently(pids);
9673
9751
  }
9674
9752
  gitOk(dir, ["worktree", "remove", wt.path]);
9675
- return existsSync22(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
9753
+ return existsSync21(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
9676
9754
  }
9677
9755
 
9678
9756
  // src/handoff-guard.ts
@@ -10310,7 +10388,7 @@ function queueAgentRun(stateDir, name, q2) {
10310
10388
  }
10311
10389
  async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
10312
10390
  const dir = queueDir(stateDir);
10313
- if (!existsSync23(dir)) return 0;
10391
+ if (!existsSync22(dir)) return 0;
10314
10392
  let sent = 0;
10315
10393
  for (const f of readdirSync7(dir).filter((n) => n.endsWith(".json")).sort()) {
10316
10394
  const path = join25(dir, f);
@@ -10363,7 +10441,7 @@ function queueTicketCounters(stateDir, name, q2) {
10363
10441
  }
10364
10442
  async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
10365
10443
  const dir = countersDir(stateDir);
10366
- if (!existsSync23(dir)) return 0;
10444
+ if (!existsSync22(dir)) return 0;
10367
10445
  let sent = 0;
10368
10446
  for (const f of readdirSync7(dir).filter((n) => n.endsWith(".json")).sort()) {
10369
10447
  const path = join25(dir, f);
@@ -10672,7 +10750,7 @@ function expandSkillMarkers(text2, lookup2, opts = {}) {
10672
10750
  }
10673
10751
 
10674
10752
  // src/skills.ts
10675
- import { readFileSync as readFileSync25, readdirSync as readdirSync8, existsSync as existsSync25 } from "fs";
10753
+ import { readFileSync as readFileSync25, readdirSync as readdirSync8, existsSync as existsSync24 } from "fs";
10676
10754
  import { join as join27 } from "path";
10677
10755
  import { parse as parseYaml } from "yaml";
10678
10756
  import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError3, TablationApiError as TablationApiError2 } from "@tablation/client";
@@ -10689,7 +10767,7 @@ ${additions}`;
10689
10767
  }
10690
10768
 
10691
10769
  // src/agents.ts
10692
- import { readFileSync as readFileSync24, existsSync as existsSync24 } from "fs";
10770
+ import { readFileSync as readFileSync24, existsSync as existsSync23 } from "fs";
10693
10771
  import { join as join26 } from "path";
10694
10772
  import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError2, TablationApiError } from "@tablation/client";
10695
10773
  var PERSONA_NAME = {
@@ -10705,10 +10783,10 @@ var AgentsSyncError = class extends Error {
10705
10783
  };
10706
10784
  function personaDefaultPrompt(promptsDir, role) {
10707
10785
  const briefPath = join26(promptsDir, "personas", `lane-${role}.md`);
10708
- if (!existsSync24(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
10786
+ if (!existsSync23(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
10709
10787
  if (SELF_CONTAINED_ROLES.has(role)) return readFileSync24(briefPath, "utf8");
10710
10788
  const commonPath = join26(promptsDir, "personas", "common.md");
10711
- if (!existsSync24(commonPath)) return readFileSync24(briefPath, "utf8");
10789
+ if (!existsSync23(commonPath)) return readFileSync24(briefPath, "utf8");
10712
10790
  return `${readFileSync24(commonPath, "utf8")}${readFileSync24(briefPath, "utf8")}`;
10713
10791
  }
10714
10792
  async function resolveAgentId(route2, role, opts) {
@@ -11170,7 +11248,7 @@ function parseSkillFile(path) {
11170
11248
  }
11171
11249
  function listLocalSkills(promptsDir) {
11172
11250
  const dir = join27(promptsDir, "skills");
11173
- if (!existsSync25(dir)) return [];
11251
+ if (!existsSync24(dir)) return [];
11174
11252
  return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join27(dir, f)));
11175
11253
  }
11176
11254
  async function syncSkills(route2, opts) {
@@ -11355,7 +11433,7 @@ function skillMarkerWarnings(promptsDir, personaPrompts) {
11355
11433
  }
11356
11434
 
11357
11435
  // src/runner.ts
11358
- import { spawn as spawn3, execFile, execFileSync as execFileSync6 } from "child_process";
11436
+ import { spawn as spawn3, execFile, execFileSync as execFileSync7 } from "child_process";
11359
11437
  import { mkdtempSync, mkdirSync as mkdirSync17, writeFileSync as writeFileSync18, rmSync as rmSync14 } from "fs";
11360
11438
  import { createHash as createHash4 } from "crypto";
11361
11439
  import { tmpdir as tmpdir2 } from "os";
@@ -11605,7 +11683,7 @@ var SandboxRunner = class {
11605
11683
  var describeExecError = describe;
11606
11684
  function gitOrigin(cwd) {
11607
11685
  try {
11608
- return execFileSync6("git", ["-C", cwd, "remote", "get-url", "origin"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
11686
+ return execFileSync7("git", ["-C", cwd, "remote", "get-url", "origin"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
11609
11687
  } catch {
11610
11688
  return null;
11611
11689
  }
@@ -11617,7 +11695,7 @@ function runnerOfSidecar(sc) {
11617
11695
  return sc.runner === "sandbox" ? new SandboxRunner(sc.sandboxBin ? { bin: sc.sandboxBin } : {}) : new HostRunner();
11618
11696
  }
11619
11697
  var PROBE_CREATE_TIMEOUT_MS = 12e4;
11620
- function checkSandboxRunner(cfg = {}, run3 = (b, a, t) => execFileSync6(b, a, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: t ?? 15e3 })) {
11698
+ function checkSandboxRunner(cfg = {}, run3 = (b, a, t) => execFileSync7(b, a, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: t ?? 15e3 })) {
11621
11699
  const bin = cfg.bin ?? DEFAULT_SANDBOX_CONFIG.bin;
11622
11700
  let version;
11623
11701
  try {
@@ -11806,18 +11884,18 @@ function codeBridgeFor(o, harness) {
11806
11884
  }
11807
11885
  function planAgentRun(o) {
11808
11886
  const briefPath = join29(o.route.promptsDir, "personas", `lane-${o.role}.md`);
11809
- if (!existsSync26(briefPath)) {
11887
+ if (!existsSync25(briefPath)) {
11810
11888
  throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
11811
11889
  }
11812
11890
  const commonPath = join29(o.route.promptsDir, "personas", "common.md");
11813
- const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync26(commonPath);
11891
+ const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync25(commonPath);
11814
11892
  const now = o.now ?? (() => Date.now());
11815
11893
  const routeLabel = routeSlug(o.route.route);
11816
11894
  const ownDigest = o.runId ? digestPathFor(o.stateDir, routeLabel, o.role, o.runId) : void 0;
11817
- const digestPath2 = ownDigest && existsSync26(ownDigest) ? ownDigest : digestPathFor(o.stateDir, routeLabel, o.role);
11895
+ const digestPath2 = ownDigest && existsSync25(ownDigest) ? ownDigest : digestPathFor(o.stateDir, routeLabel, o.role);
11818
11896
  let digest;
11819
11897
  let ageSeconds;
11820
- if (existsSync26(digestPath2)) {
11898
+ if (existsSync25(digestPath2)) {
11821
11899
  ageSeconds = Math.floor((now() - statSync8(digestPath2).mtimeMs) / 1e3);
11822
11900
  if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync26(digestPath2, "utf8");
11823
11901
  }
@@ -12563,7 +12641,7 @@ var pidAlive = (pid) => {
12563
12641
  };
12564
12642
  function runFinished(streamPath) {
12565
12643
  const path = `${streamPath}.meta.json`;
12566
- if (!existsSync27(path)) return false;
12644
+ if (!existsSync26(path)) return false;
12567
12645
  try {
12568
12646
  return JSON.parse(readFileSync27(path, "utf8")).running !== true;
12569
12647
  } catch {
@@ -12572,7 +12650,7 @@ function runFinished(streamPath) {
12572
12650
  }
12573
12651
  function orphanedStreams(o) {
12574
12652
  const dir = join30(o.stateDir, "streams");
12575
- if (!existsSync27(dir)) return [];
12653
+ if (!existsSync26(dir)) return [];
12576
12654
  const ownerAlive = o.isOwnerAlive ?? pidAlive;
12577
12655
  const out = [];
12578
12656
  for (const name of readdirSync9(dir)) {
@@ -12630,11 +12708,11 @@ async function reconcileRuns(o) {
12630
12708
  }
12631
12709
 
12632
12710
  // src/live-runs.ts
12633
- import { existsSync as existsSync28, readdirSync as readdirSync10, statSync as statSync9 } from "fs";
12711
+ import { existsSync as existsSync27, readdirSync as readdirSync10, statSync as statSync9 } from "fs";
12634
12712
  import { join as join31 } from "path";
12635
12713
  function liveRuns(stateDir, isAlive = runIsAlive) {
12636
12714
  const dir = join31(stateDir, "streams");
12637
- if (!existsSync28(dir)) return [];
12715
+ if (!existsSync27(dir)) return [];
12638
12716
  const out = [];
12639
12717
  for (const name of readdirSync10(dir)) {
12640
12718
  if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
@@ -12650,7 +12728,7 @@ function runActivity(stateDir, isAlive = runIsAlive) {
12650
12728
  const activeRuns = liveRuns(stateDir, isAlive).length;
12651
12729
  const dir = join31(stateDir, "streams");
12652
12730
  let newest = 0;
12653
- if (existsSync28(dir)) {
12731
+ if (existsSync27(dir)) {
12654
12732
  for (const name of readdirSync10(dir)) {
12655
12733
  if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
12656
12734
  const streamPath = join31(dir, name);
@@ -12743,7 +12821,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
12743
12821
  if (host === "windows") return [];
12744
12822
  const ctx = installContext();
12745
12823
  const systemd = detectSystemd();
12746
- return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) => existsSync29(p));
12824
+ return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) => existsSync28(p));
12747
12825
  };
12748
12826
  return {
12749
12827
  liveRuns: () => liveRuns(stateDir).map((r) => ({ role: r.role, route: r.route, pid: r.pid, ticketRecordId: r.ticketRecordId ?? void 0, streamPath: r.streamPath })),
@@ -12804,7 +12882,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
12804
12882
  },
12805
12883
  remaining() {
12806
12884
  const left = [];
12807
- if (existsSync29(cfg.configFile)) left.push(`config ${cfg.configFile}`);
12885
+ if (existsSync28(cfg.configFile)) left.push(`config ${cfg.configFile}`);
12808
12886
  for (const h of forgeHosts()) if (hasForgeToken(cfg.ship.forges, h, stateDir)) left.push(`forge token for ${h}`);
12809
12887
  try {
12810
12888
  for (const k of readdirSync11(keysDir(stateDir))) left.push(`seat key ${k}`);
@@ -13161,7 +13239,7 @@ function makeCachePass(o) {
13161
13239
  // src/passenger-containers.ts
13162
13240
  var realExec2 = (cmd, args, opts) => {
13163
13241
  try {
13164
- return execFileSync7(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: opts?.timeoutMs });
13242
+ return execFileSync8(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: opts?.timeoutMs });
13165
13243
  } catch (e) {
13166
13244
  const err3 = e;
13167
13245
  const why = [err3.stderr, err3.stdout].map((x) => (x ?? "").trim()).filter(Boolean).join(" / ");
@@ -13384,7 +13462,7 @@ var SANDBOX_BRIDGE_PATH = `${CREW_PREFIX}/bin/intel-bridge`;
13384
13462
  function readBridgeBundle() {
13385
13463
  const here = dirname15(fileURLToPath(import.meta.url));
13386
13464
  for (const p of [join32(here, "sandbox-intel-bridge.cjs"), join32(here, "..", "dist", "sandbox-intel-bridge.cjs")]) {
13387
- if (existsSync30(p)) return readFileSync28(p, "utf8");
13465
+ if (existsSync29(p)) return readFileSync28(p, "utf8");
13388
13466
  }
13389
13467
  return void 0;
13390
13468
  }
@@ -13490,7 +13568,7 @@ var WARM_MAX_BYTES = 64 * 1024 * 1024;
13490
13568
  var globToRegExp = (glob) => new RegExp(`^${glob.split("*").map((p) => p.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*")}$`);
13491
13569
  function readWarmFiles(dir, ref, patterns) {
13492
13570
  if (patterns.length === 0) return {};
13493
- const git2 = (args) => execFileSync8("git", ["-C", dir, ...args], { encoding: "utf8", maxBuffer: WARM_MAX_BYTES, stdio: ["ignore", "pipe", "pipe"] });
13571
+ const git2 = (args) => execFileSync9("git", ["-C", dir, ...args], { encoding: "utf8", maxBuffer: WARM_MAX_BYTES, stdio: ["ignore", "pipe", "pipe"] });
13494
13572
  const all = git2(["ls-tree", "-r", "--name-only", ref]).split("\n").filter(Boolean);
13495
13573
  const res = patterns.map(globToRegExp);
13496
13574
  const out = {};
@@ -13682,10 +13760,10 @@ async function sandboxBuildTick(repos, deps, force = false) {
13682
13760
  }
13683
13761
 
13684
13762
  // src/doctor-checks.ts
13685
- import { execFileSync as execFileSync9, spawnSync as spawnSync8 } from "child_process";
13763
+ import { execFileSync as execFileSync10, spawnSync as spawnSync8 } from "child_process";
13686
13764
  var PROBE_BRANCH = "crew-probe-branch";
13687
13765
  var realGit = (cwd, args, env) => {
13688
- execFileSync9("git", args, { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
13766
+ execFileSync10("git", args, { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
13689
13767
  };
13690
13768
  function isSshRemote(url) {
13691
13769
  return /^ssh:\/\//.test(url) || /^[\w.-]+@[\w.-]+:/.test(url);
@@ -13833,7 +13911,7 @@ function managedGitIdentityWarnings(kind, identity) {
13833
13911
 
13834
13912
  // src/selftest.ts
13835
13913
  import { spawn as spawn6 } from "child_process";
13836
- import { existsSync as existsSync31, mkdirSync as mkdirSync20, mkdtempSync as mkdtempSync2, readFileSync as readFileSync29, rmSync as rmSync16, writeFileSync as writeFileSync21 } from "fs";
13914
+ import { existsSync as existsSync30, mkdirSync as mkdirSync20, mkdtempSync as mkdtempSync2, readFileSync as readFileSync29, rmSync as rmSync16, writeFileSync as writeFileSync21 } from "fs";
13837
13915
  import { tmpdir as tmpdir4 } from "os";
13838
13916
  import { join as join33 } from "path";
13839
13917
  var SELFTEST_TIMEOUT_MS = 12e4;
@@ -14038,7 +14116,7 @@ var fileFor2 = (stateDir) => join33(stateDir, "selftest.json");
14038
14116
  function readSelftest(stateDir) {
14039
14117
  try {
14040
14118
  const p = fileFor2(stateDir);
14041
- return existsSync31(p) ? JSON.parse(readFileSync29(p, "utf8")) : {};
14119
+ return existsSync30(p) ? JSON.parse(readFileSync29(p, "utf8")) : {};
14042
14120
  } catch {
14043
14121
  return {};
14044
14122
  }
@@ -14059,7 +14137,7 @@ function doctorSelftestLines(stored, toolchains) {
14059
14137
  }
14060
14138
 
14061
14139
  // src/cli.ts
14062
- import { resolve as resolve7, dirname as dirname34, join as join56, basename as basename16 } from "path";
14140
+ import { resolve as resolve7, dirname as dirname35, join as join56, basename as basename16 } from "path";
14063
14141
  import { fileURLToPath as fileURLToPath2 } from "url";
14064
14142
  import { hostname as hostname5, homedir as homedir11 } from "os";
14065
14143
  import { spawnSync as spawnSync12 } from "child_process";
@@ -14364,7 +14442,7 @@ async function signOutSessions(o) {
14364
14442
  }
14365
14443
 
14366
14444
  // src/reset.ts
14367
- import { existsSync as existsSync32, mkdirSync as mkdirSync22, readFileSync as readFileSync31, readdirSync as readdirSync12, rmSync as rmSync18, statSync as statSync10, writeFileSync as writeFileSync23 } from "fs";
14445
+ import { existsSync as existsSync31, mkdirSync as mkdirSync22, readFileSync as readFileSync31, readdirSync as readdirSync12, rmSync as rmSync18, statSync as statSync10, writeFileSync as writeFileSync23 } from "fs";
14368
14446
  import { dirname as dirname17, join as join35 } from "path";
14369
14447
  import { hostFromUrl as hostFromUrl3 } from "@tablation/client";
14370
14448
  function clonedCheckoutsFile(stateDir) {
@@ -14437,7 +14515,7 @@ async function runReset(o, fx) {
14437
14515
  }
14438
14516
  if (o.beforeState) steps.push(...await o.beforeState());
14439
14517
  steps.push(...o.wipeState ? wipeStateDir(o.stateDir, o.dryRun) : removeState(o.stateDir, o.dryRun));
14440
- if (existsSync32(o.configFile)) {
14518
+ if (existsSync31(o.configFile)) {
14441
14519
  if (!o.dryRun) rmSync18(o.configFile, { force: true });
14442
14520
  steps.push({ step: "config", target: o.configFile, action: mark(o.dryRun) });
14443
14521
  } else {
@@ -14446,7 +14524,7 @@ async function runReset(o, fx) {
14446
14524
  return steps;
14447
14525
  }
14448
14526
  function removeCheckout(dir, dryRun, fx) {
14449
- if (!existsSync32(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
14527
+ if (!existsSync31(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
14450
14528
  const { dirty, linkedWorktrees } = fx.inspectCheckout(dir);
14451
14529
  if (dirty.length) {
14452
14530
  return { step: "checkout", target: dir, action: "kept", detail: `has uncommitted changes (${dirty.length} path${dirty.length === 1 ? "" : "s"}: ${dirty.slice(0, 5).join(", ")}${dirty.length > 5 ? ", \u2026" : ""})` };
@@ -14467,7 +14545,7 @@ function removeState(stateDir, dryRun) {
14467
14545
  } catch {
14468
14546
  }
14469
14547
  for (const t of targets) {
14470
- if (!existsSync32(t)) continue;
14548
+ if (!existsSync31(t)) continue;
14471
14549
  if (!dryRun) rmSync18(t, { recursive: true, force: true });
14472
14550
  out.push({ step: "state", target: t, action: mark(dryRun) });
14473
14551
  }
@@ -14863,13 +14941,13 @@ function hostArchitecture(arch = process.arch) {
14863
14941
 
14864
14942
  // src/pool-member-host.ts
14865
14943
  import "child_process";
14866
- import { existsSync as existsSync36 } from "fs";
14944
+ import { existsSync as existsSync35 } from "fs";
14867
14945
  import { hostname as hostname3 } from "os";
14868
14946
 
14869
14947
  // src/machine-id.ts
14870
- import { execFileSync as execFileSync10 } from "child_process";
14948
+ import { execFileSync as execFileSync11 } from "child_process";
14871
14949
  import { createHash as createHash8 } from "crypto";
14872
- import { existsSync as existsSync33, mkdirSync as mkdirSync24, readFileSync as readFileSync33, writeFileSync as writeFileSync25 } from "fs";
14950
+ import { existsSync as existsSync32, mkdirSync as mkdirSync24, readFileSync as readFileSync33, writeFileSync as writeFileSync25 } from "fs";
14873
14951
  import { dirname as dirname19, join as join37 } from "path";
14874
14952
  function machineIdPathFor(stateDir) {
14875
14953
  return join37(stateDir, "machine-id");
@@ -14878,14 +14956,14 @@ var sha256 = (s) => createHash8("sha256").update(s).digest("hex");
14878
14956
  function readRawMachineId(platform2 = process.platform) {
14879
14957
  try {
14880
14958
  if (platform2 === "darwin") {
14881
- const out = execFileSync10("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
14959
+ const out = execFileSync11("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
14882
14960
  encoding: "utf8",
14883
14961
  stdio: ["ignore", "pipe", "ignore"]
14884
14962
  });
14885
14963
  return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out)?.[1];
14886
14964
  }
14887
14965
  for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
14888
- if (existsSync33(p)) {
14966
+ if (existsSync32(p)) {
14889
14967
  const id = readFileSync33(p, "utf8").trim();
14890
14968
  if (id) return id;
14891
14969
  }
@@ -14897,7 +14975,7 @@ function readRawMachineId(platform2 = process.platform) {
14897
14975
  function ensureMachineId(stateDir, opts = {}) {
14898
14976
  if (opts.override?.trim()) return sha256(opts.override.trim());
14899
14977
  const path = machineIdPathFor(stateDir);
14900
- if (existsSync33(path)) {
14978
+ if (existsSync32(path)) {
14901
14979
  const cached = readFileSync33(path, "utf8").trim();
14902
14980
  if (/^[0-9a-f]{64}$/.test(cached)) return cached;
14903
14981
  }
@@ -14918,7 +14996,7 @@ function matchShipRows(ships, shipName2, machineId) {
14918
14996
 
14919
14997
  // src/provision.ts
14920
14998
  import { spawnSync as spawnSync9 } from "child_process";
14921
- import { existsSync as existsSync34, readFileSync as readFileSync34, writeFileSync as writeFileSync26 } from "fs";
14999
+ import { existsSync as existsSync33, readFileSync as readFileSync34, writeFileSync as writeFileSync26 } from "fs";
14922
15000
  import { hostname as hostname2 } from "os";
14923
15001
  import { parseDocument as parseDocument3, isMap as isMap2, isScalar } from "yaml";
14924
15002
 
@@ -15535,7 +15613,7 @@ function realProvisionDeps(crewHome) {
15535
15613
  };
15536
15614
  const configFile = () => {
15537
15615
  const f = findConfigFile(crewHome);
15538
- return existsSync34(f) ? f : null;
15616
+ return existsSync33(f) ? f : null;
15539
15617
  };
15540
15618
  return {
15541
15619
  crew: spawnCrew,
@@ -15569,14 +15647,14 @@ function realProvisionDeps(crewHome) {
15569
15647
  continue;
15570
15648
  }
15571
15649
  for (const p of plan.unitPaths) {
15572
- if (!existsSync34(p) || readFileSync34(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
15650
+ if (!existsSync33(p) || readFileSync34(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
15573
15651
  }
15574
15652
  }
15575
15653
  const label = labelFor(crewHome, "run");
15576
15654
  let running = false;
15577
15655
  let lingering = true;
15578
15656
  if (mechanism === "systemd") {
15579
- const system = ctx.root || existsSync34(systemUnitPathFor(label));
15657
+ const system = ctx.root || existsSync33(systemUnitPathFor(label));
15580
15658
  const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
15581
15659
  running = spawnSync9(ctl[0], [...ctl.slice(1), "is-active", "--quiet", `${label}.service`]).status === 0;
15582
15660
  if (!system) {
@@ -15632,7 +15710,7 @@ host=${host}
15632
15710
 
15633
15711
  // src/tunnel.ts
15634
15712
  import { spawn as spawn7 } from "child_process";
15635
- import { existsSync as existsSync35, mkdirSync as mkdirSync25, readFileSync as readFileSync35, writeFileSync as writeFileSync27, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "fs";
15713
+ import { existsSync as existsSync34, mkdirSync as mkdirSync25, readFileSync as readFileSync35, writeFileSync as writeFileSync27, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "fs";
15636
15714
  import { randomInt } from "crypto";
15637
15715
  import { join as join39 } from "path";
15638
15716
  var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
@@ -15716,7 +15794,7 @@ function tunnelStatePath(stateDir, workspaceId) {
15716
15794
  }
15717
15795
  function readPersistedTunnel(stateDir, workspaceId) {
15718
15796
  const path = tunnelStatePath(stateDir, workspaceId);
15719
- if (!existsSync35(path)) return void 0;
15797
+ if (!existsSync34(path)) return void 0;
15720
15798
  try {
15721
15799
  return JSON.parse(readFileSync35(path, "utf8"));
15722
15800
  } catch {
@@ -15856,7 +15934,7 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
15856
15934
  }
15857
15935
  function readAllPersistedWorkspaceIds(stateDir) {
15858
15936
  const dir = join39(stateDir, "passengers");
15859
- if (!existsSync35(dir)) return [];
15937
+ if (!existsSync34(dir)) return [];
15860
15938
  return readdirSync13(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
15861
15939
  }
15862
15940
  var TUNNEL_REJECTED_KEY = "tunnel_rejected";
@@ -15896,7 +15974,7 @@ function realPoolMemberDeps(o) {
15896
15974
  crewVersion: crewVersion(o.crewHome),
15897
15975
  hostname: hostname3()
15898
15976
  },
15899
- hasShipConfig: () => existsSync36(findConfigFile(o.crewHome)),
15977
+ hasShipConfig: () => existsSync35(findConfigFile(o.crewHome)),
15900
15978
  async provision(lease, instanceClass, shipKind) {
15901
15979
  const hosts = lease.forges.map((f) => f.host.toLowerCase());
15902
15980
  const lines2 = [];
@@ -17272,7 +17350,7 @@ async function resolveOne(tracker, t, seat, approvedStatus, buildingStatus, affi
17272
17350
  }
17273
17351
 
17274
17352
  // src/repo-refresh.ts
17275
- import { existsSync as existsSync37, mkdirSync as mkdirSync26, readFileSync as readFileSync36, writeFileSync as writeFileSync29 } from "fs";
17353
+ import { existsSync as existsSync36, mkdirSync as mkdirSync26, readFileSync as readFileSync36, writeFileSync as writeFileSync29 } from "fs";
17276
17354
  import { dirname as dirname21 } from "path";
17277
17355
  var REFRESH_COOLDOWN_MS = 2 * 6e4;
17278
17356
  var UNSERVABLE_PASSES_BEFORE_ATTENTION = 3;
@@ -17302,7 +17380,7 @@ function applyRepoRows(resolved, rows) {
17302
17380
  }
17303
17381
  function persistRepoRows(stateDir, routeName, rows) {
17304
17382
  const path = resolvedPathFor(stateDir, routeName);
17305
- if (!existsSync37(path)) return;
17383
+ if (!existsSync36(path)) return;
17306
17384
  const raw = JSON.parse(readFileSync36(path, "utf8"));
17307
17385
  applyRepoRows(raw, rows);
17308
17386
  mkdirSync26(dirname21(path), { recursive: true });
@@ -17757,12 +17835,12 @@ var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.rec
17757
17835
 
17758
17836
  // src/connect-wizard.ts
17759
17837
  import { delimiter, dirname as dirname22, join as join41 } from "path";
17760
- import { accessSync as accessSync3, constants as constants3, existsSync as existsSync38, mkdirSync as mkdirSync27, writeFileSync as writeFileSync30 } from "fs";
17838
+ import { accessSync as accessSync3, constants as constants3, existsSync as existsSync37, mkdirSync as mkdirSync27, writeFileSync as writeFileSync30 } from "fs";
17761
17839
  import { stringify as stringify3 } from "yaml";
17762
17840
  var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
17763
17841
  function shouldRunWizard(opts) {
17764
17842
  if (!opts.isTTY) return false;
17765
- const exists = opts.configExists ?? existsSync38;
17843
+ const exists = opts.configExists ?? existsSync37;
17766
17844
  return !configSearchPath(opts.crewHome).some((p) => exists(p));
17767
17845
  }
17768
17846
  function firstRunConfigPath() {
@@ -17956,7 +18034,7 @@ function flagWizardAnswers(f, env) {
17956
18034
  }
17957
18035
 
17958
18036
  // src/release-run.ts
17959
- import { readFileSync as readFileSync37, writeFileSync as writeFileSync31, existsSync as existsSync39, copyFileSync, mkdirSync as mkdirSync28, mkdtempSync as mkdtempSync3, rmSync as rmSync19 } from "fs";
18037
+ import { readFileSync as readFileSync37, writeFileSync as writeFileSync31, existsSync as existsSync38, copyFileSync, mkdirSync as mkdirSync28, mkdtempSync as mkdtempSync3, rmSync as rmSync19 } from "fs";
17960
18038
  import { dirname as dirname24, join as join43 } from "path";
17961
18039
  import { tmpdir as tmpdir5, hostname as hostname4 } from "os";
17962
18040
 
@@ -18656,6 +18734,17 @@ function ensureCommitterIdentity(o) {
18656
18734
  function isConflict(message, unmergedPaths) {
18657
18735
  return unmergedPaths.length > 0 || /CONFLICT \(|Automatic merge failed|unmerged (files|paths)/i.test(message);
18658
18736
  }
18737
+ var DEEPEN_STEP = 200;
18738
+ var DEEPEN_ATTEMPTS = 5;
18739
+ function deepenUntilMergeBase(o, branch) {
18740
+ const { remote } = o.repo.branch;
18741
+ for (let i = 0; i < DEEPEN_ATTEMPTS && gitOk(o.cwd, ["merge-base", "HEAD", branch]) === null; i++) {
18742
+ const before = gitOk(o.cwd, ["rev-list", "--count", "--all"]);
18743
+ if (gitOk(o.cwd, ["fetch", "--quiet", `--deepen=${DEEPEN_STEP}`, remote]) === null) return;
18744
+ o.emit.emit(`deepened the release clone to find the merge base of ${branch}`, { step: "merge" });
18745
+ if (gitOk(o.cwd, ["rev-list", "--count", "--all"]) === before) return;
18746
+ }
18747
+ }
18659
18748
  function mergeOne(o, c) {
18660
18749
  const subject = c.entries[0] ?? `${c.ticket.title ?? c.ticket.issue_id} (${c.ticket.issue_id})`;
18661
18750
  if (o.dryRun) {
@@ -18663,6 +18752,7 @@ function mergeOne(o, c) {
18663
18752
  return { applied: true };
18664
18753
  }
18665
18754
  const before = headSha(o.cwd);
18755
+ if (remoteOf(o.cwd)) deepenUntilMergeBase(o, c.branch);
18666
18756
  const clearFailure = () => {
18667
18757
  o.attention?.clear(`release_merge_failed:${o.scope ?? "release"}`);
18668
18758
  };
@@ -18729,11 +18819,11 @@ async function bump(o, size) {
18729
18819
  }
18730
18820
  const files = o.repo.release.versionFiles.map((f) => join43(o.cwd, f));
18731
18821
  const first = files[0];
18732
- if (!existsSync39(first)) {
18822
+ if (!existsAt(first, existsSync38)) {
18733
18823
  o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
18734
18824
  return null;
18735
18825
  }
18736
- const firstText = readFileSync37(first, "utf8");
18826
+ const firstText = readTextAt(first, (p) => readFileSync37(p, "utf8"));
18737
18827
  const problem = versionFileProblem(o.repo.release.versionFiles[0], firstText);
18738
18828
  if (problem) {
18739
18829
  o.emit.error(problem);
@@ -18752,13 +18842,13 @@ async function bump(o, size) {
18752
18842
  return version;
18753
18843
  }
18754
18844
  for (const f of files) {
18755
- const src = readFileSync37(f, "utf8");
18845
+ const src = readTextAt(f, (p) => readFileSync37(p, "utf8"));
18756
18846
  const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
18757
18847
  if (out === src) {
18758
18848
  o.emit.error(`no version field in ${f}`);
18759
18849
  return null;
18760
18850
  }
18761
- writeFileSync31(f, out);
18851
+ writeTextAt(f, out, writeFileSync31);
18762
18852
  }
18763
18853
  return version;
18764
18854
  }
@@ -18771,8 +18861,8 @@ function writeChangelog(o, version, entries) {
18771
18861
  o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
18772
18862
  return;
18773
18863
  }
18774
- const existing = existsSync39(path) ? readFileSync37(path, "utf8") : "# Changelog\n";
18775
- writeFileSync31(path, insertChangelogSection(existing, section));
18864
+ const existing = existsAt(path, existsSync38) ? readTextAt(path, (p) => readFileSync37(p, "utf8")) : "# Changelog\n";
18865
+ writeTextAt(path, insertChangelogSection(existing, section), writeFileSync31);
18776
18866
  }
18777
18867
  async function confirm(o, expected) {
18778
18868
  if (!o.repo.hooks.released) return void 0;
@@ -18877,7 +18967,7 @@ function openReleaseCheckout(o) {
18877
18967
  }
18878
18968
  for (const f of o.repo.worktrees.copy) {
18879
18969
  const src = join43(o.cwd, f);
18880
- if (!existsSync39(src)) continue;
18970
+ if (!existsSync38(src)) continue;
18881
18971
  mkdirSync28(dirname24(join43(dir, f)), { recursive: true });
18882
18972
  copyFileSync(src, join43(dir, f));
18883
18973
  }
@@ -19017,6 +19107,12 @@ async function runReleaseCycle(o, movedPast2, localRefSkips) {
19017
19107
  if (inPlace) {
19018
19108
  return mergeAndRelease(o, decision, fresh);
19019
19109
  }
19110
+ if (remoteOf(o.cwd)) {
19111
+ const releaseBranch = `crew/release-${Date.now()}`;
19112
+ git(o.cwd, ["checkout", "-q", "--no-track", "-b", releaseBranch, remoteRef]);
19113
+ o.emit.emit("release checkout: the release sandbox clone", { step: "release" });
19114
+ return mergeAndRelease(o, decision, fresh, releaseBranch);
19115
+ }
19020
19116
  const primary = checkGuards(o.cwd, base);
19021
19117
  if (primary) {
19022
19118
  o.emit.emit(scoped(`primary checkout not fast-forwarded: ${primary.detail}; its dist/ is stale until rebuilt`), { step: "release" });
@@ -19708,7 +19804,7 @@ function runForge(a, io) {
19708
19804
  }
19709
19805
 
19710
19806
  // src/mirror-contract.ts
19711
- import { existsSync as existsSync40 } from "fs";
19807
+ import { existsSync as existsSync39 } from "fs";
19712
19808
 
19713
19809
  // src/passenger-mirror.ts
19714
19810
  import { mkdirSync as mkdirSync29 } from "fs";
@@ -19749,11 +19845,15 @@ function mirrorOutcome(m) {
19749
19845
 
19750
19846
  // src/mirror-contract.ts
19751
19847
  function lacksCheckout(ship, dir) {
19752
- return ship.runner === "sandbox" && !existsSync40(dir);
19848
+ return ship.runner === "sandbox" && !existsSync39(dir) && !remoteOf(dir);
19753
19849
  }
19754
19850
  function mirrorRepoOfTarget(t) {
19755
19851
  return mirrorRepoOf({ remote: t.remote, forgeHost: t.forgeHost });
19756
19852
  }
19853
+ function cacheRepoOfTarget(ship, t, origin) {
19854
+ if (origin) return parseRemote(origin);
19855
+ return lacksCheckout(ship, t.dir) ? mirrorRepoOfTarget(t) : null;
19856
+ }
19757
19857
  async function readMirrorContract(cache, repo, base) {
19758
19858
  for (const name of REPO_CONFIG_FILENAMES) {
19759
19859
  const text2 = await cache.showFile(repo, base, name);
@@ -19762,10 +19862,13 @@ async function readMirrorContract(cache, repo, base) {
19762
19862
  return null;
19763
19863
  }
19764
19864
  function noCheckoutReleaseNote(repo, mode) {
19765
- if (mode === "local" || mode === null) {
19766
- return `${repo}: no checkout on this ship; release.mode "local" repositories are released by the host that owns the deploy hook, which takes a ticket at verified`;
19865
+ if (mode === "external") {
19866
+ return `${repo}: no checkout on this ship; release.mode "external" repositories are handed off by a ship with a checkout`;
19767
19867
  }
19768
- return `${repo}: no checkout on this ship; release.mode "${mode}" needs the sandboxed release over a mirror-fed clone, which is not built (CREW-2940)`;
19868
+ return `${repo}: no checkout on this ship; release.mode "local" repositories are released by the host that owns the deploy hook, which takes a ticket at verified`;
19869
+ }
19870
+ function releasesFromSandbox(mode) {
19871
+ return mode === "integrate" || mode === "ci_auto" || mode === "ci_manual";
19769
19872
  }
19770
19873
  var reported = /* @__PURE__ */ new Set();
19771
19874
  function firstReport(key) {
@@ -19774,6 +19877,144 @@ function firstReport(key) {
19774
19877
  return true;
19775
19878
  }
19776
19879
 
19880
+ // src/release-sandbox.ts
19881
+ import { execFile as execFile3 } from "child_process";
19882
+ import { mkdirSync as mkdirSync30, writeFileSync as writeFileSync32 } from "fs";
19883
+ import { dirname as dirname26 } from "path";
19884
+ import { promisify as promisify3 } from "util";
19885
+ var execFileP3 = promisify3(execFile3);
19886
+ var RELEASE_CLONE_DEPTH = 50;
19887
+ var FORGE_TOKEN_VAR = "CREW_FORGE_TOKEN";
19888
+ var FORGE_USER_VAR = "CREW_FORGE_USER";
19889
+ var CREDENTIAL_HELPER2 = `!f() { echo username=\${${FORGE_USER_VAR}:-x-access-token}; echo password=$${FORGE_TOKEN_VAR}; }; f`;
19890
+ function releaseCloneScript(o) {
19891
+ const mirror = `${o.mirrorDir}/${mirrorRelPath(o.repo)}`;
19892
+ const depth = o.depth ?? RELEASE_CLONE_DEPTH;
19893
+ const branch = shQuote2(o.base);
19894
+ return [
19895
+ "set -e",
19896
+ "export GIT_TERMINAL_PROMPT=0",
19897
+ // The mirror is written by a helper container as root and mounted read-only, so git sees it as someone else's.
19898
+ `git config --global --add safe.directory '*'`,
19899
+ `git config --global credential.helper ${shQuote2(CREDENTIAL_HELPER2)}`,
19900
+ `m=${shQuote2(mirror)}`,
19901
+ `depth=${depth}`,
19902
+ `t=$(git -C "$m" describe --tags --abbrev=0 --match ${shQuote2(o.tagPattern)} ${shQuote2(`refs/heads/${o.base}`)} 2>/dev/null || true)`,
19903
+ `if [ -n "$t" ]; then n=$(git -C "$m" rev-list --count "$t..refs/heads/"${branch}); [ $((n + 20)) -gt "$depth" ] && depth=$((n + 20)); fi`,
19904
+ `git clone --quiet --no-tags --depth "$depth" --single-branch --branch ${branch} "file://$m" ${shQuote2(o.workDir)}`,
19905
+ `cd ${shQuote2(o.workDir)}`,
19906
+ `git remote set-url origin ${shQuote2(o.origin)}`,
19907
+ `git config crew.releaseDepth "$depth"`,
19908
+ // A tag already in the window: only its (annotated) tag object is new.
19909
+ `if [ -n "$t" ]; then git fetch --quiet --no-tags "file://$m" "refs/tags/$t:refs/tags/$t" || true; fi`,
19910
+ ...o.branches.flatMap((b) => {
19911
+ const spec = shQuote2(`+refs/heads/${b}:refs/remotes/origin/${b}`);
19912
+ return [
19913
+ `git config --add remote.origin.fetch ${spec}`,
19914
+ `git fetch --quiet --no-tags --depth "$depth" "file://$m" ${spec} 2>/dev/null || true`
19915
+ ];
19916
+ })
19917
+ ].join("\n");
19918
+ }
19919
+ var endpointsOf = (g) => g.groups.flatMap((x) => x.endpoints.map((e) => `${e.host}:${e.port}`));
19920
+ async function openReleaseSandbox(o) {
19921
+ const cfg = { ...DEFAULT_SANDBOX_CONFIG, ...o.sandbox };
19922
+ const run3 = o.exec ?? ((bin, args, env) => execFileP3(bin, args, { env, maxBuffer: 16 * 1024 * 1024 }));
19923
+ const name = sandboxName({ route: o.route, role: "release", ticket: o.repoName }, o.now?.());
19924
+ let policyFile = cfg.policy;
19925
+ let generated;
19926
+ if (!policyFile) {
19927
+ generated = generateSandboxPolicy({ mirrorDir: cfg.mirrorDir, workDir: cfg.workDir, ...o.policy });
19928
+ policyFile = o.policyFile;
19929
+ mkdirSync30(dirname26(policyFile), { recursive: true });
19930
+ writeFileSync32(policyFile, generated.yaml, { mode: 384 });
19931
+ }
19932
+ const secrets = { ...process.env };
19933
+ const secretKeys = [];
19934
+ if (o.token) {
19935
+ secrets[FORGE_TOKEN_VAR] = o.token;
19936
+ secrets[FORGE_USER_VAR] = o.username ?? "x-access-token";
19937
+ secretKeys.push(FORGE_TOKEN_VAR, FORGE_USER_VAR);
19938
+ }
19939
+ const create2 = ["sandbox", "create", "--name", name, "--detach"];
19940
+ if (policyFile) create2.push("--policy", policyFile);
19941
+ const image = cfg.image ?? o.image;
19942
+ if (image) create2.push("--from", image);
19943
+ if (cfg.cacheVolume) {
19944
+ create2.push("--driver-config-json", JSON.stringify({
19945
+ docker: { mounts: [{ type: "volume", source: cfg.cacheVolume, target: cfg.mirrorDir, read_only: true }] }
19946
+ }));
19947
+ }
19948
+ for (const k of secretKeys) create2.push("--env-from", k);
19949
+ create2.push("--", "sleep", String(3 * 3600));
19950
+ const t0 = Date.now();
19951
+ const deleteQuietly = async () => {
19952
+ try {
19953
+ await run3(cfg.bin, ["sandbox", "delete", name], process.env);
19954
+ } catch {
19955
+ }
19956
+ };
19957
+ try {
19958
+ await run3(cfg.bin, create2, secrets);
19959
+ } catch (e) {
19960
+ await deleteQuietly();
19961
+ throw new Error(`openshell could not create the release sandbox: ${describe(e)}`);
19962
+ }
19963
+ o.emit.emit(`sandbox_created: ${name}`, { role: "release", data: { event: "sandbox_created", sandbox: name, role: "release", repo: o.repoName, ms: Date.now() - t0 } });
19964
+ if (generated) {
19965
+ o.emit.emit(`sandbox_policy: ${endpointsOf(generated).length} allowed endpoint(s)`, {
19966
+ role: "release",
19967
+ data: { event: "sandbox_policy", sandbox: name, file: policyFile, allowed: endpointsOf(generated), declared: generated.extraHosts }
19968
+ });
19969
+ }
19970
+ const closeSandbox = async () => {
19971
+ try {
19972
+ const { stdout } = await run3(cfg.bin, ["logs", name], process.env);
19973
+ for (const d of parseDeniedRequests(stdout)) {
19974
+ const where = `${d.host}${d.port !== void 0 ? `:${d.port}` : ""}${d.binary ? ` by ${d.binary}` : ""}`;
19975
+ o.emit.warn(`sandbox_denied: ${where}${d.count > 1 ? ` (${d.count}x)` : ""} \u2014 blocked by the sandbox policy`, { data: { event: "sandbox_denied", sandbox: name, ...d } });
19976
+ }
19977
+ } catch {
19978
+ }
19979
+ const t1 = Date.now();
19980
+ await deleteQuietly();
19981
+ o.emit.emit(`sandbox_deleted: ${name}`, { role: "release", data: { event: "sandbox_deleted", sandbox: name, ms: Date.now() - t1 } });
19982
+ };
19983
+ try {
19984
+ const clone = `${releaseCloneScript({
19985
+ mirrorDir: cfg.mirrorDir,
19986
+ repo: o.repo,
19987
+ workDir: cfg.workDir,
19988
+ origin: o.origin,
19989
+ base: o.base,
19990
+ branches: o.branches,
19991
+ tagPattern: o.tagPattern
19992
+ })}
19993
+ ${containerEngineStubScript()}`;
19994
+ await run3(cfg.bin, ["sandbox", "exec", "-n", name, "--no-login-shell", "--", "sh", "-c", clone], process.env);
19995
+ } catch (e) {
19996
+ await closeSandbox();
19997
+ throw new Error(`could not make the release clone in ${name}: ${describe(e)}`);
19998
+ }
19999
+ const dir = registerRemoteDir({
20000
+ bin: cfg.bin,
20001
+ sandbox: name,
20002
+ workDir: cfg.workDir,
20003
+ prelude: `${sandboxEnvExports()}PATH=${shQuote2(SANDBOX_STUB_DIR)}:"$PATH"; export PATH; `
20004
+ });
20005
+ let closed = false;
20006
+ return {
20007
+ dir,
20008
+ sandbox: name,
20009
+ async close() {
20010
+ if (closed) return;
20011
+ closed = true;
20012
+ unregisterRemoteDir(dir);
20013
+ await closeSandbox();
20014
+ }
20015
+ };
20016
+ }
20017
+
19777
20018
  // src/whoami.ts
19778
20019
  async function buildWhoami(routes, deps = {}) {
19779
20020
  const doFetch = deps.fetch ?? fetch;
@@ -19851,7 +20092,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
19851
20092
  }
19852
20093
 
19853
20094
  // src/watch.ts
19854
- import { watch as watchFile, existsSync as existsSync41, statSync as statSync11, openSync as openSync5, readSync as readSync2, closeSync as closeSync5 } from "fs";
20095
+ import { watch as watchFile, existsSync as existsSync40, statSync as statSync11, openSync as openSync5, readSync as readSync2, closeSync as closeSync5 } from "fs";
19855
20096
  var E = "\x1B[";
19856
20097
  var dim = (s) => `${E}2m${s}${E}0m`;
19857
20098
  var bold = (s) => `${E}1m${s}${E}0m`;
@@ -19952,7 +20193,7 @@ function renderHeaders(states, now = Date.now()) {
19952
20193
  return states.map((s) => renderHeader(s, now)).join("");
19953
20194
  }
19954
20195
  function readFrom(file, from) {
19955
- if (!existsSync41(file)) return { events: [], offset: from };
20196
+ if (!existsSync40(file)) return { events: [], offset: from };
19956
20197
  const size = statSync11(file).size;
19957
20198
  if (size < from) return readFrom(file, 0);
19958
20199
  if (size === from) return { events: [], offset: from };
@@ -20195,8 +20436,8 @@ function nextRoles(decision, ran) {
20195
20436
  }
20196
20437
 
20197
20438
  // src/ship-name.ts
20198
- import { existsSync as existsSync42, mkdirSync as mkdirSync30, readFileSync as readFileSync38, renameSync as renameSync5, writeFileSync as writeFileSync32 } from "fs";
20199
- import { dirname as dirname26 } from "path";
20439
+ import { existsSync as existsSync41, mkdirSync as mkdirSync31, readFileSync as readFileSync38, renameSync as renameSync5, writeFileSync as writeFileSync33 } from "fs";
20440
+ import { dirname as dirname27 } from "path";
20200
20441
  import { parseDocument as parseDocument5 } from "yaml";
20201
20442
  var ShipNameError = class extends Error {
20202
20443
  };
@@ -20257,7 +20498,7 @@ async function syncRouteName(i) {
20257
20498
  return { status: "ok", ...base, action, name: i.local };
20258
20499
  }
20259
20500
  function readSyncedName(resolvedFile) {
20260
- if (!existsSync42(resolvedFile)) return void 0;
20501
+ if (!existsSync41(resolvedFile)) return void 0;
20261
20502
  try {
20262
20503
  const v = JSON.parse(readFileSync38(resolvedFile, "utf8")).shipName;
20263
20504
  return typeof v === "string" ? v : void 0;
@@ -20266,7 +20507,7 @@ function readSyncedName(resolvedFile) {
20266
20507
  }
20267
20508
  }
20268
20509
  function writeSyncedName(resolvedFile, name) {
20269
- if (!existsSync42(resolvedFile)) return;
20510
+ if (!existsSync41(resolvedFile)) return;
20270
20511
  let raw;
20271
20512
  try {
20272
20513
  raw = JSON.parse(readFileSync38(resolvedFile, "utf8"));
@@ -20275,9 +20516,9 @@ function writeSyncedName(resolvedFile, name) {
20275
20516
  }
20276
20517
  if (raw.shipName === name) return;
20277
20518
  raw.shipName = name;
20278
- mkdirSync30(dirname26(resolvedFile), { recursive: true });
20519
+ mkdirSync31(dirname27(resolvedFile), { recursive: true });
20279
20520
  const tmp = `${resolvedFile}.tmp${process.pid}`;
20280
- writeFileSync32(tmp, `${JSON.stringify(raw, null, 2)}
20521
+ writeFileSync33(tmp, `${JSON.stringify(raw, null, 2)}
20281
20522
  `);
20282
20523
  renameSync5(tmp, resolvedFile);
20283
20524
  }
@@ -20309,7 +20550,7 @@ async function reconcileShipName(i) {
20309
20550
  if (result.status !== "ok") return result;
20310
20551
  if (result.action === "adopt" || result.action === "row-wins") {
20311
20552
  const text2 = readFileSync38(i.configFile, "utf8");
20312
- writeFileSync32(i.configFile, setShipNameInYaml(text2, result.name));
20553
+ writeFileSync33(i.configFile, setShipNameInYaml(text2, result.name));
20313
20554
  i.ship.name = result.name;
20314
20555
  if (result.action === "row-wins") {
20315
20556
  i.emit?.warn(
@@ -20968,8 +21209,8 @@ function describeCapability(i) {
20968
21209
  }
20969
21210
 
20970
21211
  // src/agents-sync-run.ts
20971
- import { existsSync as existsSync43, mkdirSync as mkdirSync31, readFileSync as readFileSync39, writeFileSync as writeFileSync33 } from "fs";
20972
- import { dirname as dirname27 } from "path";
21212
+ import { existsSync as existsSync42, mkdirSync as mkdirSync32, readFileSync as readFileSync39, writeFileSync as writeFileSync34 } from "fs";
21213
+ import { dirname as dirname28 } from "path";
20973
21214
  async function runAgentsSync(target, o) {
20974
21215
  const dryRun = o.dryRun ?? false;
20975
21216
  const force = o.force ?? false;
@@ -21002,12 +21243,12 @@ async function runAgentsSync(target, o) {
21002
21243
  for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
21003
21244
  if (!dryRun) {
21004
21245
  const resolvedPath = resolvedPathFor(o.stateDir, target.route);
21005
- const raw = existsSync43(resolvedPath) ? JSON.parse(readFileSync39(resolvedPath, "utf8")) : {};
21246
+ const raw = existsSync42(resolvedPath) ? JSON.parse(readFileSync39(resolvedPath, "utf8")) : {};
21006
21247
  raw.agentPersonas = personaResult.agentPersonas;
21007
21248
  if (skillResult) raw.agentSkills = skillResult.agentSkills;
21008
21249
  if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
21009
- mkdirSync31(dirname27(resolvedPath), { recursive: true });
21010
- writeFileSync33(resolvedPath, `${JSON.stringify(raw, null, 2)}
21250
+ mkdirSync32(dirname28(resolvedPath), { recursive: true });
21251
+ writeFileSync34(resolvedPath, `${JSON.stringify(raw, null, 2)}
21011
21252
  `);
21012
21253
  if (target.resolved) {
21013
21254
  target.resolved.agentPersonas = personaResult.agentPersonas;
@@ -21461,7 +21702,7 @@ function existingRepoConfig(f) {
21461
21702
  }
21462
21703
 
21463
21704
  // src/repos-hooks-agent.ts
21464
- import { spawn as spawn8, execFileSync as execFileSync11 } from "child_process";
21705
+ import { spawn as spawn8, execFileSync as execFileSync12 } from "child_process";
21465
21706
  import { realpathSync as realpathSync2 } from "fs";
21466
21707
  import { isAbsolute as isAbsolute3, relative } from "path";
21467
21708
  var AgentSuggestError = class extends Error {
@@ -21564,7 +21805,7 @@ function classifyFailure(o) {
21564
21805
  }
21565
21806
  function porcelain(dir) {
21566
21807
  try {
21567
- return execFileSync11("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: dir, encoding: "utf8" });
21808
+ return execFileSync12("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: dir, encoding: "utf8" });
21568
21809
  } catch {
21569
21810
  return void 0;
21570
21811
  }
@@ -21633,7 +21874,7 @@ ${run3.err}` });
21633
21874
  }
21634
21875
 
21635
21876
  // src/sample-ticket.ts
21636
- import { basename as basename12, dirname as dirname28, join as join44 } from "path";
21877
+ import { basename as basename12, dirname as dirname29, join as join44 } from "path";
21637
21878
  var SAMPLE_REPO = "crew-sample";
21638
21879
  var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
21639
21880
  var SAMPLE_MARKER = "sample: true";
@@ -21656,7 +21897,7 @@ has something safe to work on: its test and build hooks always pass, it has no
21656
21897
  remote, and \`crew sample-ticket --remove\` deletes it.
21657
21898
  `;
21658
21899
  function sampleDir(existing, reposBase, workspace) {
21659
- if (existing.length > 0) return join44(dirname28(existing[0]), SAMPLE_REPO);
21900
+ if (existing.length > 0) return join44(dirname29(existing[0]), SAMPLE_REPO);
21660
21901
  return join44(reposBase, workspace, SAMPLE_REPO);
21661
21902
  }
21662
21903
  var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
@@ -21701,8 +21942,8 @@ function planSampleTicket(i) {
21701
21942
  }
21702
21943
 
21703
21944
  // src/worktree-sweep.ts
21704
- import { existsSync as existsSync44, readFileSync as readFileSync41, readdirSync as readdirSync14 } from "fs";
21705
- import { basename as basename13, dirname as dirname29, join as join45, resolve as resolve5 } from "path";
21945
+ import { existsSync as existsSync43, readFileSync as readFileSync41, readdirSync as readdirSync14 } from "fs";
21946
+ import { basename as basename13, dirname as dirname30, join as join45, resolve as resolve5 } from "path";
21706
21947
  function planWorktreeSweep(target, tickets, contract) {
21707
21948
  const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
21708
21949
  const verified = contract.statuses.verified;
@@ -21766,7 +22007,7 @@ function worktreeTicketNumbers(target) {
21766
22007
  return [...nums];
21767
22008
  }
21768
22009
  function siblingWorktreeDirs(target) {
21769
- const parent = dirname29(target.dir.replace(/[/\\]+$/, ""));
22010
+ const parent = dirname30(target.dir.replace(/[/\\]+$/, ""));
21770
22011
  const stem = `${basename13(target.dir.replace(/[/\\]+$/, ""))}-`;
21771
22012
  const registered = new Set(worktrees(target.dir).map((w) => resolve5(w.path)));
21772
22013
  let names;
@@ -21814,7 +22055,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
21814
22055
  let removed = 0;
21815
22056
  let keptBranches = 0;
21816
22057
  for (const a of actions) {
21817
- if (!dryRun && base && !a.keepBranch && existsSync44(a.path) && isHandBackMerge(a.path, base)) {
22058
+ if (!dryRun && base && !a.keepBranch && existsSync43(a.path) && isHandBackMerge(a.path, base)) {
21818
22059
  if (gitOk(a.path, ["merge", "--abort"]) !== null) {
21819
22060
  log.emit(
21820
22061
  `abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
@@ -21822,7 +22063,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
21822
22063
  );
21823
22064
  }
21824
22065
  }
21825
- if (!dryRun && existsSync44(a.path)) {
22066
+ if (!dryRun && existsSync43(a.path)) {
21826
22067
  let dirty = false;
21827
22068
  try {
21828
22069
  dirty = status(a.path).length > 0;
@@ -21833,7 +22074,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
21833
22074
  continue;
21834
22075
  }
21835
22076
  }
21836
- if (!dryRun && a.requirePushed && a.branch && existsSync44(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
22077
+ if (!dryRun && a.requirePushed && a.branch && existsSync43(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
21837
22078
  refuse2(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
21838
22079
  continue;
21839
22080
  }
@@ -21987,12 +22228,12 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
21987
22228
  }
21988
22229
 
21989
22230
  // src/stream-sweep.ts
21990
- import { existsSync as existsSync45, readdirSync as readdirSync15, rmSync as rmSync20, statSync as statSync12 } from "fs";
22231
+ import { existsSync as existsSync44, readdirSync as readdirSync15, rmSync as rmSync20, statSync as statSync12 } from "fs";
21991
22232
  import { join as join46 } from "path";
21992
22233
  var DAY_MS2 = 24 * 60 * 60 * 1e3;
21993
22234
  function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
21994
22235
  const dir = join46(stateDir, "streams");
21995
- if (!existsSync45(dir)) return [];
22236
+ if (!existsSync44(dir)) return [];
21996
22237
  const cutoff = now - retentionDays * DAY_MS2;
21997
22238
  return readdirSync15(dir).map((name) => join46(dir, name)).filter((path) => {
21998
22239
  try {
@@ -22020,7 +22261,7 @@ function applyStreamSweep(paths, dryRun, log) {
22020
22261
  return removed;
22021
22262
  }
22022
22263
  function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
22023
- if (!existsSync45(stateDir)) return [];
22264
+ if (!existsSync44(stateDir)) return [];
22024
22265
  const cutoff = now - retentionDays * DAY_MS2;
22025
22266
  return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join46(stateDir, name)).filter((path) => {
22026
22267
  try {
@@ -22032,11 +22273,11 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
22032
22273
  }
22033
22274
 
22034
22275
  // src/cli.ts
22035
- import { realpathSync as realpathSync3, readFileSync as readFileSync47, existsSync as existsSync52, mkdirSync as mkdirSync38, writeFileSync as writeFileSync38, rmSync as rmSync25, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync6, statSync as statSync16, readdirSync as readdirSync20 } from "fs";
22276
+ import { realpathSync as realpathSync3, readFileSync as readFileSync47, existsSync as existsSync51, mkdirSync as mkdirSync39, writeFileSync as writeFileSync39, rmSync as rmSync25, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync6, statSync as statSync16, readdirSync as readdirSync20 } from "fs";
22036
22277
  import { createInterface } from "readline/promises";
22037
22278
 
22038
22279
  // src/periscope-cmd.ts
22039
- import { existsSync as existsSync46, mkdirSync as mkdirSync32, readFileSync as readFileSync42, rmSync as rmSync21, writeFileSync as writeFileSync34 } from "fs";
22280
+ import { existsSync as existsSync45, mkdirSync as mkdirSync33, readFileSync as readFileSync42, rmSync as rmSync21, writeFileSync as writeFileSync35 } from "fs";
22040
22281
  import { join as join47 } from "path";
22041
22282
  var PeriscopeError = class extends Error {
22042
22283
  };
@@ -22054,15 +22295,15 @@ function removePeriscopePrior(stateDir, ticketRecordId) {
22054
22295
  }
22055
22296
  function writePeriscopePrior(stateDir, ticketRecordId, prior) {
22056
22297
  try {
22057
- mkdirSync32(priorDir(stateDir), { recursive: true });
22058
- writeFileSync34(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
22298
+ mkdirSync33(priorDir(stateDir), { recursive: true });
22299
+ writeFileSync35(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
22059
22300
  } catch {
22060
22301
  }
22061
22302
  }
22062
22303
  function readPeriscopePrior(stateDir, ticketRecordId) {
22063
22304
  try {
22064
22305
  const p = priorPath(stateDir, ticketRecordId);
22065
- return existsSync46(p) ? JSON.parse(readFileSync42(p, "utf8")) : void 0;
22306
+ return existsSync45(p) ? JSON.parse(readFileSync42(p, "utf8")) : void 0;
22066
22307
  } catch {
22067
22308
  return void 0;
22068
22309
  }
@@ -22514,7 +22755,7 @@ ${tail2(preview.output)}`);
22514
22755
  }
22515
22756
 
22516
22757
  // src/periscope-idle.ts
22517
- import { execFileSync as execFileSync12 } from "child_process";
22758
+ import { execFileSync as execFileSync13 } from "child_process";
22518
22759
  import { statSync as statSync13 } from "fs";
22519
22760
  import { join as join48 } from "path";
22520
22761
  var MAX_SCANNED_FILES = 2e4;
@@ -22526,7 +22767,7 @@ function newestWorktreeMtime(root) {
22526
22767
  function newestTrackedMtime(root) {
22527
22768
  let listing;
22528
22769
  try {
22529
- listing = execFileSync12("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
22770
+ listing = execFileSync13("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
22530
22771
  cwd: root,
22531
22772
  encoding: "utf8",
22532
22773
  stdio: ["ignore", "pipe", "ignore"],
@@ -22617,9 +22858,9 @@ async function runPeriscopeIdlePass(d) {
22617
22858
  import { dirname as dirOf2, resolve as resolvePath } from "path";
22618
22859
 
22619
22860
  // src/ssh-keys.ts
22620
- import { execFileSync as execFileSync13 } from "child_process";
22621
- import { existsSync as existsSync47, mkdirSync as mkdirSync33, readFileSync as readFileSync43, chmodSync as chmodSync3 } from "fs";
22622
- import { dirname as dirname30, join as join49 } from "path";
22861
+ import { execFileSync as execFileSync14 } from "child_process";
22862
+ import { existsSync as existsSync46, mkdirSync as mkdirSync34, readFileSync as readFileSync43, chmodSync as chmodSync3 } from "fs";
22863
+ import { dirname as dirname31, join as join49 } from "path";
22623
22864
  var SshKeyError = class extends Error {
22624
22865
  };
22625
22866
  function shipSshKeyPathFor(stateDir) {
@@ -22628,10 +22869,10 @@ function shipSshKeyPathFor(stateDir) {
22628
22869
  function ensureShipSshKeypair(stateDir) {
22629
22870
  const privateKeyPath = shipSshKeyPathFor(stateDir);
22630
22871
  const publicKeyPath = `${privateKeyPath}.pub`;
22631
- if (!existsSync47(privateKeyPath)) {
22632
- mkdirSync33(dirname30(privateKeyPath), { recursive: true });
22872
+ if (!existsSync46(privateKeyPath)) {
22873
+ mkdirSync34(dirname31(privateKeyPath), { recursive: true });
22633
22874
  try {
22634
- execFileSync13("ssh-keygen", [
22875
+ execFileSync14("ssh-keygen", [
22635
22876
  "-t",
22636
22877
  "ed25519",
22637
22878
  "-N",
@@ -22649,7 +22890,7 @@ function ensureShipSshKeypair(stateDir) {
22649
22890
  }
22650
22891
  chmodSync3(privateKeyPath, 384);
22651
22892
  }
22652
- if (!existsSync47(publicKeyPath)) {
22893
+ if (!existsSync46(publicKeyPath)) {
22653
22894
  throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
22654
22895
  }
22655
22896
  const publicKey = readFileSync43(publicKeyPath, "utf8").trim();
@@ -22657,7 +22898,7 @@ function ensureShipSshKeypair(stateDir) {
22657
22898
  }
22658
22899
  function sshKeygenAvailable() {
22659
22900
  try {
22660
- execFileSync13("ssh-keygen", ["-V"], { stdio: ["ignore", "ignore", "ignore"] });
22901
+ execFileSync14("ssh-keygen", ["-V"], { stdio: ["ignore", "ignore", "ignore"] });
22661
22902
  return true;
22662
22903
  } catch (e) {
22663
22904
  return e.code !== "ENOENT";
@@ -22816,7 +23057,7 @@ async function unregisterEndpoint(workspaceId, info, d) {
22816
23057
  import { createServer as createHttpServer } from "http";
22817
23058
  import { spawn as spawn9 } from "child_process";
22818
23059
  import { randomBytes } from "crypto";
22819
- import { existsSync as existsSync48, mkdirSync as mkdirSync34, readFileSync as readFileSync44, writeFileSync as writeFileSync35, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
23060
+ import { existsSync as existsSync47, mkdirSync as mkdirSync35, readFileSync as readFileSync44, writeFileSync as writeFileSync36, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
22820
23061
  import { join as join50 } from "path";
22821
23062
 
22822
23063
  // src/secret-compare.ts
@@ -22914,7 +23155,7 @@ function syncDaemonStatePath(stateDir, workspaceId) {
22914
23155
  }
22915
23156
  function readPersistedSyncDaemon(stateDir, workspaceId) {
22916
23157
  const path = syncDaemonStatePath(stateDir, workspaceId);
22917
- if (!existsSync48(path)) return void 0;
23158
+ if (!existsSync47(path)) return void 0;
22918
23159
  try {
22919
23160
  return JSON.parse(readFileSync44(path, "utf8"));
22920
23161
  } catch {
@@ -22923,8 +23164,8 @@ function readPersistedSyncDaemon(stateDir, workspaceId) {
22923
23164
  }
22924
23165
  function writePersistedSyncDaemon(stateDir, state) {
22925
23166
  const path = syncDaemonStatePath(stateDir, state.workspaceId);
22926
- mkdirSync34(join50(stateDir, "passengers"), { recursive: true });
22927
- writeFileSync35(path, `${JSON.stringify(state, null, 2)}
23167
+ mkdirSync35(join50(stateDir, "passengers"), { recursive: true });
23168
+ writeFileSync36(path, `${JSON.stringify(state, null, 2)}
22928
23169
  `);
22929
23170
  }
22930
23171
  function removePersistedSyncDaemon(stateDir, workspaceId) {
@@ -22945,7 +23186,7 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
22945
23186
  }
22946
23187
  function readAllPersistedSyncWorkspaceIds(stateDir) {
22947
23188
  const dir = join50(stateDir, "passengers");
22948
- if (!existsSync48(dir)) return [];
23189
+ if (!existsSync47(dir)) return [];
22949
23190
  const suffix = "-sync.json";
22950
23191
  return readdirSync16(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
22951
23192
  }
@@ -23003,12 +23244,12 @@ function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
23003
23244
 
23004
23245
  // src/periscope-proxy.ts
23005
23246
  import { spawn as spawn10 } from "child_process";
23006
- import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync35 } from "fs";
23247
+ import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync36 } from "fs";
23007
23248
  import { createServer, request as httpRequest } from "http";
23008
23249
  import { request as httpsRequest } from "https";
23009
23250
  import { connect as netConnect } from "net";
23010
23251
  import { connect as tlsConnect } from "tls";
23011
- import { dirname as dirname31, isAbsolute as isAbsolute4 } from "path";
23252
+ import { dirname as dirname32, isAbsolute as isAbsolute4 } from "path";
23012
23253
  import { brotliDecompressSync, gunzipSync, inflateSync } from "zlib";
23013
23254
 
23014
23255
  // src/periscope-inspector.ts
@@ -23122,7 +23363,7 @@ function parsePointedBody(raw) {
23122
23363
  return entry;
23123
23364
  }
23124
23365
  function appendPointed(file, entry, now = /* @__PURE__ */ new Date()) {
23125
- mkdirSync35(dirname31(file), { recursive: true });
23366
+ mkdirSync36(dirname32(file), { recursive: true });
23126
23367
  appendFileSync3(file, `${JSON.stringify({ ...entry, at: now.toISOString() })}
23127
23368
  `);
23128
23369
  }
@@ -23465,7 +23706,7 @@ function contractFixers(dir, baseRef, tickets, locate) {
23465
23706
  }
23466
23707
 
23467
23708
  // src/daemon.ts
23468
- import { existsSync as existsSync49, mkdirSync as mkdirSync36, readFileSync as readFileSync45, readdirSync as readdirSync17, rmSync as rmSync22, statSync as statSync14, writeFileSync as writeFileSync36 } from "fs";
23709
+ import { existsSync as existsSync48, mkdirSync as mkdirSync37, readFileSync as readFileSync45, readdirSync as readdirSync17, rmSync as rmSync22, statSync as statSync14, writeFileSync as writeFileSync37 } from "fs";
23469
23710
  import { join as join52 } from "path";
23470
23711
  import { randomBytes as randomBytes2 } from "crypto";
23471
23712
  var DEFAULT_FLOOR_MS = 5e3;
@@ -23563,7 +23804,7 @@ function computeTreeSignature(paths) {
23563
23804
  }
23564
23805
  function makeStaleChecker(paths, opts = {}) {
23565
23806
  const initial = computeTreeSignature(paths);
23566
- return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync49(opts.reloadSentinel);
23807
+ return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync48(opts.reloadSentinel);
23567
23808
  }
23568
23809
  function reloadSentinelFor(stateDir) {
23569
23810
  return join52(stateDir, "reload-requested");
@@ -23579,9 +23820,9 @@ function daemonStampFor(stateDir) {
23579
23820
  return join52(stateDir, "daemon.started");
23580
23821
  }
23581
23822
  function noteDaemonStarted(stateDir, now = Date.now()) {
23582
- mkdirSync36(stateDir, { recursive: true });
23823
+ mkdirSync37(stateDir, { recursive: true });
23583
23824
  rmSync22(reloadSentinelFor(stateDir), { force: true });
23584
- writeFileSync36(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
23825
+ writeFileSync37(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
23585
23826
  `);
23586
23827
  }
23587
23828
  function readDaemonStarted(stateDir) {
@@ -24204,7 +24445,7 @@ import { createHash as createHash11 } from "crypto";
24204
24445
  import {
24205
24446
  chmodSync as chmodSync4,
24206
24447
  closeSync as closeSync6,
24207
- existsSync as existsSync50,
24448
+ existsSync as existsSync49,
24208
24449
  fstatSync,
24209
24450
  openSync as openSync6,
24210
24451
  readSync as readSync3,
@@ -24215,8 +24456,8 @@ import {
24215
24456
  } from "fs";
24216
24457
  import { createConnection } from "net";
24217
24458
  import { createServer as createServer2 } from "http";
24218
- import { basename as basename14, dirname as dirname32, join as join53 } from "path";
24219
- import { execFile as execFile3, spawn as spawn12 } from "child_process";
24459
+ import { basename as basename14, dirname as dirname33, join as join53 } from "path";
24460
+ import { execFile as execFile4, spawn as spawn12 } from "child_process";
24220
24461
 
24221
24462
  // src/periscope-control.ts
24222
24463
  import { spawn as spawn11 } from "child_process";
@@ -24531,7 +24772,7 @@ function candidates(live, rotated) {
24531
24772
  const out = [live];
24532
24773
  if (!rotated) return out;
24533
24774
  try {
24534
- for (const n of readdirSync18(dirname32(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join53(dirname32(live), n));
24775
+ for (const n of readdirSync18(dirname33(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join53(dirname33(live), n));
24535
24776
  } catch {
24536
24777
  }
24537
24778
  return out;
@@ -24575,7 +24816,7 @@ function lastLines2(path, n) {
24575
24816
  }
24576
24817
  function readTail(live, opts) {
24577
24818
  const max = opts.maxBytes ?? MAX_TAIL_BYTES;
24578
- if (!existsSync50(live)) {
24819
+ if (!existsSync49(live)) {
24579
24820
  return { lines: [], cursor: opts.after ?? encodeCursor({ dev: 0, ino: 0, off: 0 }), reset: false };
24580
24821
  }
24581
24822
  const cur = opts.after ? decodeCursor(opts.after) : null;
@@ -24691,7 +24932,7 @@ async function tailReply(path, q2, rotated, abort) {
24691
24932
  const deadline = Date.now() + Math.min(wait, MAX_WAIT_SECONDS) * 1e3;
24692
24933
  let r = readTail(path, { after, tail: tail3, rotated });
24693
24934
  while (after && !r.lines.length && !r.reset && Date.now() < deadline && !abort.aborted) {
24694
- await waitForChange(dirname32(path), deadline - Date.now(), abort);
24935
+ await waitForChange(dirname33(path), deadline - Date.now(), abort);
24695
24936
  r = readTail(path, { after: r.cursor, rotated });
24696
24937
  }
24697
24938
  return reply2(200, r);
@@ -24732,7 +24973,7 @@ async function route(deps, method, url, req, abort) {
24732
24973
  const run3 = decodeURIComponent(p.slice("/streams/".length));
24733
24974
  if (!RUN_NAME_RE.test(run3) || run3.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run3)) return err2(400, "not a run name");
24734
24975
  const file = join53(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
24735
- if (!existsSync50(file)) return err2(404, "no such run");
24976
+ if (!existsSync49(file)) return err2(404, "no such run");
24736
24977
  return tailReply(file, url.searchParams, false, abort);
24737
24978
  }
24738
24979
  if ((p === "/pause" || p === "/resume") && post3) {
@@ -24776,7 +25017,7 @@ function send(res, r) {
24776
25017
  res.end(body);
24777
25018
  }
24778
25019
  async function socketIsStale(path) {
24779
- if (!existsSync50(path)) return false;
25020
+ if (!existsSync49(path)) return false;
24780
25021
  return new Promise((resolve8) => {
24781
25022
  const c = createConnection(path);
24782
25023
  c.once("connect", () => {
@@ -24795,7 +25036,7 @@ async function startControlServer(deps) {
24795
25036
  return null;
24796
25037
  }
24797
25038
  if (await socketIsStale(path)) rmSync23(path, { force: true });
24798
- else if (existsSync50(path)) {
25039
+ else if (existsSync49(path)) {
24799
25040
  log(`control API not started: ${path} is in use by another process`);
24800
25041
  return null;
24801
25042
  }
@@ -24868,7 +25109,7 @@ function schedulerHandler(control, run3 = runDetached) {
24868
25109
  }
24869
25110
  function crewJson(args, timeoutMs = 6e4) {
24870
25111
  return new Promise((resolve8) => {
24871
- execFile3(process.execPath, [process.argv[1] ?? "", ...args], { timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024 }, (_e, stdout) => {
25112
+ execFile4(process.execPath, [process.argv[1] ?? "", ...args], { timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024 }, (_e, stdout) => {
24872
25113
  try {
24873
25114
  const v = JSON.parse(String(stdout));
24874
25115
  resolve8(v && typeof v === "object" && !Array.isArray(v) ? v : null);
@@ -24881,24 +25122,24 @@ function crewJson(args, timeoutMs = 6e4) {
24881
25122
 
24882
25123
  // src/periscope-mcp.ts
24883
25124
  import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
24884
- import { basename as basename15, dirname as dirname33, isAbsolute as isAbsolute5, join as join55, relative as relative2, resolve as resolve6, sep } from "path";
25125
+ import { basename as basename15, dirname as dirname34, isAbsolute as isAbsolute5, join as join55, relative as relative2, resolve as resolve6, sep } from "path";
24885
25126
  import { z } from "zod";
24886
25127
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
24887
25128
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
24888
25129
 
24889
25130
  // src/periscope-endpoints.ts
24890
25131
  import { randomBytes as randomBytes3 } from "crypto";
24891
- import { chmodSync as chmodSync5, existsSync as existsSync51, mkdirSync as mkdirSync37, readdirSync as readdirSync19, readFileSync as readFileSync46, rmSync as rmSync24, writeFileSync as writeFileSync37 } from "fs";
25132
+ import { chmodSync as chmodSync5, existsSync as existsSync50, mkdirSync as mkdirSync38, readdirSync as readdirSync19, readFileSync as readFileSync46, rmSync as rmSync24, writeFileSync as writeFileSync38 } from "fs";
24892
25133
  import { join as join54 } from "path";
24893
25134
  var secretDir = (stateDir) => join54(stateDir, "periscope-mcp");
24894
25135
  var secretFile = (stateDir, id) => join54(secretDir(stateDir), `${id}.json`);
24895
25136
  var endpointDir = (stateDir) => join54(stateDir, "periscope-endpoints");
24896
25137
  var endpointFile = (stateDir, id) => join54(endpointDir(stateDir), `${id}.json`);
24897
25138
  function mintPeriscopeSecret(stateDir, ticketRecordId) {
24898
- mkdirSync37(secretDir(stateDir), { recursive: true, mode: 448 });
25139
+ mkdirSync38(secretDir(stateDir), { recursive: true, mode: 448 });
24899
25140
  const secret = randomBytes3(32).toString("base64url");
24900
25141
  const file = secretFile(stateDir, ticketRecordId);
24901
- writeFileSync37(file, `${JSON.stringify({ secret })}
25142
+ writeFileSync38(file, `${JSON.stringify({ secret })}
24902
25143
  `, { mode: 384 });
24903
25144
  chmodSync5(file, 384);
24904
25145
  return secret;
@@ -24922,8 +25163,8 @@ function readPeriscopeEndpoint(stateDir, ticketRecordId) {
24922
25163
  }
24923
25164
  }
24924
25165
  function writePeriscopeEndpoint(stateDir, rec) {
24925
- mkdirSync37(endpointDir(stateDir), { recursive: true });
24926
- writeFileSync37(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
25166
+ mkdirSync38(endpointDir(stateDir), { recursive: true });
25167
+ writeFileSync38(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
24927
25168
  `);
24928
25169
  }
24929
25170
  function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
@@ -24931,7 +25172,7 @@ function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
24931
25172
  }
24932
25173
  function listPeriscopeEndpoints(stateDir) {
24933
25174
  const dir = endpointDir(stateDir);
24934
- if (!existsSync51(dir)) return [];
25175
+ if (!existsSync50(dir)) return [];
24935
25176
  const out = [];
24936
25177
  for (const name of readdirSync19(dir)) {
24937
25178
  if (!name.endsWith(".json")) continue;
@@ -25019,10 +25260,10 @@ async function realAncestor(p) {
25019
25260
  }
25020
25261
  if (isLink) {
25021
25262
  if (++hops > 40) throw new PathRefusal(`too many symbolic links resolving ${p}`);
25022
- cur = resolve6(dirname33(cur), await readlink(cur));
25263
+ cur = resolve6(dirname34(cur), await readlink(cur));
25023
25264
  continue;
25024
25265
  }
25025
- const parent = dirname33(cur);
25266
+ const parent = dirname34(cur);
25026
25267
  if (parent === cur) throw new PathRefusal(`cannot resolve ${p}`);
25027
25268
  tail3.push(basename15(cur));
25028
25269
  cur = parent;
@@ -25133,7 +25374,7 @@ function createPeriscopeMcpServer(d, sc) {
25133
25374
  if (Buffer.byteLength(content) > MAX_WRITE_BYTES) return text(`content exceeds ${MAX_WRITE_BYTES} bytes`, true);
25134
25375
  const abs = await inTree(path);
25135
25376
  if (abs === await realpath(root)) return text("path must name a file", true);
25136
- await mkdir(dirname33(abs), { recursive: true });
25377
+ await mkdir(dirname34(abs), { recursive: true });
25137
25378
  await inTree(path);
25138
25379
  await writeFile(abs, content);
25139
25380
  return text(`Wrote ${Buffer.byteLength(content)} bytes to ${await rel(abs)}`);
@@ -25173,7 +25414,7 @@ function createPeriscopeMcpServer(d, sc) {
25173
25414
  inputSchema: { path: z.string() },
25174
25415
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
25175
25416
  }, async ({ path }) => {
25176
- const parent = await inTree(dirname33(path) === "" ? "." : dirname33(path));
25417
+ const parent = await inTree(dirname34(path) === "" ? "." : dirname34(path));
25177
25418
  const abs = join55(parent, basename15(path));
25178
25419
  if (basename15(path) === ".git" || abs === parent) return text("path must name a file", true);
25179
25420
  const st = await lstat(abs).catch(() => void 0);
@@ -25295,7 +25536,7 @@ function findPeriscope(sidecars, segment) {
25295
25536
  }
25296
25537
 
25297
25538
  // src/cli.ts
25298
- var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname34(process.execPath) : resolve7(dirname34(fileURLToPath2(import.meta.url)), "..");
25539
+ var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname35(process.execPath) : resolve7(dirname35(fileURLToPath2(import.meta.url)), "..");
25299
25540
  var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
25300
25541
  var CYCLE_SECONDS2 = 120;
25301
25542
  var STALE_CYCLES = 3;
@@ -25814,7 +26055,7 @@ if (command === "passenger-sync-daemon") {
25814
26055
  let images;
25815
26056
  for (const t of reposOf(c)) {
25816
26057
  const origin = originUrlOf(t.dir, "origin");
25817
- const repo = origin ? parseRemote(origin) : null;
26058
+ const repo = cacheRepoOfTarget(ship, t, origin);
25818
26059
  if (!repo) continue;
25819
26060
  let token = null;
25820
26061
  try {
@@ -25914,7 +26155,7 @@ if (command === "passenger-sync-daemon") {
25914
26155
  } else {
25915
26156
  const toleratesNoRoutes = command === "credential" || command === "forge" || command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "deprovision" || command === "connect" && flag("sign-out");
25916
26157
  const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset") || command === "deprovision";
25917
- if (configless && !existsSync52(findConfigFile(CREW_HOME))) {
26158
+ if (configless && !existsSync51(findConfigFile(CREW_HOME))) {
25918
26159
  cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
25919
26160
  } else {
25920
26161
  cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
@@ -25942,7 +26183,7 @@ if (command === "passenger-sync-daemon") {
25942
26183
  stateDir: cfg.ship.stateDir,
25943
26184
  configFile: cfg.configFile,
25944
26185
  readFile: (p) => readFileSync47(p, "utf8"),
25945
- writeFile: (p, t) => writeFileSync38(p, t),
26186
+ writeFile: (p, t) => writeFileSync39(p, t),
25946
26187
  stdin: () => readFileSync47(0, "utf8"),
25947
26188
  env: process.env
25948
26189
  }
@@ -26267,7 +26508,7 @@ if (command === "passenger-sync-daemon") {
26267
26508
  if (host === "linux" && hasSystemd) {
26268
26509
  const label = labelFor(CREW_HOME, "run");
26269
26510
  const staleTimer = join56(homedir11(), ".config", "systemd", "user", `${label}.timer`);
26270
- if (existsSync52(staleTimer)) {
26511
+ if (existsSync51(staleTimer)) {
26271
26512
  process.stdout.write(
26272
26513
  `removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
26273
26514
  `
@@ -26296,7 +26537,7 @@ if (command === "passenger-sync-daemon") {
26296
26537
  const { settled: doctorRepos, errors: refusedRepos } = await settleRepos(reposOf(c), (t) => t.name, (t) => resolveOneRepo(c, t));
26297
26538
  for (const e of refusedRepos) problems.push(`repo ${e.repo}: ${e.detail}`);
26298
26539
  for (const r of doctorRepos) {
26299
- if (!existsSync52(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
26540
+ if (!existsSync51(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
26300
26541
  if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
26301
26542
  readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
26302
26543
  }
@@ -26323,7 +26564,7 @@ if (command === "passenger-sync-daemon") {
26323
26564
  }
26324
26565
  async function doctorFix(o) {
26325
26566
  const routes = await Promise.all(routesInScope(cfg.routes, named).map((c) => gatherHealth({ route: c.route, enabled: c.enabled, hasRepos: reposOf(c).length > 0 }, () => routeProblems(c))));
26326
- const unitExists = (p) => existsSync52(p);
26567
+ const unitExists = (p) => existsSync51(p);
26327
26568
  const crontab = o.host === "linux" ? spawnSync12("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
26328
26569
  const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run", installContext()), unitExists, crontab) : true;
26329
26570
  const plan = planFix({ routes, schedulerAbsent, host: o.host });
@@ -26340,7 +26581,7 @@ if (command === "passenger-sync-daemon") {
26340
26581
  for (const a of plan.actions) {
26341
26582
  if (a.kind === "enable") {
26342
26583
  if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
26343
- writeFileSync38(cfg.configFile, planEnable(readFileSync47(cfg.configFile, "utf8"), a.route));
26584
+ writeFileSync39(cfg.configFile, planEnable(readFileSync47(cfg.configFile, "utf8"), a.route));
26344
26585
  enabledSound.add(a.route);
26345
26586
  changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
26346
26587
  } else {
@@ -26384,6 +26625,13 @@ ${changed2.map((c) => ` - ${c}
26384
26625
  return [...out.values()];
26385
26626
  }
26386
26627
  const withShipSandbox = (declared) => cfg.ship.runner === "sandbox" ? [shipSandboxRef(), ...declared] : declared;
26628
+ async function loadContractOf(c, t) {
26629
+ if (!lacksCheckout(cfg.ship, t.dir)) return loadRepoConfig(t.dir);
26630
+ const settings = cacheSettingsOf(cfg.ship.sandbox);
26631
+ const mirror = mirrorRepoOfTarget(t);
26632
+ if (!settings || !mirror) return null;
26633
+ return readMirrorContract(new SandboxCache(settings), mirror, c.branch?.base ?? "main");
26634
+ }
26387
26635
  async function resolveOneRepo(c, t) {
26388
26636
  {
26389
26637
  const id = repoIdForName(c, t.name);
@@ -26392,7 +26640,7 @@ ${changed2.map((c) => ` - ${c}
26392
26640
  name: t.name,
26393
26641
  dir: t.dir,
26394
26642
  id,
26395
- config: resolveRepoConfig(loadRepoConfig(t.dir), {
26643
+ config: resolveRepoConfig(await loadContractOf(c, t), {
26396
26644
  hooks: { ...c.hooks, ...o?.hooks },
26397
26645
  labels: { ...c.labels, ...o?.labels },
26398
26646
  release: mergeRouteRelease(c.release, o?.release),
@@ -26448,6 +26696,7 @@ ${changed2.map((c) => ` - ${c}
26448
26696
  return `${who2}; ${lock2}`;
26449
26697
  }
26450
26698
  async function sweepClosedTickets(c, target, branch, tracker, dryRun2, remit, idle = false) {
26699
+ if (remoteOf(target.dir)) return;
26451
26700
  try {
26452
26701
  if (!dryRun2) reconcileHeldWorktrees(state, target.name, remit);
26453
26702
  if (idle && !sweepCandidatesExist(target.dir, branch.base)) return;
@@ -26477,20 +26726,86 @@ ${changed2.map((c) => ` - ${c}
26477
26726
  }
26478
26727
  const precheckTickets = /* @__PURE__ */ new Map();
26479
26728
  const scope0 = (c, t) => releaseLockScope(c.route, t.name);
26729
+ async function releaseFromSandbox(c, target, contract, mirror, opts) {
26730
+ const scope = releaseLockScope(c.route, target.name);
26731
+ const remit = emit.forRoute(c.route);
26732
+ const identity = await resolveReleaseKey(c);
26733
+ const tracker = new Tracker(c, cfg.ship, { apiKey: identity.apiKey });
26734
+ const { byRepo } = ticketsByRepo(c, await tracker.openTickets());
26735
+ const modes = { modes: await tracker.projectWorkModes(), agentAssignees: /* @__PURE__ */ new Set() };
26736
+ const verified = withoutManualVerified(byRepo.get(target.name) ?? [], tracker.contract.statuses.verified, modes).filter((t) => t.status === tracker.contract.statuses.verified);
26737
+ const force2 = command === "deploy" || flag("force");
26738
+ if (!dryRun && verified.length === 0 && !force2) {
26739
+ return { scope, tests: "skipped", outcome: "skipped", detail: "nothing verified \u2014 no release sandbox created" };
26740
+ }
26741
+ const o = c.repoOverrides[target.name];
26742
+ const effective = resolveRepoConfig(contract, {
26743
+ hooks: { ...c.hooks, ...o?.hooks },
26744
+ labels: { ...c.labels, ...o?.labels },
26745
+ release: mergeRouteRelease(c.release, o?.release),
26746
+ branch: { ...c.branch, ...o?.branch },
26747
+ worktrees: { prefix: shipWorktreePrefixFor(c), nameOverride: o?.worktreeName }
26748
+ }, target.dir);
26749
+ const forgeHost = mirror.host;
26750
+ let token = null;
26751
+ try {
26752
+ token = readForgeToken(cfg.ship.forges, forgeHost, cfg.ship.stateDir);
26753
+ } catch {
26754
+ }
26755
+ const sandbox = await openReleaseSandbox({
26756
+ sandbox: cfg.ship.sandbox ?? {},
26757
+ route: c.route,
26758
+ repoName: target.name,
26759
+ image: sandboxImageForRun(readSandboxState(cfg.ship.stateDir), target.name, Boolean(contract.sandbox)) ?? void 0,
26760
+ policy: {
26761
+ forgeHosts: [...new Set(reposOf(c).map((r) => r.forgeHost ?? "github.com"))],
26762
+ tablationUrl: c.baseUrl,
26763
+ authMode: "workspace",
26764
+ provider: "anthropic",
26765
+ harnessBin: "claude",
26766
+ ...contract.sandboxNetwork ? { extraHosts: contract.sandboxNetwork.hosts, toolchains: contract.sandboxNetwork.toolchains } : {}
26767
+ },
26768
+ policyFile: join56(cfg.ship.stateDir, "release", `${target.name}.policy.yaml`),
26769
+ repo: mirror,
26770
+ origin: forgeDescriptorFor(forgeHost).httpsUrl(mirror.host, mirror.path),
26771
+ base: effective.branch.base,
26772
+ branches: verified.map((t) => pushedBranchForTicket(effective, t)),
26773
+ tagPattern: effective.release.tagPattern ?? "v*",
26774
+ token,
26775
+ username: forgeFor(cfg.ship.forges, forgeHost)?.entry.username || forgeDescriptorFor(forgeHost).credentialUsername,
26776
+ emit: remit
26777
+ }).catch((e) => {
26778
+ remit.warn(`${scope}: could not start the release sandbox: ${e.message}`, { step: "release" });
26779
+ return null;
26780
+ });
26781
+ if (!sandbox) return { scope, tests: "skipped", outcome: "skipped", detail: "could not start the release sandbox" };
26782
+ remit.emit(`${scope}: releasing from sandbox ${sandbox.sandbox}`, { step: "release", data: { event: "release_sandbox", sandbox: sandbox.sandbox } });
26783
+ try {
26784
+ const summary = await releasePhase(c, { ...target, dir: sandbox.dir }, opts);
26785
+ return { ...summary, detail: `${summary.detail ? `${summary.detail}; ` : ""}ran in sandbox ${sandbox.sandbox}` };
26786
+ } finally {
26787
+ await sandbox.close();
26788
+ }
26789
+ }
26480
26790
  async function releasePhase(c, target, opts = {}) {
26481
26791
  const scope = releaseLockScope(c.route, target.name);
26482
26792
  const remit = emit.forRoute(c.route);
26483
26793
  if (lacksCheckout(cfg.ship, target.dir)) {
26484
26794
  let mode = null;
26795
+ let contract = null;
26485
26796
  const settings = cacheSettingsOf(cfg.ship.sandbox);
26486
26797
  const mirror = mirrorRepoOfTarget(target);
26487
26798
  if (settings && mirror) {
26488
26799
  try {
26489
- mode = (await readMirrorContract(new SandboxCache(settings), mirror, c.branch?.base ?? "main"))?.release.mode ?? null;
26800
+ contract = await readMirrorContract(new SandboxCache(settings), mirror, c.branch?.base ?? "main");
26801
+ mode = contract?.release.mode ?? null;
26490
26802
  } catch (e) {
26491
26803
  if (firstReport(`${scope0(c, target)}:contract`)) remit.warn(`${target.name}: could not read the contract from the mirror: ${e.message}`, { step: "release" });
26492
26804
  }
26493
26805
  }
26806
+ if (releasesFromSandbox(mode) && settings && mirror && contract) {
26807
+ return releaseFromSandbox(c, target, contract, mirror, opts);
26808
+ }
26494
26809
  const note = noCheckoutReleaseNote(target.name, mode);
26495
26810
  if (firstReport(`${scope0(c, target)}:release`)) remit.emit(note, { step: "release" });
26496
26811
  return { scope: releaseLockScope(c.route, target.name), tests: "skipped", outcome: "skipped", detail: note };
@@ -27841,10 +28156,10 @@ ${cu.tail.trimEnd()}
27841
28156
  `);
27842
28157
  if (!dryRun) {
27843
28158
  const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
27844
- const raw = existsSync52(resolvedPath) ? JSON.parse(readFileSync47(resolvedPath, "utf8")) : {};
28159
+ const raw = existsSync51(resolvedPath) ? JSON.parse(readFileSync47(resolvedPath, "utf8")) : {};
27845
28160
  raw.agentSkills = result.agentSkills;
27846
- mkdirSync38(dirOf2(resolvedPath), { recursive: true });
27847
- writeFileSync38(resolvedPath, `${JSON.stringify(raw, null, 2)}
28161
+ mkdirSync39(dirOf2(resolvedPath), { recursive: true });
28162
+ writeFileSync39(resolvedPath, `${JSON.stringify(raw, null, 2)}
27848
28163
  `);
27849
28164
  }
27850
28165
  if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
@@ -27867,7 +28182,7 @@ ${cu.tail.trimEnd()}
27867
28182
  throw e;
27868
28183
  }
27869
28184
  const oldName = cfg.ship.name;
27870
- writeFileSync38(cfg.configFile, setShipNameInYaml(readFileSync47(cfg.configFile, "utf8"), newName));
28185
+ writeFileSync39(cfg.configFile, setShipNameInYaml(readFileSync47(cfg.configFile, "utf8"), newName));
27871
28186
  cfg.ship.name = newName;
27872
28187
  let renameMachineId;
27873
28188
  try {
@@ -27921,7 +28236,7 @@ ${cu.tail.trimEnd()}
27921
28236
  const found = cfg.routes.find((r) => r.route === routeArg);
27922
28237
  if (!found) throw new ReposError(`no route "${routeArg}" in the config (have: ${cfg.routes.map((r) => r.route).join(", ") || "none"})`);
27923
28238
  const named2 = value("route") ? void 0 : positional[3];
27924
- const dir = named2 ? repoDirOf(configText, dirname34(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
28239
+ const dir = named2 ? repoDirOf(configText, dirname35(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
27925
28240
  const target = reposOf(found).find((t) => resolvePath(t.dir) === dir || t.name === named2);
27926
28241
  const o = target ? found.repoOverrides[target.name] : void 0;
27927
28242
  runReposCheck(dir, {
@@ -27945,9 +28260,9 @@ ${cu.tail.trimEnd()}
27945
28260
  if (sub === "hooks") {
27946
28261
  try {
27947
28262
  const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
27948
- const dir = repoDirOf(configText, dirname34(cfg.configFile), a.route, a.name);
27949
- if (!existsSync52(dir)) throw new ReposError(`${dir} does not exist on disk`);
27950
- const files = { exists: (r) => existsSync52(resolvePath(dir, r)), list: (r) => {
28263
+ const dir = repoDirOf(configText, dirname35(cfg.configFile), a.route, a.name);
28264
+ if (!existsSync51(dir)) throw new ReposError(`${dir} does not exist on disk`);
28265
+ const files = { exists: (r) => existsSync51(resolvePath(dir, r)), list: (r) => {
27951
28266
  try {
27952
28267
  return readdirSync20(resolvePath(dir, r));
27953
28268
  } catch {
@@ -27997,19 +28312,19 @@ ${cu.tail.trimEnd()}
27997
28312
  let cleared;
27998
28313
  if (clear2) {
27999
28314
  const plan = clearRouteHooks(configText, a.route, a.name);
28000
- if (plan.removed.length > 0) writeFileSync38(cfg.configFile, plan.text);
28315
+ if (plan.removed.length > 0) writeFileSync39(cfg.configFile, plan.text);
28001
28316
  cleared = { file: cfg.configFile, removed: plan.removed };
28002
28317
  }
28003
28318
  if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
28004
28319
  if (write === "route") {
28005
28320
  const plan = planRouteHooks(configText, a.route, a.name, picked.hooks, { replace: flag("replace") });
28006
- writeFileSync38(cfg.configFile, plan.text);
28321
+ writeFileSync39(cfg.configFile, plan.text);
28007
28322
  written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept, replaced: plan.replaced };
28008
28323
  } else if (write === "repo") {
28009
28324
  const have = existingRepoConfig(files);
28010
28325
  if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
28011
28326
  const file = resolvePath(dir, ".crew.yaml");
28012
- writeFileSync38(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
28327
+ writeFileSync39(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
28013
28328
  written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [], replaced: [] };
28014
28329
  }
28015
28330
  if (flag("json")) {
@@ -28065,7 +28380,7 @@ ${cu.tail.trimEnd()}
28065
28380
  if (sub === "list" && !routeName) usage2();
28066
28381
  if (sub === "add") {
28067
28382
  try {
28068
- const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync52(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
28383
+ const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync51(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
28069
28384
  routeName = a.route;
28070
28385
  addPath = a.path;
28071
28386
  addName = a.name;
@@ -28095,7 +28410,7 @@ ${cu.tail.trimEnd()}
28095
28410
  const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
28096
28411
  if (sub === "list" || sub === "add") {
28097
28412
  const liveRoute = cfg.routes.find((r) => r.route === routeName);
28098
- if (liveRoute?.resolved?.reposModelId && existsSync52(resolvedFile) && !dryRun) {
28413
+ if (liveRoute?.resolved?.reposModelId && existsSync51(resolvedFile) && !dryRun) {
28099
28414
  try {
28100
28415
  await hydrateApiKeys([liveRoute]);
28101
28416
  persistRepoRows(cfg.ship.stateDir, liveRoute.route, await new Tracker(liveRoute, cfg.ship).repoRowsForMap());
@@ -28104,7 +28419,7 @@ ${cu.tail.trimEnd()}
28104
28419
  }
28105
28420
  }
28106
28421
  let resolvedRemotes;
28107
- if (existsSync52(resolvedFile)) {
28422
+ if (existsSync51(resolvedFile)) {
28108
28423
  try {
28109
28424
  resolvedRemotes = JSON.parse(readFileSync47(resolvedFile, "utf8"));
28110
28425
  } catch {
@@ -28112,8 +28427,8 @@ ${cu.tail.trimEnd()}
28112
28427
  }
28113
28428
  const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
28114
28429
  const text2 = configText;
28115
- const base = dirname34(cfg.configFile);
28116
- const isCheckout = (d) => existsSync52(`${d}/.git`);
28430
+ const base = dirname35(cfg.configFile);
28431
+ const isCheckout = (d) => existsSync51(`${d}/.git`);
28117
28432
  try {
28118
28433
  if (sub === "list") {
28119
28434
  if (flag("json")) {
@@ -28123,7 +28438,7 @@ ${cu.tail.trimEnd()}
28123
28438
  base,
28124
28439
  resolved: resolvedRemotes,
28125
28440
  originOf,
28126
- exists: (d) => existsSync52(d),
28441
+ exists: (d) => existsSync51(d),
28127
28442
  isCheckout,
28128
28443
  repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
28129
28444
  try {
@@ -28145,7 +28460,7 @@ ${cu.tail.trimEnd()}
28145
28460
  for (const r of repos) {
28146
28461
  const origin2 = originOf(r.dir);
28147
28462
  const row = matchRepoName(origin2, resolvedRemotes);
28148
- const note = !existsSync52(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
28463
+ const note = !existsSync51(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
28149
28464
  process.stdout.write(`${r.name} ${r.dir} ${note}
28150
28465
  `);
28151
28466
  }
@@ -28157,9 +28472,9 @@ ${cu.tail.trimEnd()}
28157
28472
  ` : `${human}
28158
28473
  `);
28159
28474
  const plan2 = planRepoClone({ text: text2, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
28160
- if (existsSync52(plan2.target) && (existsSync52(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
28475
+ if (existsSync51(plan2.target) && (existsSync51(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
28161
28476
  throw new ReposError(
28162
- existsSync52(`${plan2.target}/.git`) ? `${plan2.target} is already a checkout \u2014 attach it with \`crew repos add ${routeName} ${plan2.name} ${plan2.target}\`` : `${plan2.target} exists and is not empty \u2014 pass --dir to clone somewhere else`
28477
+ existsSync51(`${plan2.target}/.git`) ? `${plan2.target} is already a checkout \u2014 attach it with \`crew repos add ${routeName} ${plan2.name} ${plan2.target}\`` : `${plan2.target} exists and is not empty \u2014 pass --dir to clone somewhere else`
28163
28478
  );
28164
28479
  }
28165
28480
  if (dryRun) {
@@ -28177,14 +28492,14 @@ ${cu.tail.trimEnd()}
28177
28492
  throw new ReposError(`${e.message}${authHint}
28178
28493
  crew.yaml was not changed`);
28179
28494
  }
28180
- writeFileSync38(cfg.configFile, plan2.text);
28495
+ writeFileSync39(cfg.configFile, plan2.text);
28181
28496
  if (plan2.droppedDir) process.stderr.write(`crew repos: dropped dir: ${plan2.droppedDir} from route ${routeName} \u2014 it is not a checkout (the clone base); checkouts now live under repos:
28182
28497
  `);
28183
28498
  say("attached", { route: routeName, name: plan2.name, dir: plan2.target, mode: plan2.mode }, `cloned and attached ${plan2.target} to ${routeName} as ${plan2.mode === "dir" ? "dir" : `repos.${plan2.name}`} in ${cfg.configFile}`);
28184
28499
  break;
28185
28500
  }
28186
28501
  const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir11()));
28187
- const top = existsSync52(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
28502
+ const top = existsSync51(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
28188
28503
  if (!top) throw new ReposError(`${target} is not a git checkout`);
28189
28504
  const origin = originOf(top);
28190
28505
  const plan = planRepoAdd({
@@ -28236,15 +28551,15 @@ ${cu.tail.trimEnd()}
28236
28551
  const created = await rowTracker.createRepoRow(rowPayload);
28237
28552
  process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
28238
28553
  `);
28239
- if (existsSync52(resolvedFile)) {
28554
+ if (existsSync51(resolvedFile)) {
28240
28555
  const raw = JSON.parse(readFileSync47(resolvedFile, "utf8"));
28241
28556
  raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
28242
28557
  raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
28243
- writeFileSync38(resolvedFile, `${JSON.stringify(raw, null, 2)}
28558
+ writeFileSync39(resolvedFile, `${JSON.stringify(raw, null, 2)}
28244
28559
  `);
28245
28560
  }
28246
28561
  }
28247
- writeFileSync38(cfg.configFile, plan.text);
28562
+ writeFileSync39(cfg.configFile, plan.text);
28248
28563
  process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
28249
28564
  `);
28250
28565
  } catch (e) {
@@ -28616,7 +28931,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
28616
28931
  process.exit(2);
28617
28932
  }
28618
28933
  if (found.shipNameAdopted && !dryRun) {
28619
- writeFileSync38(cfg.configFile, setShipNameInYaml(readFileSync47(cfg.configFile, "utf8"), found.shipNameAdopted));
28934
+ writeFileSync39(cfg.configFile, setShipNameInYaml(readFileSync47(cfg.configFile, "utf8"), found.shipNameAdopted));
28620
28935
  cfg.ship.name = found.shipNameAdopted;
28621
28936
  }
28622
28937
  if (found.shipRow) {
@@ -28659,7 +28974,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
28659
28974
  }
28660
28975
  }
28661
28976
  let previous;
28662
- const hadPreviousFile = existsSync52(resolvedPath);
28977
+ const hadPreviousFile = existsSync51(resolvedPath);
28663
28978
  if (hadPreviousFile) {
28664
28979
  try {
28665
28980
  previous = JSON.parse(readFileSync47(resolvedPath, "utf8"));
@@ -28792,10 +29107,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
28792
29107
  process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
28793
29108
  `);
28794
29109
  } else {
28795
- mkdirSync38(dirname34(resolvedPath), { recursive: true });
29110
+ mkdirSync39(dirname35(resolvedPath), { recursive: true });
28796
29111
  if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
28797
29112
  const tmpPath = `${resolvedPath}.tmp${process.pid}`;
28798
- writeFileSync38(tmpPath, `${JSON.stringify(resolved, null, 2)}
29113
+ writeFileSync39(tmpPath, `${JSON.stringify(resolved, null, 2)}
28799
29114
  `);
28800
29115
  renameSync6(tmpPath, resolvedPath);
28801
29116
  process.stderr.write(`Resolved ids written to ${resolvedPath}
@@ -28841,8 +29156,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
28841
29156
  process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
28842
29157
  `);
28843
29158
  } else {
28844
- mkdirSync38(dirname34(apiKeyFile), { recursive: true });
28845
- writeFileSync38(apiKeyFile, `${apiKeyVar}=${apiKey}
29159
+ mkdirSync39(dirname35(apiKeyFile), { recursive: true });
29160
+ writeFileSync39(apiKeyFile, `${apiKeyVar}=${apiKey}
28846
29161
  `, { mode: 384 });
28847
29162
  process.stderr.write(`API key written to ${apiKeyFile}
28848
29163
  `);
@@ -29546,8 +29861,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
29546
29861
  prepare: async (path) => {
29547
29862
  for (const f of repoWt.config.worktrees.copy) {
29548
29863
  const src = join56(repoWt.dir, f);
29549
- if (!existsSync52(src) || existsSync52(join56(path, f))) continue;
29550
- mkdirSync38(dirOf2(join56(path, f)), { recursive: true });
29864
+ if (!existsSync51(src) || existsSync51(join56(path, f))) continue;
29865
+ mkdirSync39(dirOf2(join56(path, f)), { recursive: true });
29551
29866
  copyFileSync2(src, join56(path, f));
29552
29867
  }
29553
29868
  const setup = repoWt.config.hooks.setup;
@@ -29769,7 +30084,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
29769
30084
  const shellR = hit && resolveShell(hit.repo.config.shell ?? cfg.ship.shell);
29770
30085
  const resumed = await runPeriscopeResume({
29771
30086
  find: async () => hit && { row: hit.row, tag: hit.tag },
29772
- worktreeExists: (p) => existsSync52(p),
30087
+ worktreeExists: (p) => existsSync51(p),
29773
30088
  prepareDropFile,
29774
30089
  startProxy: (wt, url, n) => launchProxy(crewInvocation(CREW_HOME), wt, url, Number(n), { host: hit.row.host || "localhost", inject: hit.repo.config.periscope?.inspector !== "repo" }),
29775
30090
  caps: periscopeCaps(cfg.ship),
@@ -29866,8 +30181,8 @@ ${where.join("\n")}`);
29866
30181
  prepare: async (path) => {
29867
30182
  for (const f of repoP.config.worktrees.copy) {
29868
30183
  const src = join56(repoP.dir, f);
29869
- if (!existsSync52(src)) continue;
29870
- mkdirSync38(dirOf2(join56(path, f)), { recursive: true });
30184
+ if (!existsSync51(src)) continue;
30185
+ mkdirSync39(dirOf2(join56(path, f)), { recursive: true });
29871
30186
  copyFileSync2(src, join56(path, f));
29872
30187
  }
29873
30188
  const setup = repoP.config.hooks.setup;
@@ -30243,8 +30558,8 @@ ${where.join("\n")}`);
30243
30558
  process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
30244
30559
  break;
30245
30560
  }
30246
- mkdirSync38(cfg.ship.stateDir, { recursive: true });
30247
- writeFileSync38(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
30561
+ mkdirSync39(cfg.ship.stateDir, { recursive: true });
30562
+ writeFileSync39(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
30248
30563
  `);
30249
30564
  process.stdout.write(
30250
30565
  "crew reload: requested \u2014 the daemon relaunches with the new config once its running agents end (it starts no new work meanwhile); if it is busy, `crew reload --now` forces the relaunch and interrupts them\n"
@@ -30333,7 +30648,7 @@ ${where.join("\n")}`);
30333
30648
  if (!route2.resolved) fail2(`route ${route2.route} is not connected \u2014 run \`crew connect\` first`);
30334
30649
  const tracker = new Tracker(route2, cfg.ship);
30335
30650
  const configText = readFileSync47(cfg.configFile, "utf8");
30336
- const cfgBase = dirname34(cfg.configFile);
30651
+ const cfgBase = dirname35(cfg.configFile);
30337
30652
  const configured2 = configuredRepos(configText, route2.route, cfgBase);
30338
30653
  const dir = configured2.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured2.map((r) => r.dir), route2.reposBasePath, route2.route.split("/")[0]);
30339
30654
  const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route2.route);
@@ -30361,7 +30676,7 @@ ${where.join("\n")}`);
30361
30676
  }
30362
30677
  if (configured2.some((r) => r.dir === dir)) {
30363
30678
  try {
30364
- writeFileSync38(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
30679
+ writeFileSync39(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
30365
30680
  } catch (e) {
30366
30681
  if (e instanceof ReposError) fail2(e.message);
30367
30682
  throw e;
@@ -30369,29 +30684,29 @@ ${where.join("\n")}`);
30369
30684
  }
30370
30685
  if (row) {
30371
30686
  await tracker.deleteRepoRow(row.id);
30372
- if (existsSync52(resolvedFile)) {
30687
+ if (existsSync51(resolvedFile)) {
30373
30688
  const raw = JSON.parse(readFileSync47(resolvedFile, "utf8"));
30374
30689
  if (raw.repoNames) delete raw.repoNames[row.id];
30375
30690
  if (raw.repoRemotes) delete raw.repoRemotes[row.id];
30376
- writeFileSync38(resolvedFile, `${JSON.stringify(raw, null, 2)}
30691
+ writeFileSync39(resolvedFile, `${JSON.stringify(raw, null, 2)}
30377
30692
  `);
30378
30693
  }
30379
30694
  }
30380
- if (existsSync52(join56(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync25(dir, { recursive: true, force: true });
30695
+ if (existsSync51(join56(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync25(dir, { recursive: true, force: true });
30381
30696
  say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
30382
30697
  break;
30383
30698
  }
30384
30699
  const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
30385
- const fresh = !existsSync52(join56(dir, ".git"));
30386
- if (existsSync52(dir) && fresh && readdirSync20(dir).length > 0) fail2(`${dir} exists and is not a git checkout \u2014 move it aside`);
30700
+ const fresh = !existsSync51(join56(dir, ".git"));
30701
+ if (existsSync51(dir) && fresh && readdirSync20(dir).length > 0) fail2(`${dir} exists and is not a git checkout \u2014 move it aside`);
30387
30702
  if (dryRun) {
30388
30703
  say("dry-run", { dir, newCheckout: fresh, repoRow: row?.id ?? null }, `would ${fresh ? "create" : "reuse"} ${dir}${row ? "" : ", create the Repos row"}, and file one ${tracker.contract.statuses.approved} ticket`);
30389
30704
  break;
30390
30705
  }
30391
30706
  if (fresh) {
30392
- mkdirSync38(dir, { recursive: true });
30393
- writeFileSync38(join56(dir, "README.md"), SAMPLE_README);
30394
- writeFileSync38(join56(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
30707
+ mkdirSync39(dir, { recursive: true });
30708
+ writeFileSync39(join56(dir, "README.md"), SAMPLE_README);
30709
+ writeFileSync39(join56(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
30395
30710
  const g = (...a) => {
30396
30711
  const r = spawnSync12("git", a, { cwd: dir, encoding: "utf8" });
30397
30712
  if (r.status !== 0) fail2(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
@@ -30409,17 +30724,17 @@ ${where.join("\n")}`);
30409
30724
  spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
30410
30725
  });
30411
30726
  repoId = (await tracker.createRepoRow(payload)).id;
30412
- if (existsSync52(resolvedFile)) {
30727
+ if (existsSync51(resolvedFile)) {
30413
30728
  const raw = JSON.parse(readFileSync47(resolvedFile, "utf8"));
30414
30729
  raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
30415
30730
  raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
30416
- writeFileSync38(resolvedFile, `${JSON.stringify(raw, null, 2)}
30731
+ writeFileSync39(resolvedFile, `${JSON.stringify(raw, null, 2)}
30417
30732
  `);
30418
30733
  }
30419
30734
  }
30420
30735
  if (!configured2.some((r) => r.dir === dir)) {
30421
30736
  try {
30422
- writeFileSync38(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
30737
+ writeFileSync39(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
30423
30738
  } catch (e) {
30424
30739
  if (e instanceof ReposError) fail2(e.message);
30425
30740
  throw e;
@@ -30765,7 +31080,7 @@ watermark: ${state.watermark()}
30765
31080
  }
30766
31081
  for (const r of repos) {
30767
31082
  const remote = r.config.branch.remote;
30768
- if (existsSync52(r.dir)) {
31083
+ if (existsSync51(r.dir)) {
30769
31084
  pushAuth.push({
30770
31085
  route: c.route,
30771
31086
  repo: r.name,
@@ -30888,7 +31203,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
30888
31203
  }
30889
31204
  try {
30890
31205
  const evFile = eventFileFor(cfg.ship.stateDir);
30891
- const evSize = existsSync52(evFile) ? statSync16(evFile).size : 0;
31206
+ const evSize = existsSync51(evFile) ? statSync16(evFile).size : 0;
30892
31207
  process.stdout.write(
30893
31208
  evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
30894
31209
  ` : "events log: ok\n"
@@ -30913,7 +31228,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
30913
31228
  if (host === "linux" && detectSystemd()) {
30914
31229
  const ctl = planDaemonControl(CREW_HOME, host, true);
30915
31230
  const unitPlan = planInstall(cfg.ship, CREW_HOME, host, true, "run", installContext());
30916
- if (ctl.statusCommand && unitPlan.unitPaths.some((p) => existsSync52(p))) {
31231
+ if (ctl.statusCommand && unitPlan.unitPaths.some((p) => existsSync51(p))) {
30917
31232
  const st = spawnSync12(ctl.statusCommand[0], ctl.statusCommand.slice(1), { cwd: CREW_HOME, encoding: "utf8" });
30918
31233
  if (!st.error && st.status !== 0) {
30919
31234
  process.stdout.write("daemon unit: WARN installed but not running \u2014 no work is picked up; run `crew reload` (or `crew daemon start`), and `crew install` if the unit still says Restart=on-failure\n");