@tablation/crew 0.128.2 → 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
 
@@ -4937,7 +5001,7 @@ WorkingDirectory=${crewHome}
4937
5001
  ${systemdEnvLines(ship)}
4938
5002
  StandardOutput=append:${schedulerLog}
4939
5003
  StandardError=append:${schedulerLog}
4940
- Restart=on-failure
5004
+ Restart=always
4941
5005
  RestartSec=5
4942
5006
  # CREW-1775: a restart stops only the daemon; agent runs it started live in
4943
5007
  # their own process groups and the next daemon reattaches to them.
@@ -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
 
@@ -15441,6 +15519,10 @@ function applyManagedDefaultsToDoc(doc) {
15441
15519
  doc.setIn(["ship", "sandbox", "cacheVolume"], MANAGED_CACHE_VOLUME);
15442
15520
  wrote.push("ship.sandbox.cacheVolume");
15443
15521
  }
15522
+ if (doc.getIn(["ship", "cloneScheme"]) === void 0) {
15523
+ doc.setIn(["ship", "cloneScheme"], "https");
15524
+ wrote.push("ship.cloneScheme");
15525
+ }
15444
15526
  const existing = doc.getIn(["ship", "toolchains"]);
15445
15527
  const hasClaude = isMap2(existing) && existing.items.some((i) => (isScalar(i.key) ? i.key.value : i.key) === "claude");
15446
15528
  if (!hasClaude) {
@@ -15531,7 +15613,7 @@ function realProvisionDeps(crewHome) {
15531
15613
  };
15532
15614
  const configFile = () => {
15533
15615
  const f = findConfigFile(crewHome);
15534
- return existsSync34(f) ? f : null;
15616
+ return existsSync33(f) ? f : null;
15535
15617
  };
15536
15618
  return {
15537
15619
  crew: spawnCrew,
@@ -15565,14 +15647,14 @@ function realProvisionDeps(crewHome) {
15565
15647
  continue;
15566
15648
  }
15567
15649
  for (const p of plan.unitPaths) {
15568
- if (!existsSync34(p) || readFileSync34(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
15650
+ if (!existsSync33(p) || readFileSync34(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
15569
15651
  }
15570
15652
  }
15571
15653
  const label = labelFor(crewHome, "run");
15572
15654
  let running = false;
15573
15655
  let lingering = true;
15574
15656
  if (mechanism === "systemd") {
15575
- const system = ctx.root || existsSync34(systemUnitPathFor(label));
15657
+ const system = ctx.root || existsSync33(systemUnitPathFor(label));
15576
15658
  const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
15577
15659
  running = spawnSync9(ctl[0], [...ctl.slice(1), "is-active", "--quiet", `${label}.service`]).status === 0;
15578
15660
  if (!system) {
@@ -15628,7 +15710,7 @@ host=${host}
15628
15710
 
15629
15711
  // src/tunnel.ts
15630
15712
  import { spawn as spawn7 } from "child_process";
15631
- 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";
15632
15714
  import { randomInt } from "crypto";
15633
15715
  import { join as join39 } from "path";
15634
15716
  var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
@@ -15712,7 +15794,7 @@ function tunnelStatePath(stateDir, workspaceId) {
15712
15794
  }
15713
15795
  function readPersistedTunnel(stateDir, workspaceId) {
15714
15796
  const path = tunnelStatePath(stateDir, workspaceId);
15715
- if (!existsSync35(path)) return void 0;
15797
+ if (!existsSync34(path)) return void 0;
15716
15798
  try {
15717
15799
  return JSON.parse(readFileSync35(path, "utf8"));
15718
15800
  } catch {
@@ -15852,7 +15934,7 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
15852
15934
  }
15853
15935
  function readAllPersistedWorkspaceIds(stateDir) {
15854
15936
  const dir = join39(stateDir, "passengers");
15855
- if (!existsSync35(dir)) return [];
15937
+ if (!existsSync34(dir)) return [];
15856
15938
  return readdirSync13(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
15857
15939
  }
15858
15940
  var TUNNEL_REJECTED_KEY = "tunnel_rejected";
@@ -15892,7 +15974,7 @@ function realPoolMemberDeps(o) {
15892
15974
  crewVersion: crewVersion(o.crewHome),
15893
15975
  hostname: hostname3()
15894
15976
  },
15895
- hasShipConfig: () => existsSync36(findConfigFile(o.crewHome)),
15977
+ hasShipConfig: () => existsSync35(findConfigFile(o.crewHome)),
15896
15978
  async provision(lease, instanceClass, shipKind) {
15897
15979
  const hosts = lease.forges.map((f) => f.host.toLowerCase());
15898
15980
  const lines2 = [];
@@ -17268,7 +17350,7 @@ async function resolveOne(tracker, t, seat, approvedStatus, buildingStatus, affi
17268
17350
  }
17269
17351
 
17270
17352
  // src/repo-refresh.ts
17271
- 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";
17272
17354
  import { dirname as dirname21 } from "path";
17273
17355
  var REFRESH_COOLDOWN_MS = 2 * 6e4;
17274
17356
  var UNSERVABLE_PASSES_BEFORE_ATTENTION = 3;
@@ -17298,7 +17380,7 @@ function applyRepoRows(resolved, rows) {
17298
17380
  }
17299
17381
  function persistRepoRows(stateDir, routeName, rows) {
17300
17382
  const path = resolvedPathFor(stateDir, routeName);
17301
- if (!existsSync37(path)) return;
17383
+ if (!existsSync36(path)) return;
17302
17384
  const raw = JSON.parse(readFileSync36(path, "utf8"));
17303
17385
  applyRepoRows(raw, rows);
17304
17386
  mkdirSync26(dirname21(path), { recursive: true });
@@ -17753,12 +17835,12 @@ var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.rec
17753
17835
 
17754
17836
  // src/connect-wizard.ts
17755
17837
  import { delimiter, dirname as dirname22, join as join41 } from "path";
17756
- 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";
17757
17839
  import { stringify as stringify3 } from "yaml";
17758
17840
  var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
17759
17841
  function shouldRunWizard(opts) {
17760
17842
  if (!opts.isTTY) return false;
17761
- const exists = opts.configExists ?? existsSync38;
17843
+ const exists = opts.configExists ?? existsSync37;
17762
17844
  return !configSearchPath(opts.crewHome).some((p) => exists(p));
17763
17845
  }
17764
17846
  function firstRunConfigPath() {
@@ -17952,7 +18034,7 @@ function flagWizardAnswers(f, env) {
17952
18034
  }
17953
18035
 
17954
18036
  // src/release-run.ts
17955
- 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";
17956
18038
  import { dirname as dirname24, join as join43 } from "path";
17957
18039
  import { tmpdir as tmpdir5, hostname as hostname4 } from "os";
17958
18040
 
@@ -18652,6 +18734,17 @@ function ensureCommitterIdentity(o) {
18652
18734
  function isConflict(message, unmergedPaths) {
18653
18735
  return unmergedPaths.length > 0 || /CONFLICT \(|Automatic merge failed|unmerged (files|paths)/i.test(message);
18654
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
+ }
18655
18748
  function mergeOne(o, c) {
18656
18749
  const subject = c.entries[0] ?? `${c.ticket.title ?? c.ticket.issue_id} (${c.ticket.issue_id})`;
18657
18750
  if (o.dryRun) {
@@ -18659,6 +18752,7 @@ function mergeOne(o, c) {
18659
18752
  return { applied: true };
18660
18753
  }
18661
18754
  const before = headSha(o.cwd);
18755
+ if (remoteOf(o.cwd)) deepenUntilMergeBase(o, c.branch);
18662
18756
  const clearFailure = () => {
18663
18757
  o.attention?.clear(`release_merge_failed:${o.scope ?? "release"}`);
18664
18758
  };
@@ -18725,11 +18819,11 @@ async function bump(o, size) {
18725
18819
  }
18726
18820
  const files = o.repo.release.versionFiles.map((f) => join43(o.cwd, f));
18727
18821
  const first = files[0];
18728
- if (!existsSync39(first)) {
18822
+ if (!existsAt(first, existsSync38)) {
18729
18823
  o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
18730
18824
  return null;
18731
18825
  }
18732
- const firstText = readFileSync37(first, "utf8");
18826
+ const firstText = readTextAt(first, (p) => readFileSync37(p, "utf8"));
18733
18827
  const problem = versionFileProblem(o.repo.release.versionFiles[0], firstText);
18734
18828
  if (problem) {
18735
18829
  o.emit.error(problem);
@@ -18748,13 +18842,13 @@ async function bump(o, size) {
18748
18842
  return version;
18749
18843
  }
18750
18844
  for (const f of files) {
18751
- const src = readFileSync37(f, "utf8");
18845
+ const src = readTextAt(f, (p) => readFileSync37(p, "utf8"));
18752
18846
  const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
18753
18847
  if (out === src) {
18754
18848
  o.emit.error(`no version field in ${f}`);
18755
18849
  return null;
18756
18850
  }
18757
- writeFileSync31(f, out);
18851
+ writeTextAt(f, out, writeFileSync31);
18758
18852
  }
18759
18853
  return version;
18760
18854
  }
@@ -18767,8 +18861,8 @@ function writeChangelog(o, version, entries) {
18767
18861
  o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
18768
18862
  return;
18769
18863
  }
18770
- const existing = existsSync39(path) ? readFileSync37(path, "utf8") : "# Changelog\n";
18771
- 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);
18772
18866
  }
18773
18867
  async function confirm(o, expected) {
18774
18868
  if (!o.repo.hooks.released) return void 0;
@@ -18873,7 +18967,7 @@ function openReleaseCheckout(o) {
18873
18967
  }
18874
18968
  for (const f of o.repo.worktrees.copy) {
18875
18969
  const src = join43(o.cwd, f);
18876
- if (!existsSync39(src)) continue;
18970
+ if (!existsSync38(src)) continue;
18877
18971
  mkdirSync28(dirname24(join43(dir, f)), { recursive: true });
18878
18972
  copyFileSync(src, join43(dir, f));
18879
18973
  }
@@ -19013,6 +19107,12 @@ async function runReleaseCycle(o, movedPast2, localRefSkips) {
19013
19107
  if (inPlace) {
19014
19108
  return mergeAndRelease(o, decision, fresh);
19015
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
+ }
19016
19116
  const primary = checkGuards(o.cwd, base);
19017
19117
  if (primary) {
19018
19118
  o.emit.emit(scoped(`primary checkout not fast-forwarded: ${primary.detail}; its dist/ is stale until rebuilt`), { step: "release" });
@@ -19704,7 +19804,7 @@ function runForge(a, io) {
19704
19804
  }
19705
19805
 
19706
19806
  // src/mirror-contract.ts
19707
- import { existsSync as existsSync40 } from "fs";
19807
+ import { existsSync as existsSync39 } from "fs";
19708
19808
 
19709
19809
  // src/passenger-mirror.ts
19710
19810
  import { mkdirSync as mkdirSync29 } from "fs";
@@ -19745,11 +19845,15 @@ function mirrorOutcome(m) {
19745
19845
 
19746
19846
  // src/mirror-contract.ts
19747
19847
  function lacksCheckout(ship, dir) {
19748
- return ship.runner === "sandbox" && !existsSync40(dir);
19848
+ return ship.runner === "sandbox" && !existsSync39(dir) && !remoteOf(dir);
19749
19849
  }
19750
19850
  function mirrorRepoOfTarget(t) {
19751
19851
  return mirrorRepoOf({ remote: t.remote, forgeHost: t.forgeHost });
19752
19852
  }
19853
+ function cacheRepoOfTarget(ship, t, origin) {
19854
+ if (origin) return parseRemote(origin);
19855
+ return lacksCheckout(ship, t.dir) ? mirrorRepoOfTarget(t) : null;
19856
+ }
19753
19857
  async function readMirrorContract(cache, repo, base) {
19754
19858
  for (const name of REPO_CONFIG_FILENAMES) {
19755
19859
  const text2 = await cache.showFile(repo, base, name);
@@ -19758,10 +19862,13 @@ async function readMirrorContract(cache, repo, base) {
19758
19862
  return null;
19759
19863
  }
19760
19864
  function noCheckoutReleaseNote(repo, mode) {
19761
- if (mode === "local" || mode === null) {
19762
- 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`;
19763
19867
  }
19764
- 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";
19765
19872
  }
19766
19873
  var reported = /* @__PURE__ */ new Set();
19767
19874
  function firstReport(key) {
@@ -19770,6 +19877,144 @@ function firstReport(key) {
19770
19877
  return true;
19771
19878
  }
19772
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
+
19773
20018
  // src/whoami.ts
19774
20019
  async function buildWhoami(routes, deps = {}) {
19775
20020
  const doFetch = deps.fetch ?? fetch;
@@ -19847,7 +20092,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
19847
20092
  }
19848
20093
 
19849
20094
  // src/watch.ts
19850
- 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";
19851
20096
  var E = "\x1B[";
19852
20097
  var dim = (s) => `${E}2m${s}${E}0m`;
19853
20098
  var bold = (s) => `${E}1m${s}${E}0m`;
@@ -19948,7 +20193,7 @@ function renderHeaders(states, now = Date.now()) {
19948
20193
  return states.map((s) => renderHeader(s, now)).join("");
19949
20194
  }
19950
20195
  function readFrom(file, from) {
19951
- if (!existsSync41(file)) return { events: [], offset: from };
20196
+ if (!existsSync40(file)) return { events: [], offset: from };
19952
20197
  const size = statSync11(file).size;
19953
20198
  if (size < from) return readFrom(file, 0);
19954
20199
  if (size === from) return { events: [], offset: from };
@@ -20191,8 +20436,8 @@ function nextRoles(decision, ran) {
20191
20436
  }
20192
20437
 
20193
20438
  // src/ship-name.ts
20194
- import { existsSync as existsSync42, mkdirSync as mkdirSync30, readFileSync as readFileSync38, renameSync as renameSync5, writeFileSync as writeFileSync32 } from "fs";
20195
- 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";
20196
20441
  import { parseDocument as parseDocument5 } from "yaml";
20197
20442
  var ShipNameError = class extends Error {
20198
20443
  };
@@ -20253,7 +20498,7 @@ async function syncRouteName(i) {
20253
20498
  return { status: "ok", ...base, action, name: i.local };
20254
20499
  }
20255
20500
  function readSyncedName(resolvedFile) {
20256
- if (!existsSync42(resolvedFile)) return void 0;
20501
+ if (!existsSync41(resolvedFile)) return void 0;
20257
20502
  try {
20258
20503
  const v = JSON.parse(readFileSync38(resolvedFile, "utf8")).shipName;
20259
20504
  return typeof v === "string" ? v : void 0;
@@ -20262,7 +20507,7 @@ function readSyncedName(resolvedFile) {
20262
20507
  }
20263
20508
  }
20264
20509
  function writeSyncedName(resolvedFile, name) {
20265
- if (!existsSync42(resolvedFile)) return;
20510
+ if (!existsSync41(resolvedFile)) return;
20266
20511
  let raw;
20267
20512
  try {
20268
20513
  raw = JSON.parse(readFileSync38(resolvedFile, "utf8"));
@@ -20271,9 +20516,9 @@ function writeSyncedName(resolvedFile, name) {
20271
20516
  }
20272
20517
  if (raw.shipName === name) return;
20273
20518
  raw.shipName = name;
20274
- mkdirSync30(dirname26(resolvedFile), { recursive: true });
20519
+ mkdirSync31(dirname27(resolvedFile), { recursive: true });
20275
20520
  const tmp = `${resolvedFile}.tmp${process.pid}`;
20276
- writeFileSync32(tmp, `${JSON.stringify(raw, null, 2)}
20521
+ writeFileSync33(tmp, `${JSON.stringify(raw, null, 2)}
20277
20522
  `);
20278
20523
  renameSync5(tmp, resolvedFile);
20279
20524
  }
@@ -20305,7 +20550,7 @@ async function reconcileShipName(i) {
20305
20550
  if (result.status !== "ok") return result;
20306
20551
  if (result.action === "adopt" || result.action === "row-wins") {
20307
20552
  const text2 = readFileSync38(i.configFile, "utf8");
20308
- writeFileSync32(i.configFile, setShipNameInYaml(text2, result.name));
20553
+ writeFileSync33(i.configFile, setShipNameInYaml(text2, result.name));
20309
20554
  i.ship.name = result.name;
20310
20555
  if (result.action === "row-wins") {
20311
20556
  i.emit?.warn(
@@ -20964,8 +21209,8 @@ function describeCapability(i) {
20964
21209
  }
20965
21210
 
20966
21211
  // src/agents-sync-run.ts
20967
- import { existsSync as existsSync43, mkdirSync as mkdirSync31, readFileSync as readFileSync39, writeFileSync as writeFileSync33 } from "fs";
20968
- 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";
20969
21214
  async function runAgentsSync(target, o) {
20970
21215
  const dryRun = o.dryRun ?? false;
20971
21216
  const force = o.force ?? false;
@@ -20998,12 +21243,12 @@ async function runAgentsSync(target, o) {
20998
21243
  for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
20999
21244
  if (!dryRun) {
21000
21245
  const resolvedPath = resolvedPathFor(o.stateDir, target.route);
21001
- const raw = existsSync43(resolvedPath) ? JSON.parse(readFileSync39(resolvedPath, "utf8")) : {};
21246
+ const raw = existsSync42(resolvedPath) ? JSON.parse(readFileSync39(resolvedPath, "utf8")) : {};
21002
21247
  raw.agentPersonas = personaResult.agentPersonas;
21003
21248
  if (skillResult) raw.agentSkills = skillResult.agentSkills;
21004
21249
  if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
21005
- mkdirSync31(dirname27(resolvedPath), { recursive: true });
21006
- writeFileSync33(resolvedPath, `${JSON.stringify(raw, null, 2)}
21250
+ mkdirSync32(dirname28(resolvedPath), { recursive: true });
21251
+ writeFileSync34(resolvedPath, `${JSON.stringify(raw, null, 2)}
21007
21252
  `);
21008
21253
  if (target.resolved) {
21009
21254
  target.resolved.agentPersonas = personaResult.agentPersonas;
@@ -21457,7 +21702,7 @@ function existingRepoConfig(f) {
21457
21702
  }
21458
21703
 
21459
21704
  // src/repos-hooks-agent.ts
21460
- import { spawn as spawn8, execFileSync as execFileSync11 } from "child_process";
21705
+ import { spawn as spawn8, execFileSync as execFileSync12 } from "child_process";
21461
21706
  import { realpathSync as realpathSync2 } from "fs";
21462
21707
  import { isAbsolute as isAbsolute3, relative } from "path";
21463
21708
  var AgentSuggestError = class extends Error {
@@ -21560,7 +21805,7 @@ function classifyFailure(o) {
21560
21805
  }
21561
21806
  function porcelain(dir) {
21562
21807
  try {
21563
- 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" });
21564
21809
  } catch {
21565
21810
  return void 0;
21566
21811
  }
@@ -21629,7 +21874,7 @@ ${run3.err}` });
21629
21874
  }
21630
21875
 
21631
21876
  // src/sample-ticket.ts
21632
- 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";
21633
21878
  var SAMPLE_REPO = "crew-sample";
21634
21879
  var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
21635
21880
  var SAMPLE_MARKER = "sample: true";
@@ -21652,7 +21897,7 @@ has something safe to work on: its test and build hooks always pass, it has no
21652
21897
  remote, and \`crew sample-ticket --remove\` deletes it.
21653
21898
  `;
21654
21899
  function sampleDir(existing, reposBase, workspace) {
21655
- if (existing.length > 0) return join44(dirname28(existing[0]), SAMPLE_REPO);
21900
+ if (existing.length > 0) return join44(dirname29(existing[0]), SAMPLE_REPO);
21656
21901
  return join44(reposBase, workspace, SAMPLE_REPO);
21657
21902
  }
21658
21903
  var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
@@ -21697,8 +21942,8 @@ function planSampleTicket(i) {
21697
21942
  }
21698
21943
 
21699
21944
  // src/worktree-sweep.ts
21700
- import { existsSync as existsSync44, readFileSync as readFileSync41, readdirSync as readdirSync14 } from "fs";
21701
- 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";
21702
21947
  function planWorktreeSweep(target, tickets, contract) {
21703
21948
  const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
21704
21949
  const verified = contract.statuses.verified;
@@ -21762,7 +22007,7 @@ function worktreeTicketNumbers(target) {
21762
22007
  return [...nums];
21763
22008
  }
21764
22009
  function siblingWorktreeDirs(target) {
21765
- const parent = dirname29(target.dir.replace(/[/\\]+$/, ""));
22010
+ const parent = dirname30(target.dir.replace(/[/\\]+$/, ""));
21766
22011
  const stem = `${basename13(target.dir.replace(/[/\\]+$/, ""))}-`;
21767
22012
  const registered = new Set(worktrees(target.dir).map((w) => resolve5(w.path)));
21768
22013
  let names;
@@ -21810,7 +22055,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
21810
22055
  let removed = 0;
21811
22056
  let keptBranches = 0;
21812
22057
  for (const a of actions) {
21813
- 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)) {
21814
22059
  if (gitOk(a.path, ["merge", "--abort"]) !== null) {
21815
22060
  log.emit(
21816
22061
  `abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
@@ -21818,7 +22063,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
21818
22063
  );
21819
22064
  }
21820
22065
  }
21821
- if (!dryRun && existsSync44(a.path)) {
22066
+ if (!dryRun && existsSync43(a.path)) {
21822
22067
  let dirty = false;
21823
22068
  try {
21824
22069
  dirty = status(a.path).length > 0;
@@ -21829,7 +22074,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
21829
22074
  continue;
21830
22075
  }
21831
22076
  }
21832
- 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)) {
21833
22078
  refuse2(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
21834
22079
  continue;
21835
22080
  }
@@ -21983,12 +22228,12 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
21983
22228
  }
21984
22229
 
21985
22230
  // src/stream-sweep.ts
21986
- 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";
21987
22232
  import { join as join46 } from "path";
21988
22233
  var DAY_MS2 = 24 * 60 * 60 * 1e3;
21989
22234
  function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
21990
22235
  const dir = join46(stateDir, "streams");
21991
- if (!existsSync45(dir)) return [];
22236
+ if (!existsSync44(dir)) return [];
21992
22237
  const cutoff = now - retentionDays * DAY_MS2;
21993
22238
  return readdirSync15(dir).map((name) => join46(dir, name)).filter((path) => {
21994
22239
  try {
@@ -22016,7 +22261,7 @@ function applyStreamSweep(paths, dryRun, log) {
22016
22261
  return removed;
22017
22262
  }
22018
22263
  function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
22019
- if (!existsSync45(stateDir)) return [];
22264
+ if (!existsSync44(stateDir)) return [];
22020
22265
  const cutoff = now - retentionDays * DAY_MS2;
22021
22266
  return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join46(stateDir, name)).filter((path) => {
22022
22267
  try {
@@ -22028,11 +22273,11 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
22028
22273
  }
22029
22274
 
22030
22275
  // src/cli.ts
22031
- 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";
22032
22277
  import { createInterface } from "readline/promises";
22033
22278
 
22034
22279
  // src/periscope-cmd.ts
22035
- 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";
22036
22281
  import { join as join47 } from "path";
22037
22282
  var PeriscopeError = class extends Error {
22038
22283
  };
@@ -22050,15 +22295,15 @@ function removePeriscopePrior(stateDir, ticketRecordId) {
22050
22295
  }
22051
22296
  function writePeriscopePrior(stateDir, ticketRecordId, prior) {
22052
22297
  try {
22053
- mkdirSync32(priorDir(stateDir), { recursive: true });
22054
- writeFileSync34(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
22298
+ mkdirSync33(priorDir(stateDir), { recursive: true });
22299
+ writeFileSync35(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
22055
22300
  } catch {
22056
22301
  }
22057
22302
  }
22058
22303
  function readPeriscopePrior(stateDir, ticketRecordId) {
22059
22304
  try {
22060
22305
  const p = priorPath(stateDir, ticketRecordId);
22061
- return existsSync46(p) ? JSON.parse(readFileSync42(p, "utf8")) : void 0;
22306
+ return existsSync45(p) ? JSON.parse(readFileSync42(p, "utf8")) : void 0;
22062
22307
  } catch {
22063
22308
  return void 0;
22064
22309
  }
@@ -22510,7 +22755,7 @@ ${tail2(preview.output)}`);
22510
22755
  }
22511
22756
 
22512
22757
  // src/periscope-idle.ts
22513
- import { execFileSync as execFileSync12 } from "child_process";
22758
+ import { execFileSync as execFileSync13 } from "child_process";
22514
22759
  import { statSync as statSync13 } from "fs";
22515
22760
  import { join as join48 } from "path";
22516
22761
  var MAX_SCANNED_FILES = 2e4;
@@ -22522,7 +22767,7 @@ function newestWorktreeMtime(root) {
22522
22767
  function newestTrackedMtime(root) {
22523
22768
  let listing;
22524
22769
  try {
22525
- listing = execFileSync12("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
22770
+ listing = execFileSync13("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
22526
22771
  cwd: root,
22527
22772
  encoding: "utf8",
22528
22773
  stdio: ["ignore", "pipe", "ignore"],
@@ -22613,9 +22858,9 @@ async function runPeriscopeIdlePass(d) {
22613
22858
  import { dirname as dirOf2, resolve as resolvePath } from "path";
22614
22859
 
22615
22860
  // src/ssh-keys.ts
22616
- import { execFileSync as execFileSync13 } from "child_process";
22617
- import { existsSync as existsSync47, mkdirSync as mkdirSync33, readFileSync as readFileSync43, chmodSync as chmodSync3 } from "fs";
22618
- 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";
22619
22864
  var SshKeyError = class extends Error {
22620
22865
  };
22621
22866
  function shipSshKeyPathFor(stateDir) {
@@ -22624,10 +22869,10 @@ function shipSshKeyPathFor(stateDir) {
22624
22869
  function ensureShipSshKeypair(stateDir) {
22625
22870
  const privateKeyPath = shipSshKeyPathFor(stateDir);
22626
22871
  const publicKeyPath = `${privateKeyPath}.pub`;
22627
- if (!existsSync47(privateKeyPath)) {
22628
- mkdirSync33(dirname30(privateKeyPath), { recursive: true });
22872
+ if (!existsSync46(privateKeyPath)) {
22873
+ mkdirSync34(dirname31(privateKeyPath), { recursive: true });
22629
22874
  try {
22630
- execFileSync13("ssh-keygen", [
22875
+ execFileSync14("ssh-keygen", [
22631
22876
  "-t",
22632
22877
  "ed25519",
22633
22878
  "-N",
@@ -22645,7 +22890,7 @@ function ensureShipSshKeypair(stateDir) {
22645
22890
  }
22646
22891
  chmodSync3(privateKeyPath, 384);
22647
22892
  }
22648
- if (!existsSync47(publicKeyPath)) {
22893
+ if (!existsSync46(publicKeyPath)) {
22649
22894
  throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
22650
22895
  }
22651
22896
  const publicKey = readFileSync43(publicKeyPath, "utf8").trim();
@@ -22653,7 +22898,7 @@ function ensureShipSshKeypair(stateDir) {
22653
22898
  }
22654
22899
  function sshKeygenAvailable() {
22655
22900
  try {
22656
- execFileSync13("ssh-keygen", ["-V"], { stdio: ["ignore", "ignore", "ignore"] });
22901
+ execFileSync14("ssh-keygen", ["-V"], { stdio: ["ignore", "ignore", "ignore"] });
22657
22902
  return true;
22658
22903
  } catch (e) {
22659
22904
  return e.code !== "ENOENT";
@@ -22812,7 +23057,7 @@ async function unregisterEndpoint(workspaceId, info, d) {
22812
23057
  import { createServer as createHttpServer } from "http";
22813
23058
  import { spawn as spawn9 } from "child_process";
22814
23059
  import { randomBytes } from "crypto";
22815
- 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";
22816
23061
  import { join as join50 } from "path";
22817
23062
 
22818
23063
  // src/secret-compare.ts
@@ -22910,7 +23155,7 @@ function syncDaemonStatePath(stateDir, workspaceId) {
22910
23155
  }
22911
23156
  function readPersistedSyncDaemon(stateDir, workspaceId) {
22912
23157
  const path = syncDaemonStatePath(stateDir, workspaceId);
22913
- if (!existsSync48(path)) return void 0;
23158
+ if (!existsSync47(path)) return void 0;
22914
23159
  try {
22915
23160
  return JSON.parse(readFileSync44(path, "utf8"));
22916
23161
  } catch {
@@ -22919,8 +23164,8 @@ function readPersistedSyncDaemon(stateDir, workspaceId) {
22919
23164
  }
22920
23165
  function writePersistedSyncDaemon(stateDir, state) {
22921
23166
  const path = syncDaemonStatePath(stateDir, state.workspaceId);
22922
- mkdirSync34(join50(stateDir, "passengers"), { recursive: true });
22923
- writeFileSync35(path, `${JSON.stringify(state, null, 2)}
23167
+ mkdirSync35(join50(stateDir, "passengers"), { recursive: true });
23168
+ writeFileSync36(path, `${JSON.stringify(state, null, 2)}
22924
23169
  `);
22925
23170
  }
22926
23171
  function removePersistedSyncDaemon(stateDir, workspaceId) {
@@ -22941,7 +23186,7 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
22941
23186
  }
22942
23187
  function readAllPersistedSyncWorkspaceIds(stateDir) {
22943
23188
  const dir = join50(stateDir, "passengers");
22944
- if (!existsSync48(dir)) return [];
23189
+ if (!existsSync47(dir)) return [];
22945
23190
  const suffix = "-sync.json";
22946
23191
  return readdirSync16(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
22947
23192
  }
@@ -22999,12 +23244,12 @@ function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
22999
23244
 
23000
23245
  // src/periscope-proxy.ts
23001
23246
  import { spawn as spawn10 } from "child_process";
23002
- import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync35 } from "fs";
23247
+ import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync36 } from "fs";
23003
23248
  import { createServer, request as httpRequest } from "http";
23004
23249
  import { request as httpsRequest } from "https";
23005
23250
  import { connect as netConnect } from "net";
23006
23251
  import { connect as tlsConnect } from "tls";
23007
- import { dirname as dirname31, isAbsolute as isAbsolute4 } from "path";
23252
+ import { dirname as dirname32, isAbsolute as isAbsolute4 } from "path";
23008
23253
  import { brotliDecompressSync, gunzipSync, inflateSync } from "zlib";
23009
23254
 
23010
23255
  // src/periscope-inspector.ts
@@ -23118,7 +23363,7 @@ function parsePointedBody(raw) {
23118
23363
  return entry;
23119
23364
  }
23120
23365
  function appendPointed(file, entry, now = /* @__PURE__ */ new Date()) {
23121
- mkdirSync35(dirname31(file), { recursive: true });
23366
+ mkdirSync36(dirname32(file), { recursive: true });
23122
23367
  appendFileSync3(file, `${JSON.stringify({ ...entry, at: now.toISOString() })}
23123
23368
  `);
23124
23369
  }
@@ -23461,7 +23706,7 @@ function contractFixers(dir, baseRef, tickets, locate) {
23461
23706
  }
23462
23707
 
23463
23708
  // src/daemon.ts
23464
- 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";
23465
23710
  import { join as join52 } from "path";
23466
23711
  import { randomBytes as randomBytes2 } from "crypto";
23467
23712
  var DEFAULT_FLOOR_MS = 5e3;
@@ -23559,7 +23804,7 @@ function computeTreeSignature(paths) {
23559
23804
  }
23560
23805
  function makeStaleChecker(paths, opts = {}) {
23561
23806
  const initial = computeTreeSignature(paths);
23562
- return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync49(opts.reloadSentinel);
23807
+ return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync48(opts.reloadSentinel);
23563
23808
  }
23564
23809
  function reloadSentinelFor(stateDir) {
23565
23810
  return join52(stateDir, "reload-requested");
@@ -23575,9 +23820,9 @@ function daemonStampFor(stateDir) {
23575
23820
  return join52(stateDir, "daemon.started");
23576
23821
  }
23577
23822
  function noteDaemonStarted(stateDir, now = Date.now()) {
23578
- mkdirSync36(stateDir, { recursive: true });
23823
+ mkdirSync37(stateDir, { recursive: true });
23579
23824
  rmSync22(reloadSentinelFor(stateDir), { force: true });
23580
- writeFileSync36(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
23825
+ writeFileSync37(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
23581
23826
  `);
23582
23827
  }
23583
23828
  function readDaemonStarted(stateDir) {
@@ -24200,7 +24445,7 @@ import { createHash as createHash11 } from "crypto";
24200
24445
  import {
24201
24446
  chmodSync as chmodSync4,
24202
24447
  closeSync as closeSync6,
24203
- existsSync as existsSync50,
24448
+ existsSync as existsSync49,
24204
24449
  fstatSync,
24205
24450
  openSync as openSync6,
24206
24451
  readSync as readSync3,
@@ -24211,8 +24456,8 @@ import {
24211
24456
  } from "fs";
24212
24457
  import { createConnection } from "net";
24213
24458
  import { createServer as createServer2 } from "http";
24214
- import { basename as basename14, dirname as dirname32, join as join53 } from "path";
24215
- 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";
24216
24461
 
24217
24462
  // src/periscope-control.ts
24218
24463
  import { spawn as spawn11 } from "child_process";
@@ -24527,7 +24772,7 @@ function candidates(live, rotated) {
24527
24772
  const out = [live];
24528
24773
  if (!rotated) return out;
24529
24774
  try {
24530
- 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));
24531
24776
  } catch {
24532
24777
  }
24533
24778
  return out;
@@ -24571,7 +24816,7 @@ function lastLines2(path, n) {
24571
24816
  }
24572
24817
  function readTail(live, opts) {
24573
24818
  const max = opts.maxBytes ?? MAX_TAIL_BYTES;
24574
- if (!existsSync50(live)) {
24819
+ if (!existsSync49(live)) {
24575
24820
  return { lines: [], cursor: opts.after ?? encodeCursor({ dev: 0, ino: 0, off: 0 }), reset: false };
24576
24821
  }
24577
24822
  const cur = opts.after ? decodeCursor(opts.after) : null;
@@ -24687,7 +24932,7 @@ async function tailReply(path, q2, rotated, abort) {
24687
24932
  const deadline = Date.now() + Math.min(wait, MAX_WAIT_SECONDS) * 1e3;
24688
24933
  let r = readTail(path, { after, tail: tail3, rotated });
24689
24934
  while (after && !r.lines.length && !r.reset && Date.now() < deadline && !abort.aborted) {
24690
- await waitForChange(dirname32(path), deadline - Date.now(), abort);
24935
+ await waitForChange(dirname33(path), deadline - Date.now(), abort);
24691
24936
  r = readTail(path, { after: r.cursor, rotated });
24692
24937
  }
24693
24938
  return reply2(200, r);
@@ -24728,7 +24973,7 @@ async function route(deps, method, url, req, abort) {
24728
24973
  const run3 = decodeURIComponent(p.slice("/streams/".length));
24729
24974
  if (!RUN_NAME_RE.test(run3) || run3.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run3)) return err2(400, "not a run name");
24730
24975
  const file = join53(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
24731
- if (!existsSync50(file)) return err2(404, "no such run");
24976
+ if (!existsSync49(file)) return err2(404, "no such run");
24732
24977
  return tailReply(file, url.searchParams, false, abort);
24733
24978
  }
24734
24979
  if ((p === "/pause" || p === "/resume") && post3) {
@@ -24772,7 +25017,7 @@ function send(res, r) {
24772
25017
  res.end(body);
24773
25018
  }
24774
25019
  async function socketIsStale(path) {
24775
- if (!existsSync50(path)) return false;
25020
+ if (!existsSync49(path)) return false;
24776
25021
  return new Promise((resolve8) => {
24777
25022
  const c = createConnection(path);
24778
25023
  c.once("connect", () => {
@@ -24791,7 +25036,7 @@ async function startControlServer(deps) {
24791
25036
  return null;
24792
25037
  }
24793
25038
  if (await socketIsStale(path)) rmSync23(path, { force: true });
24794
- else if (existsSync50(path)) {
25039
+ else if (existsSync49(path)) {
24795
25040
  log(`control API not started: ${path} is in use by another process`);
24796
25041
  return null;
24797
25042
  }
@@ -24864,7 +25109,7 @@ function schedulerHandler(control, run3 = runDetached) {
24864
25109
  }
24865
25110
  function crewJson(args, timeoutMs = 6e4) {
24866
25111
  return new Promise((resolve8) => {
24867
- 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) => {
24868
25113
  try {
24869
25114
  const v = JSON.parse(String(stdout));
24870
25115
  resolve8(v && typeof v === "object" && !Array.isArray(v) ? v : null);
@@ -24877,24 +25122,24 @@ function crewJson(args, timeoutMs = 6e4) {
24877
25122
 
24878
25123
  // src/periscope-mcp.ts
24879
25124
  import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
24880
- 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";
24881
25126
  import { z } from "zod";
24882
25127
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
24883
25128
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
24884
25129
 
24885
25130
  // src/periscope-endpoints.ts
24886
25131
  import { randomBytes as randomBytes3 } from "crypto";
24887
- 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";
24888
25133
  import { join as join54 } from "path";
24889
25134
  var secretDir = (stateDir) => join54(stateDir, "periscope-mcp");
24890
25135
  var secretFile = (stateDir, id) => join54(secretDir(stateDir), `${id}.json`);
24891
25136
  var endpointDir = (stateDir) => join54(stateDir, "periscope-endpoints");
24892
25137
  var endpointFile = (stateDir, id) => join54(endpointDir(stateDir), `${id}.json`);
24893
25138
  function mintPeriscopeSecret(stateDir, ticketRecordId) {
24894
- mkdirSync37(secretDir(stateDir), { recursive: true, mode: 448 });
25139
+ mkdirSync38(secretDir(stateDir), { recursive: true, mode: 448 });
24895
25140
  const secret = randomBytes3(32).toString("base64url");
24896
25141
  const file = secretFile(stateDir, ticketRecordId);
24897
- writeFileSync37(file, `${JSON.stringify({ secret })}
25142
+ writeFileSync38(file, `${JSON.stringify({ secret })}
24898
25143
  `, { mode: 384 });
24899
25144
  chmodSync5(file, 384);
24900
25145
  return secret;
@@ -24918,8 +25163,8 @@ function readPeriscopeEndpoint(stateDir, ticketRecordId) {
24918
25163
  }
24919
25164
  }
24920
25165
  function writePeriscopeEndpoint(stateDir, rec) {
24921
- mkdirSync37(endpointDir(stateDir), { recursive: true });
24922
- 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)}
24923
25168
  `);
24924
25169
  }
24925
25170
  function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
@@ -24927,7 +25172,7 @@ function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
24927
25172
  }
24928
25173
  function listPeriscopeEndpoints(stateDir) {
24929
25174
  const dir = endpointDir(stateDir);
24930
- if (!existsSync51(dir)) return [];
25175
+ if (!existsSync50(dir)) return [];
24931
25176
  const out = [];
24932
25177
  for (const name of readdirSync19(dir)) {
24933
25178
  if (!name.endsWith(".json")) continue;
@@ -25015,10 +25260,10 @@ async function realAncestor(p) {
25015
25260
  }
25016
25261
  if (isLink) {
25017
25262
  if (++hops > 40) throw new PathRefusal(`too many symbolic links resolving ${p}`);
25018
- cur = resolve6(dirname33(cur), await readlink(cur));
25263
+ cur = resolve6(dirname34(cur), await readlink(cur));
25019
25264
  continue;
25020
25265
  }
25021
- const parent = dirname33(cur);
25266
+ const parent = dirname34(cur);
25022
25267
  if (parent === cur) throw new PathRefusal(`cannot resolve ${p}`);
25023
25268
  tail3.push(basename15(cur));
25024
25269
  cur = parent;
@@ -25129,7 +25374,7 @@ function createPeriscopeMcpServer(d, sc) {
25129
25374
  if (Buffer.byteLength(content) > MAX_WRITE_BYTES) return text(`content exceeds ${MAX_WRITE_BYTES} bytes`, true);
25130
25375
  const abs = await inTree(path);
25131
25376
  if (abs === await realpath(root)) return text("path must name a file", true);
25132
- await mkdir(dirname33(abs), { recursive: true });
25377
+ await mkdir(dirname34(abs), { recursive: true });
25133
25378
  await inTree(path);
25134
25379
  await writeFile(abs, content);
25135
25380
  return text(`Wrote ${Buffer.byteLength(content)} bytes to ${await rel(abs)}`);
@@ -25169,7 +25414,7 @@ function createPeriscopeMcpServer(d, sc) {
25169
25414
  inputSchema: { path: z.string() },
25170
25415
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
25171
25416
  }, async ({ path }) => {
25172
- const parent = await inTree(dirname33(path) === "" ? "." : dirname33(path));
25417
+ const parent = await inTree(dirname34(path) === "" ? "." : dirname34(path));
25173
25418
  const abs = join55(parent, basename15(path));
25174
25419
  if (basename15(path) === ".git" || abs === parent) return text("path must name a file", true);
25175
25420
  const st = await lstat(abs).catch(() => void 0);
@@ -25291,7 +25536,7 @@ function findPeriscope(sidecars, segment) {
25291
25536
  }
25292
25537
 
25293
25538
  // src/cli.ts
25294
- 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)), "..");
25295
25540
  var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
25296
25541
  var CYCLE_SECONDS2 = 120;
25297
25542
  var STALE_CYCLES = 3;
@@ -25810,7 +26055,7 @@ if (command === "passenger-sync-daemon") {
25810
26055
  let images;
25811
26056
  for (const t of reposOf(c)) {
25812
26057
  const origin = originUrlOf(t.dir, "origin");
25813
- const repo = origin ? parseRemote(origin) : null;
26058
+ const repo = cacheRepoOfTarget(ship, t, origin);
25814
26059
  if (!repo) continue;
25815
26060
  let token = null;
25816
26061
  try {
@@ -25910,7 +26155,7 @@ if (command === "passenger-sync-daemon") {
25910
26155
  } else {
25911
26156
  const toleratesNoRoutes = command === "credential" || command === "forge" || command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "deprovision" || command === "connect" && flag("sign-out");
25912
26157
  const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset") || command === "deprovision";
25913
- if (configless && !existsSync52(findConfigFile(CREW_HOME))) {
26158
+ if (configless && !existsSync51(findConfigFile(CREW_HOME))) {
25914
26159
  cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
25915
26160
  } else {
25916
26161
  cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
@@ -25938,7 +26183,7 @@ if (command === "passenger-sync-daemon") {
25938
26183
  stateDir: cfg.ship.stateDir,
25939
26184
  configFile: cfg.configFile,
25940
26185
  readFile: (p) => readFileSync47(p, "utf8"),
25941
- writeFile: (p, t) => writeFileSync38(p, t),
26186
+ writeFile: (p, t) => writeFileSync39(p, t),
25942
26187
  stdin: () => readFileSync47(0, "utf8"),
25943
26188
  env: process.env
25944
26189
  }
@@ -26263,7 +26508,7 @@ if (command === "passenger-sync-daemon") {
26263
26508
  if (host === "linux" && hasSystemd) {
26264
26509
  const label = labelFor(CREW_HOME, "run");
26265
26510
  const staleTimer = join56(homedir11(), ".config", "systemd", "user", `${label}.timer`);
26266
- if (existsSync52(staleTimer)) {
26511
+ if (existsSync51(staleTimer)) {
26267
26512
  process.stdout.write(
26268
26513
  `removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
26269
26514
  `
@@ -26292,7 +26537,7 @@ if (command === "passenger-sync-daemon") {
26292
26537
  const { settled: doctorRepos, errors: refusedRepos } = await settleRepos(reposOf(c), (t) => t.name, (t) => resolveOneRepo(c, t));
26293
26538
  for (const e of refusedRepos) problems.push(`repo ${e.repo}: ${e.detail}`);
26294
26539
  for (const r of doctorRepos) {
26295
- 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`);
26296
26541
  if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
26297
26542
  readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
26298
26543
  }
@@ -26319,7 +26564,7 @@ if (command === "passenger-sync-daemon") {
26319
26564
  }
26320
26565
  async function doctorFix(o) {
26321
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))));
26322
- const unitExists = (p) => existsSync52(p);
26567
+ const unitExists = (p) => existsSync51(p);
26323
26568
  const crontab = o.host === "linux" ? spawnSync12("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
26324
26569
  const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run", installContext()), unitExists, crontab) : true;
26325
26570
  const plan = planFix({ routes, schedulerAbsent, host: o.host });
@@ -26336,7 +26581,7 @@ if (command === "passenger-sync-daemon") {
26336
26581
  for (const a of plan.actions) {
26337
26582
  if (a.kind === "enable") {
26338
26583
  if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
26339
- writeFileSync38(cfg.configFile, planEnable(readFileSync47(cfg.configFile, "utf8"), a.route));
26584
+ writeFileSync39(cfg.configFile, planEnable(readFileSync47(cfg.configFile, "utf8"), a.route));
26340
26585
  enabledSound.add(a.route);
26341
26586
  changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
26342
26587
  } else {
@@ -26380,6 +26625,13 @@ ${changed2.map((c) => ` - ${c}
26380
26625
  return [...out.values()];
26381
26626
  }
26382
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
+ }
26383
26635
  async function resolveOneRepo(c, t) {
26384
26636
  {
26385
26637
  const id = repoIdForName(c, t.name);
@@ -26388,7 +26640,7 @@ ${changed2.map((c) => ` - ${c}
26388
26640
  name: t.name,
26389
26641
  dir: t.dir,
26390
26642
  id,
26391
- config: resolveRepoConfig(loadRepoConfig(t.dir), {
26643
+ config: resolveRepoConfig(await loadContractOf(c, t), {
26392
26644
  hooks: { ...c.hooks, ...o?.hooks },
26393
26645
  labels: { ...c.labels, ...o?.labels },
26394
26646
  release: mergeRouteRelease(c.release, o?.release),
@@ -26444,6 +26696,7 @@ ${changed2.map((c) => ` - ${c}
26444
26696
  return `${who2}; ${lock2}`;
26445
26697
  }
26446
26698
  async function sweepClosedTickets(c, target, branch, tracker, dryRun2, remit, idle = false) {
26699
+ if (remoteOf(target.dir)) return;
26447
26700
  try {
26448
26701
  if (!dryRun2) reconcileHeldWorktrees(state, target.name, remit);
26449
26702
  if (idle && !sweepCandidatesExist(target.dir, branch.base)) return;
@@ -26473,20 +26726,86 @@ ${changed2.map((c) => ` - ${c}
26473
26726
  }
26474
26727
  const precheckTickets = /* @__PURE__ */ new Map();
26475
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
+ }
26476
26790
  async function releasePhase(c, target, opts = {}) {
26477
26791
  const scope = releaseLockScope(c.route, target.name);
26478
26792
  const remit = emit.forRoute(c.route);
26479
26793
  if (lacksCheckout(cfg.ship, target.dir)) {
26480
26794
  let mode = null;
26795
+ let contract = null;
26481
26796
  const settings = cacheSettingsOf(cfg.ship.sandbox);
26482
26797
  const mirror = mirrorRepoOfTarget(target);
26483
26798
  if (settings && mirror) {
26484
26799
  try {
26485
- 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;
26486
26802
  } catch (e) {
26487
26803
  if (firstReport(`${scope0(c, target)}:contract`)) remit.warn(`${target.name}: could not read the contract from the mirror: ${e.message}`, { step: "release" });
26488
26804
  }
26489
26805
  }
26806
+ if (releasesFromSandbox(mode) && settings && mirror && contract) {
26807
+ return releaseFromSandbox(c, target, contract, mirror, opts);
26808
+ }
26490
26809
  const note = noCheckoutReleaseNote(target.name, mode);
26491
26810
  if (firstReport(`${scope0(c, target)}:release`)) remit.emit(note, { step: "release" });
26492
26811
  return { scope: releaseLockScope(c.route, target.name), tests: "skipped", outcome: "skipped", detail: note };
@@ -27837,10 +28156,10 @@ ${cu.tail.trimEnd()}
27837
28156
  `);
27838
28157
  if (!dryRun) {
27839
28158
  const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
27840
- const raw = existsSync52(resolvedPath) ? JSON.parse(readFileSync47(resolvedPath, "utf8")) : {};
28159
+ const raw = existsSync51(resolvedPath) ? JSON.parse(readFileSync47(resolvedPath, "utf8")) : {};
27841
28160
  raw.agentSkills = result.agentSkills;
27842
- mkdirSync38(dirOf2(resolvedPath), { recursive: true });
27843
- writeFileSync38(resolvedPath, `${JSON.stringify(raw, null, 2)}
28161
+ mkdirSync39(dirOf2(resolvedPath), { recursive: true });
28162
+ writeFileSync39(resolvedPath, `${JSON.stringify(raw, null, 2)}
27844
28163
  `);
27845
28164
  }
27846
28165
  if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
@@ -27863,7 +28182,7 @@ ${cu.tail.trimEnd()}
27863
28182
  throw e;
27864
28183
  }
27865
28184
  const oldName = cfg.ship.name;
27866
- writeFileSync38(cfg.configFile, setShipNameInYaml(readFileSync47(cfg.configFile, "utf8"), newName));
28185
+ writeFileSync39(cfg.configFile, setShipNameInYaml(readFileSync47(cfg.configFile, "utf8"), newName));
27867
28186
  cfg.ship.name = newName;
27868
28187
  let renameMachineId;
27869
28188
  try {
@@ -27917,7 +28236,7 @@ ${cu.tail.trimEnd()}
27917
28236
  const found = cfg.routes.find((r) => r.route === routeArg);
27918
28237
  if (!found) throw new ReposError(`no route "${routeArg}" in the config (have: ${cfg.routes.map((r) => r.route).join(", ") || "none"})`);
27919
28238
  const named2 = value("route") ? void 0 : positional[3];
27920
- 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] ?? ".");
27921
28240
  const target = reposOf(found).find((t) => resolvePath(t.dir) === dir || t.name === named2);
27922
28241
  const o = target ? found.repoOverrides[target.name] : void 0;
27923
28242
  runReposCheck(dir, {
@@ -27941,9 +28260,9 @@ ${cu.tail.trimEnd()}
27941
28260
  if (sub === "hooks") {
27942
28261
  try {
27943
28262
  const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
27944
- const dir = repoDirOf(configText, dirname34(cfg.configFile), a.route, a.name);
27945
- if (!existsSync52(dir)) throw new ReposError(`${dir} does not exist on disk`);
27946
- 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) => {
27947
28266
  try {
27948
28267
  return readdirSync20(resolvePath(dir, r));
27949
28268
  } catch {
@@ -27993,19 +28312,19 @@ ${cu.tail.trimEnd()}
27993
28312
  let cleared;
27994
28313
  if (clear2) {
27995
28314
  const plan = clearRouteHooks(configText, a.route, a.name);
27996
- if (plan.removed.length > 0) writeFileSync38(cfg.configFile, plan.text);
28315
+ if (plan.removed.length > 0) writeFileSync39(cfg.configFile, plan.text);
27997
28316
  cleared = { file: cfg.configFile, removed: plan.removed };
27998
28317
  }
27999
28318
  if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
28000
28319
  if (write === "route") {
28001
28320
  const plan = planRouteHooks(configText, a.route, a.name, picked.hooks, { replace: flag("replace") });
28002
- writeFileSync38(cfg.configFile, plan.text);
28321
+ writeFileSync39(cfg.configFile, plan.text);
28003
28322
  written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept, replaced: plan.replaced };
28004
28323
  } else if (write === "repo") {
28005
28324
  const have = existingRepoConfig(files);
28006
28325
  if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
28007
28326
  const file = resolvePath(dir, ".crew.yaml");
28008
- writeFileSync38(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
28327
+ writeFileSync39(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
28009
28328
  written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [], replaced: [] };
28010
28329
  }
28011
28330
  if (flag("json")) {
@@ -28061,7 +28380,7 @@ ${cu.tail.trimEnd()}
28061
28380
  if (sub === "list" && !routeName) usage2();
28062
28381
  if (sub === "add") {
28063
28382
  try {
28064
- 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()))));
28065
28384
  routeName = a.route;
28066
28385
  addPath = a.path;
28067
28386
  addName = a.name;
@@ -28091,7 +28410,7 @@ ${cu.tail.trimEnd()}
28091
28410
  const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
28092
28411
  if (sub === "list" || sub === "add") {
28093
28412
  const liveRoute = cfg.routes.find((r) => r.route === routeName);
28094
- if (liveRoute?.resolved?.reposModelId && existsSync52(resolvedFile) && !dryRun) {
28413
+ if (liveRoute?.resolved?.reposModelId && existsSync51(resolvedFile) && !dryRun) {
28095
28414
  try {
28096
28415
  await hydrateApiKeys([liveRoute]);
28097
28416
  persistRepoRows(cfg.ship.stateDir, liveRoute.route, await new Tracker(liveRoute, cfg.ship).repoRowsForMap());
@@ -28100,7 +28419,7 @@ ${cu.tail.trimEnd()}
28100
28419
  }
28101
28420
  }
28102
28421
  let resolvedRemotes;
28103
- if (existsSync52(resolvedFile)) {
28422
+ if (existsSync51(resolvedFile)) {
28104
28423
  try {
28105
28424
  resolvedRemotes = JSON.parse(readFileSync47(resolvedFile, "utf8"));
28106
28425
  } catch {
@@ -28108,8 +28427,8 @@ ${cu.tail.trimEnd()}
28108
28427
  }
28109
28428
  const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
28110
28429
  const text2 = configText;
28111
- const base = dirname34(cfg.configFile);
28112
- const isCheckout = (d) => existsSync52(`${d}/.git`);
28430
+ const base = dirname35(cfg.configFile);
28431
+ const isCheckout = (d) => existsSync51(`${d}/.git`);
28113
28432
  try {
28114
28433
  if (sub === "list") {
28115
28434
  if (flag("json")) {
@@ -28119,7 +28438,7 @@ ${cu.tail.trimEnd()}
28119
28438
  base,
28120
28439
  resolved: resolvedRemotes,
28121
28440
  originOf,
28122
- exists: (d) => existsSync52(d),
28441
+ exists: (d) => existsSync51(d),
28123
28442
  isCheckout,
28124
28443
  repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
28125
28444
  try {
@@ -28141,7 +28460,7 @@ ${cu.tail.trimEnd()}
28141
28460
  for (const r of repos) {
28142
28461
  const origin2 = originOf(r.dir);
28143
28462
  const row = matchRepoName(origin2, resolvedRemotes);
28144
- 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)"}`;
28145
28464
  process.stdout.write(`${r.name} ${r.dir} ${note}
28146
28465
  `);
28147
28466
  }
@@ -28153,9 +28472,9 @@ ${cu.tail.trimEnd()}
28153
28472
  ` : `${human}
28154
28473
  `);
28155
28474
  const plan2 = planRepoClone({ text: text2, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
28156
- 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)) {
28157
28476
  throw new ReposError(
28158
- 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`
28159
28478
  );
28160
28479
  }
28161
28480
  if (dryRun) {
@@ -28173,14 +28492,14 @@ ${cu.tail.trimEnd()}
28173
28492
  throw new ReposError(`${e.message}${authHint}
28174
28493
  crew.yaml was not changed`);
28175
28494
  }
28176
- writeFileSync38(cfg.configFile, plan2.text);
28495
+ writeFileSync39(cfg.configFile, plan2.text);
28177
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:
28178
28497
  `);
28179
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}`);
28180
28499
  break;
28181
28500
  }
28182
28501
  const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir11()));
28183
- const top = existsSync52(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
28502
+ const top = existsSync51(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
28184
28503
  if (!top) throw new ReposError(`${target} is not a git checkout`);
28185
28504
  const origin = originOf(top);
28186
28505
  const plan = planRepoAdd({
@@ -28232,15 +28551,15 @@ ${cu.tail.trimEnd()}
28232
28551
  const created = await rowTracker.createRepoRow(rowPayload);
28233
28552
  process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
28234
28553
  `);
28235
- if (existsSync52(resolvedFile)) {
28554
+ if (existsSync51(resolvedFile)) {
28236
28555
  const raw = JSON.parse(readFileSync47(resolvedFile, "utf8"));
28237
28556
  raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
28238
28557
  raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
28239
- writeFileSync38(resolvedFile, `${JSON.stringify(raw, null, 2)}
28558
+ writeFileSync39(resolvedFile, `${JSON.stringify(raw, null, 2)}
28240
28559
  `);
28241
28560
  }
28242
28561
  }
28243
- writeFileSync38(cfg.configFile, plan.text);
28562
+ writeFileSync39(cfg.configFile, plan.text);
28244
28563
  process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
28245
28564
  `);
28246
28565
  } catch (e) {
@@ -28612,7 +28931,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
28612
28931
  process.exit(2);
28613
28932
  }
28614
28933
  if (found.shipNameAdopted && !dryRun) {
28615
- writeFileSync38(cfg.configFile, setShipNameInYaml(readFileSync47(cfg.configFile, "utf8"), found.shipNameAdopted));
28934
+ writeFileSync39(cfg.configFile, setShipNameInYaml(readFileSync47(cfg.configFile, "utf8"), found.shipNameAdopted));
28616
28935
  cfg.ship.name = found.shipNameAdopted;
28617
28936
  }
28618
28937
  if (found.shipRow) {
@@ -28655,7 +28974,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
28655
28974
  }
28656
28975
  }
28657
28976
  let previous;
28658
- const hadPreviousFile = existsSync52(resolvedPath);
28977
+ const hadPreviousFile = existsSync51(resolvedPath);
28659
28978
  if (hadPreviousFile) {
28660
28979
  try {
28661
28980
  previous = JSON.parse(readFileSync47(resolvedPath, "utf8"));
@@ -28788,10 +29107,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
28788
29107
  process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
28789
29108
  `);
28790
29109
  } else {
28791
- mkdirSync38(dirname34(resolvedPath), { recursive: true });
29110
+ mkdirSync39(dirname35(resolvedPath), { recursive: true });
28792
29111
  if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
28793
29112
  const tmpPath = `${resolvedPath}.tmp${process.pid}`;
28794
- writeFileSync38(tmpPath, `${JSON.stringify(resolved, null, 2)}
29113
+ writeFileSync39(tmpPath, `${JSON.stringify(resolved, null, 2)}
28795
29114
  `);
28796
29115
  renameSync6(tmpPath, resolvedPath);
28797
29116
  process.stderr.write(`Resolved ids written to ${resolvedPath}
@@ -28837,8 +29156,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
28837
29156
  process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
28838
29157
  `);
28839
29158
  } else {
28840
- mkdirSync38(dirname34(apiKeyFile), { recursive: true });
28841
- writeFileSync38(apiKeyFile, `${apiKeyVar}=${apiKey}
29159
+ mkdirSync39(dirname35(apiKeyFile), { recursive: true });
29160
+ writeFileSync39(apiKeyFile, `${apiKeyVar}=${apiKey}
28842
29161
  `, { mode: 384 });
28843
29162
  process.stderr.write(`API key written to ${apiKeyFile}
28844
29163
  `);
@@ -29542,8 +29861,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
29542
29861
  prepare: async (path) => {
29543
29862
  for (const f of repoWt.config.worktrees.copy) {
29544
29863
  const src = join56(repoWt.dir, f);
29545
- if (!existsSync52(src) || existsSync52(join56(path, f))) continue;
29546
- mkdirSync38(dirOf2(join56(path, f)), { recursive: true });
29864
+ if (!existsSync51(src) || existsSync51(join56(path, f))) continue;
29865
+ mkdirSync39(dirOf2(join56(path, f)), { recursive: true });
29547
29866
  copyFileSync2(src, join56(path, f));
29548
29867
  }
29549
29868
  const setup = repoWt.config.hooks.setup;
@@ -29765,7 +30084,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
29765
30084
  const shellR = hit && resolveShell(hit.repo.config.shell ?? cfg.ship.shell);
29766
30085
  const resumed = await runPeriscopeResume({
29767
30086
  find: async () => hit && { row: hit.row, tag: hit.tag },
29768
- worktreeExists: (p) => existsSync52(p),
30087
+ worktreeExists: (p) => existsSync51(p),
29769
30088
  prepareDropFile,
29770
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" }),
29771
30090
  caps: periscopeCaps(cfg.ship),
@@ -29862,8 +30181,8 @@ ${where.join("\n")}`);
29862
30181
  prepare: async (path) => {
29863
30182
  for (const f of repoP.config.worktrees.copy) {
29864
30183
  const src = join56(repoP.dir, f);
29865
- if (!existsSync52(src)) continue;
29866
- mkdirSync38(dirOf2(join56(path, f)), { recursive: true });
30184
+ if (!existsSync51(src)) continue;
30185
+ mkdirSync39(dirOf2(join56(path, f)), { recursive: true });
29867
30186
  copyFileSync2(src, join56(path, f));
29868
30187
  }
29869
30188
  const setup = repoP.config.hooks.setup;
@@ -30224,12 +30543,23 @@ ${where.join("\n")}`);
30224
30543
  const control = host === "windows" ? null : planDaemonControl(CREW_HOME, host, detectSystemd());
30225
30544
  const unitStatus = control?.statusCommand ? spawnSync12(control.statusCommand[0], control.statusCommand.slice(1), { cwd: CREW_HOME, encoding: "utf8" }) : null;
30226
30545
  const unitRunning = unitStatus !== null && !unitStatus.error && unitStatus.status === 0;
30546
+ if (!unitRunning && control?.mechanism === "systemd" && control.startCommand) {
30547
+ const res = spawnSync12(control.startCommand[0], control.startCommand.slice(1), { cwd: CREW_HOME, encoding: "utf8" });
30548
+ if (res.error || res.status !== 0) {
30549
+ process.stderr.write(`crew reload: ${control.startCommand.join(" ")} exited ${res.status ?? "error"}
30550
+ `);
30551
+ process.exitCode = 1;
30552
+ } else {
30553
+ process.stdout.write("crew reload: the daemon unit was not running \u2014 started it, so it reads crew.yaml fresh\n");
30554
+ }
30555
+ break;
30556
+ }
30227
30557
  if (!unitRunning) {
30228
30558
  process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
30229
30559
  break;
30230
30560
  }
30231
- mkdirSync38(cfg.ship.stateDir, { recursive: true });
30232
- writeFileSync38(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
30561
+ mkdirSync39(cfg.ship.stateDir, { recursive: true });
30562
+ writeFileSync39(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
30233
30563
  `);
30234
30564
  process.stdout.write(
30235
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"
@@ -30318,7 +30648,7 @@ ${where.join("\n")}`);
30318
30648
  if (!route2.resolved) fail2(`route ${route2.route} is not connected \u2014 run \`crew connect\` first`);
30319
30649
  const tracker = new Tracker(route2, cfg.ship);
30320
30650
  const configText = readFileSync47(cfg.configFile, "utf8");
30321
- const cfgBase = dirname34(cfg.configFile);
30651
+ const cfgBase = dirname35(cfg.configFile);
30322
30652
  const configured2 = configuredRepos(configText, route2.route, cfgBase);
30323
30653
  const dir = configured2.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured2.map((r) => r.dir), route2.reposBasePath, route2.route.split("/")[0]);
30324
30654
  const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route2.route);
@@ -30346,7 +30676,7 @@ ${where.join("\n")}`);
30346
30676
  }
30347
30677
  if (configured2.some((r) => r.dir === dir)) {
30348
30678
  try {
30349
- writeFileSync38(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
30679
+ writeFileSync39(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
30350
30680
  } catch (e) {
30351
30681
  if (e instanceof ReposError) fail2(e.message);
30352
30682
  throw e;
@@ -30354,29 +30684,29 @@ ${where.join("\n")}`);
30354
30684
  }
30355
30685
  if (row) {
30356
30686
  await tracker.deleteRepoRow(row.id);
30357
- if (existsSync52(resolvedFile)) {
30687
+ if (existsSync51(resolvedFile)) {
30358
30688
  const raw = JSON.parse(readFileSync47(resolvedFile, "utf8"));
30359
30689
  if (raw.repoNames) delete raw.repoNames[row.id];
30360
30690
  if (raw.repoRemotes) delete raw.repoRemotes[row.id];
30361
- writeFileSync38(resolvedFile, `${JSON.stringify(raw, null, 2)}
30691
+ writeFileSync39(resolvedFile, `${JSON.stringify(raw, null, 2)}
30362
30692
  `);
30363
30693
  }
30364
30694
  }
30365
- 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 });
30366
30696
  say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
30367
30697
  break;
30368
30698
  }
30369
30699
  const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
30370
- const fresh = !existsSync52(join56(dir, ".git"));
30371
- 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`);
30372
30702
  if (dryRun) {
30373
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`);
30374
30704
  break;
30375
30705
  }
30376
30706
  if (fresh) {
30377
- mkdirSync38(dir, { recursive: true });
30378
- writeFileSync38(join56(dir, "README.md"), SAMPLE_README);
30379
- 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);
30380
30710
  const g = (...a) => {
30381
30711
  const r = spawnSync12("git", a, { cwd: dir, encoding: "utf8" });
30382
30712
  if (r.status !== 0) fail2(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
@@ -30394,17 +30724,17 @@ ${where.join("\n")}`);
30394
30724
  spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
30395
30725
  });
30396
30726
  repoId = (await tracker.createRepoRow(payload)).id;
30397
- if (existsSync52(resolvedFile)) {
30727
+ if (existsSync51(resolvedFile)) {
30398
30728
  const raw = JSON.parse(readFileSync47(resolvedFile, "utf8"));
30399
30729
  raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
30400
30730
  raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
30401
- writeFileSync38(resolvedFile, `${JSON.stringify(raw, null, 2)}
30731
+ writeFileSync39(resolvedFile, `${JSON.stringify(raw, null, 2)}
30402
30732
  `);
30403
30733
  }
30404
30734
  }
30405
30735
  if (!configured2.some((r) => r.dir === dir)) {
30406
30736
  try {
30407
- 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);
30408
30738
  } catch (e) {
30409
30739
  if (e instanceof ReposError) fail2(e.message);
30410
30740
  throw e;
@@ -30750,7 +31080,7 @@ watermark: ${state.watermark()}
30750
31080
  }
30751
31081
  for (const r of repos) {
30752
31082
  const remote = r.config.branch.remote;
30753
- if (existsSync52(r.dir)) {
31083
+ if (existsSync51(r.dir)) {
30754
31084
  pushAuth.push({
30755
31085
  route: c.route,
30756
31086
  repo: r.name,
@@ -30873,7 +31203,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
30873
31203
  }
30874
31204
  try {
30875
31205
  const evFile = eventFileFor(cfg.ship.stateDir);
30876
- const evSize = existsSync52(evFile) ? statSync16(evFile).size : 0;
31206
+ const evSize = existsSync51(evFile) ? statSync16(evFile).size : 0;
30877
31207
  process.stdout.write(
30878
31208
  evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
30879
31209
  ` : "events log: ok\n"
@@ -30895,6 +31225,16 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
30895
31225
  );
30896
31226
  }
30897
31227
  }
31228
+ if (host === "linux" && detectSystemd()) {
31229
+ const ctl = planDaemonControl(CREW_HOME, host, true);
31230
+ const unitPlan = planInstall(cfg.ship, CREW_HOME, host, true, "run", installContext());
31231
+ if (ctl.statusCommand && unitPlan.unitPaths.some((p) => existsSync51(p))) {
31232
+ const st = spawnSync12(ctl.statusCommand[0], ctl.statusCommand.slice(1), { cwd: CREW_HOME, encoding: "utf8" });
31233
+ if (!st.error && st.status !== 0) {
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");
31235
+ }
31236
+ }
31237
+ }
30898
31238
  const reloadAt = readReloadRequestedAt(cfg.ship.stateDir);
30899
31239
  if (reloadAt !== null) {
30900
31240
  process.stdout.write(