@tablation/crew 0.126.6 → 0.126.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +202 -118
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -12004,6 +12004,21 @@ function slugFor(route2, agentId) {
12004
12004
  const [workspaceSlug] = splitRoute(route2.route);
12005
12005
  return seatKeySlug(workspaceSlug, agentId);
12006
12006
  }
12007
+ function withFoundSeats(route2, found, previousReleaseAgentId) {
12008
+ const resolved = route2.resolved;
12009
+ if (!resolved) return route2;
12010
+ const releaseSeat2 = found.releaseSeat ?? resolved.releaseSeat;
12011
+ const releaseAgentId = resolved.releaseAgentId ?? previousReleaseAgentId;
12012
+ return {
12013
+ ...route2,
12014
+ resolved: {
12015
+ ...resolved,
12016
+ seats: found.seats,
12017
+ ...releaseSeat2 ? { releaseSeat: releaseSeat2 } : {},
12018
+ ...releaseAgentId ? { releaseAgentId } : {}
12019
+ }
12020
+ };
12021
+ }
12007
12022
  function seatAgentId(route2, role) {
12008
12023
  return route2.resolved?.agentPersonas?.[role]?.agentId;
12009
12024
  }
@@ -12496,6 +12511,15 @@ printf '%s
12496
12511
  ' "$tip" > "$out/$name.tip"
12497
12512
  echo "updated $tip"
12498
12513
  `;
12514
+ var SHOW_SCRIPT = `
12515
+ set -e
12516
+ export GIT_TERMINAL_PROMPT=0
12517
+ m=${VOLUME_ROOT}/"$1"; base="$2"; path="$3"
12518
+ git config --global --add safe.directory '*'
12519
+ git -C "$m" rev-parse --verify --quiet "refs/heads/$base^{commit}" >/dev/null || { echo "the mirror has no branch $base" >&2; exit 3; }
12520
+ git -C "$m" cat-file -e "refs/heads/$base:$path" 2>/dev/null || exit 4
12521
+ git -C "$m" show "refs/heads/$base:$path"
12522
+ `;
12499
12523
  var SandboxCache = class {
12500
12524
  s;
12501
12525
  exec;
@@ -12582,6 +12606,20 @@ var SandboxCache = class {
12582
12606
  if (!m) throw new Error(`unexpected output materializing ${repo.host}/${repo.path}: ${stdout.trim().slice(0, 200)}`);
12583
12607
  return { tip: m[2], changed: m[1] === "updated" };
12584
12608
  }
12609
+ /**
12610
+ * The text of `path` at the tip of `base` in the repository's mirror, or
12611
+ * null when the mirror has no such file. Throws when the mirror or the
12612
+ * branch is missing, so the caller can tell "no contract" from "no mirror".
12613
+ */
12614
+ async showFile(repo, base, path) {
12615
+ try {
12616
+ const { stdout } = await this.helper(this.s.helperImage, SHOW_SCRIPT, [mirrorRelPath(repo), base, path]);
12617
+ return stdout;
12618
+ } catch (e) {
12619
+ if (e.code === 4) return null;
12620
+ throw e;
12621
+ }
12622
+ }
12585
12623
  /** Mirrors in the volume with their last refresh; empty when the volume does not exist. */
12586
12624
  async list() {
12587
12625
  try {
@@ -14055,7 +14093,7 @@ async function runPoolMember(deps) {
14055
14093
  await deps.sleep(backoffMs(failures++));
14056
14094
  }
14057
14095
  };
14058
- let reported;
14096
+ let reported2;
14059
14097
  let busy = false;
14060
14098
  const report = async (state, o = {}) => {
14061
14099
  const body = {
@@ -14078,7 +14116,7 @@ async function runPoolMember(deps) {
14078
14116
  if (r.status === 401) return "credential";
14079
14117
  if (r.status === 409) deps.log(`status ${state}: the platform refused the transition (409); not retrying`);
14080
14118
  else if (r.status >= 400) deps.log(`status ${state}: platform answered ${r.status}; not retrying`);
14081
- reported = body;
14119
+ reported2 = body;
14082
14120
  deps.store.write({ ...deps.store.read(), lastState: state });
14083
14121
  return "ok";
14084
14122
  };
@@ -14161,7 +14199,7 @@ async function runPoolMember(deps) {
14161
14199
  if (r === "stopped") return POOL_EXIT.ok;
14162
14200
  }
14163
14201
  }
14164
- let relayReported = reported?.state === "running" ? reported.relayUrl : null;
14202
+ let relayReported = reported2?.state === "running" ? reported2.relayUrl : null;
14165
14203
  let reregister = false;
14166
14204
  while (!reregister) {
14167
14205
  if (deps.stopping()) return POOL_EXIT.ok;
@@ -19076,6 +19114,73 @@ function runForge(a, io) {
19076
19114
  `, err: "" };
19077
19115
  }
19078
19116
 
19117
+ // src/mirror-contract.ts
19118
+ import { existsSync as existsSync40 } from "fs";
19119
+
19120
+ // src/passenger-mirror.ts
19121
+ import { mkdirSync as mkdirSync29 } from "fs";
19122
+ import { dirname as dirname25 } from "path";
19123
+ function mirrorRepoOf(spec) {
19124
+ const fromOrigin = spec.originUrl ? parseRemote(spec.originUrl) : null;
19125
+ if (fromOrigin) return fromOrigin;
19126
+ if (!spec.remote) return null;
19127
+ return { host: normalizeForgeHost(spec.forgeHost) || DEFAULT_FORGE_HOST, path: spec.remote };
19128
+ }
19129
+ async function materializeMirrorCheckouts(specs, cacheFor = (s) => new SandboxCache(s.mirror.cache)) {
19130
+ const out = [];
19131
+ for (const spec of specs) {
19132
+ if (!spec.mirror) continue;
19133
+ const base = { name: spec.name, workspaceId: spec.workspaceId };
19134
+ const repo = mirrorRepoOf(spec);
19135
+ if (!repo) {
19136
+ out.push({ ...base, action: "failed", detail: `no remote known for ${spec.name}, so its mirror cannot be found` });
19137
+ continue;
19138
+ }
19139
+ try {
19140
+ const parent = dirname25(spec.dir);
19141
+ mkdirSync29(parent, { recursive: true });
19142
+ mkdirSync29(spec.dir, { recursive: true });
19143
+ const r = await cacheFor(spec).materialize(repo, spec.base, parent, spec.name);
19144
+ out.push({ ...base, action: r.changed ? "updated" : "unchanged", detail: r.tip });
19145
+ } catch (e) {
19146
+ const err3 = e;
19147
+ out.push({ ...base, action: "failed", detail: (err3.stderr?.trim() || err3.message || String(e)).split("\n").slice(-3).join(" / ") });
19148
+ }
19149
+ }
19150
+ return out;
19151
+ }
19152
+ function mirrorOutcome(m) {
19153
+ if (m.action === "failed") return { action: "clone-failed", detail: m.detail, repeat: false };
19154
+ return { action: "cloned", detail: `${m.action} ${m.detail}` };
19155
+ }
19156
+
19157
+ // src/mirror-contract.ts
19158
+ function lacksCheckout(ship, dir) {
19159
+ return ship.runner === "sandbox" && !existsSync40(dir);
19160
+ }
19161
+ function mirrorRepoOfTarget(t) {
19162
+ return mirrorRepoOf({ remote: t.remote, forgeHost: t.forgeHost });
19163
+ }
19164
+ async function readMirrorContract(cache, repo, base) {
19165
+ for (const name of REPO_CONFIG_FILENAMES) {
19166
+ const text2 = await cache.showFile(repo, base, name);
19167
+ if (text2 !== null) return parseRepoConfig(text2, `${repo.host}/${repo.path}:${base}:${name}`);
19168
+ }
19169
+ return null;
19170
+ }
19171
+ function noCheckoutReleaseNote(repo, mode) {
19172
+ if (mode === "local" || mode === null) {
19173
+ 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`;
19174
+ }
19175
+ 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)`;
19176
+ }
19177
+ var reported = /* @__PURE__ */ new Set();
19178
+ function firstReport(key) {
19179
+ if (reported.has(key)) return false;
19180
+ reported.add(key);
19181
+ return true;
19182
+ }
19183
+
19079
19184
  // src/whoami.ts
19080
19185
  async function buildWhoami(routes, deps = {}) {
19081
19186
  const doFetch = deps.fetch ?? fetch;
@@ -19247,7 +19352,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
19247
19352
  }
19248
19353
 
19249
19354
  // src/watch.ts
19250
- import { watch as watchFile, existsSync as existsSync40, statSync as statSync11, openSync as openSync5, readSync as readSync2, closeSync as closeSync5 } from "fs";
19355
+ import { watch as watchFile, existsSync as existsSync41, statSync as statSync11, openSync as openSync5, readSync as readSync2, closeSync as closeSync5 } from "fs";
19251
19356
  var E = "\x1B[";
19252
19357
  var dim = (s) => `${E}2m${s}${E}0m`;
19253
19358
  var bold = (s) => `${E}1m${s}${E}0m`;
@@ -19348,7 +19453,7 @@ function renderHeaders(states, now = Date.now()) {
19348
19453
  return states.map((s) => renderHeader(s, now)).join("");
19349
19454
  }
19350
19455
  function readFrom(file, from) {
19351
- if (!existsSync40(file)) return { events: [], offset: from };
19456
+ if (!existsSync41(file)) return { events: [], offset: from };
19352
19457
  const size = statSync11(file).size;
19353
19458
  if (size < from) return readFrom(file, 0);
19354
19459
  if (size === from) return { events: [], offset: from };
@@ -19591,8 +19696,8 @@ function nextRoles(decision, ran) {
19591
19696
  }
19592
19697
 
19593
19698
  // src/ship-name.ts
19594
- import { existsSync as existsSync41, mkdirSync as mkdirSync29, readFileSync as readFileSync37, renameSync as renameSync5, writeFileSync as writeFileSync32 } from "fs";
19595
- import { dirname as dirname25 } from "path";
19699
+ import { existsSync as existsSync42, mkdirSync as mkdirSync30, readFileSync as readFileSync37, renameSync as renameSync5, writeFileSync as writeFileSync32 } from "fs";
19700
+ import { dirname as dirname26 } from "path";
19596
19701
  import { parseDocument as parseDocument5 } from "yaml";
19597
19702
  var ShipNameError = class extends Error {
19598
19703
  };
@@ -19653,7 +19758,7 @@ async function syncRouteName(i) {
19653
19758
  return { status: "ok", ...base, action, name: i.local };
19654
19759
  }
19655
19760
  function readSyncedName(resolvedFile) {
19656
- if (!existsSync41(resolvedFile)) return void 0;
19761
+ if (!existsSync42(resolvedFile)) return void 0;
19657
19762
  try {
19658
19763
  const v = JSON.parse(readFileSync37(resolvedFile, "utf8")).shipName;
19659
19764
  return typeof v === "string" ? v : void 0;
@@ -19662,7 +19767,7 @@ function readSyncedName(resolvedFile) {
19662
19767
  }
19663
19768
  }
19664
19769
  function writeSyncedName(resolvedFile, name) {
19665
- if (!existsSync41(resolvedFile)) return;
19770
+ if (!existsSync42(resolvedFile)) return;
19666
19771
  let raw;
19667
19772
  try {
19668
19773
  raw = JSON.parse(readFileSync37(resolvedFile, "utf8"));
@@ -19671,7 +19776,7 @@ function writeSyncedName(resolvedFile, name) {
19671
19776
  }
19672
19777
  if (raw.shipName === name) return;
19673
19778
  raw.shipName = name;
19674
- mkdirSync29(dirname25(resolvedFile), { recursive: true });
19779
+ mkdirSync30(dirname26(resolvedFile), { recursive: true });
19675
19780
  const tmp = `${resolvedFile}.tmp${process.pid}`;
19676
19781
  writeFileSync32(tmp, `${JSON.stringify(raw, null, 2)}
19677
19782
  `);
@@ -20364,8 +20469,8 @@ function describeCapability(i) {
20364
20469
  }
20365
20470
 
20366
20471
  // src/agents-sync-run.ts
20367
- import { existsSync as existsSync42, mkdirSync as mkdirSync30, readFileSync as readFileSync38, writeFileSync as writeFileSync33 } from "fs";
20368
- import { dirname as dirname26 } from "path";
20472
+ import { existsSync as existsSync43, mkdirSync as mkdirSync31, readFileSync as readFileSync38, writeFileSync as writeFileSync33 } from "fs";
20473
+ import { dirname as dirname27 } from "path";
20369
20474
  async function runAgentsSync(target, o) {
20370
20475
  const dryRun = o.dryRun ?? false;
20371
20476
  const force = o.force ?? false;
@@ -20398,11 +20503,11 @@ async function runAgentsSync(target, o) {
20398
20503
  for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
20399
20504
  if (!dryRun) {
20400
20505
  const resolvedPath = resolvedPathFor(o.stateDir, target.route);
20401
- const raw = existsSync42(resolvedPath) ? JSON.parse(readFileSync38(resolvedPath, "utf8")) : {};
20506
+ const raw = existsSync43(resolvedPath) ? JSON.parse(readFileSync38(resolvedPath, "utf8")) : {};
20402
20507
  raw.agentPersonas = personaResult.agentPersonas;
20403
20508
  if (skillResult) raw.agentSkills = skillResult.agentSkills;
20404
20509
  if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
20405
- mkdirSync30(dirname26(resolvedPath), { recursive: true });
20510
+ mkdirSync31(dirname27(resolvedPath), { recursive: true });
20406
20511
  writeFileSync33(resolvedPath, `${JSON.stringify(raw, null, 2)}
20407
20512
  `);
20408
20513
  if (target.resolved) {
@@ -21025,7 +21130,7 @@ ${run3.err}` });
21025
21130
  }
21026
21131
 
21027
21132
  // src/sample-ticket.ts
21028
- import { basename as basename12, dirname as dirname27, join as join44 } from "path";
21133
+ import { basename as basename12, dirname as dirname28, join as join44 } from "path";
21029
21134
  var SAMPLE_REPO = "crew-sample";
21030
21135
  var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
21031
21136
  var SAMPLE_MARKER = "sample: true";
@@ -21048,7 +21153,7 @@ has something safe to work on: its test and build hooks always pass, it has no
21048
21153
  remote, and \`crew sample-ticket --remove\` deletes it.
21049
21154
  `;
21050
21155
  function sampleDir(existing, reposBase, workspace) {
21051
- if (existing.length > 0) return join44(dirname27(existing[0]), SAMPLE_REPO);
21156
+ if (existing.length > 0) return join44(dirname28(existing[0]), SAMPLE_REPO);
21052
21157
  return join44(reposBase, workspace, SAMPLE_REPO);
21053
21158
  }
21054
21159
  var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
@@ -21093,8 +21198,8 @@ function planSampleTicket(i) {
21093
21198
  }
21094
21199
 
21095
21200
  // src/worktree-sweep.ts
21096
- import { existsSync as existsSync43, readFileSync as readFileSync40, readdirSync as readdirSync14 } from "fs";
21097
- import { basename as basename13, dirname as dirname28, join as join45, resolve as resolve5 } from "path";
21201
+ import { existsSync as existsSync44, readFileSync as readFileSync40, readdirSync as readdirSync14 } from "fs";
21202
+ import { basename as basename13, dirname as dirname29, join as join45, resolve as resolve5 } from "path";
21098
21203
  function planWorktreeSweep(target, tickets, contract) {
21099
21204
  const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
21100
21205
  const verified = contract.statuses.verified;
@@ -21158,7 +21263,7 @@ function worktreeTicketNumbers(target) {
21158
21263
  return [...nums];
21159
21264
  }
21160
21265
  function siblingWorktreeDirs(target) {
21161
- const parent = dirname28(target.dir.replace(/[/\\]+$/, ""));
21266
+ const parent = dirname29(target.dir.replace(/[/\\]+$/, ""));
21162
21267
  const stem = `${basename13(target.dir.replace(/[/\\]+$/, ""))}-`;
21163
21268
  const registered = new Set(worktrees(target.dir).map((w) => resolve5(w.path)));
21164
21269
  let names;
@@ -21206,7 +21311,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
21206
21311
  let removed = 0;
21207
21312
  let keptBranches = 0;
21208
21313
  for (const a of actions) {
21209
- if (!dryRun && base && !a.keepBranch && existsSync43(a.path) && isHandBackMerge(a.path, base)) {
21314
+ if (!dryRun && base && !a.keepBranch && existsSync44(a.path) && isHandBackMerge(a.path, base)) {
21210
21315
  if (gitOk(a.path, ["merge", "--abort"]) !== null) {
21211
21316
  log.emit(
21212
21317
  `abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
@@ -21214,7 +21319,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
21214
21319
  );
21215
21320
  }
21216
21321
  }
21217
- if (!dryRun && existsSync43(a.path)) {
21322
+ if (!dryRun && existsSync44(a.path)) {
21218
21323
  let dirty = false;
21219
21324
  try {
21220
21325
  dirty = status(a.path).length > 0;
@@ -21225,7 +21330,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
21225
21330
  continue;
21226
21331
  }
21227
21332
  }
21228
- if (!dryRun && a.requirePushed && a.branch && existsSync43(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
21333
+ if (!dryRun && a.requirePushed && a.branch && existsSync44(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
21229
21334
  refuse2(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
21230
21335
  continue;
21231
21336
  }
@@ -21379,12 +21484,12 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
21379
21484
  }
21380
21485
 
21381
21486
  // src/stream-sweep.ts
21382
- import { existsSync as existsSync44, readdirSync as readdirSync15, rmSync as rmSync20, statSync as statSync12 } from "fs";
21487
+ import { existsSync as existsSync45, readdirSync as readdirSync15, rmSync as rmSync20, statSync as statSync12 } from "fs";
21383
21488
  import { join as join46 } from "path";
21384
21489
  var DAY_MS2 = 24 * 60 * 60 * 1e3;
21385
21490
  function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
21386
21491
  const dir = join46(stateDir, "streams");
21387
- if (!existsSync44(dir)) return [];
21492
+ if (!existsSync45(dir)) return [];
21388
21493
  const cutoff = now - retentionDays * DAY_MS2;
21389
21494
  return readdirSync15(dir).map((name) => join46(dir, name)).filter((path) => {
21390
21495
  try {
@@ -21412,7 +21517,7 @@ function applyStreamSweep(paths, dryRun, log) {
21412
21517
  return removed;
21413
21518
  }
21414
21519
  function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
21415
- if (!existsSync44(stateDir)) return [];
21520
+ if (!existsSync45(stateDir)) return [];
21416
21521
  const cutoff = now - retentionDays * DAY_MS2;
21417
21522
  return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join46(stateDir, name)).filter((path) => {
21418
21523
  try {
@@ -21424,11 +21529,11 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
21424
21529
  }
21425
21530
 
21426
21531
  // src/cli.ts
21427
- import { realpathSync as realpathSync3, readFileSync as readFileSync46, existsSync as existsSync51, 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";
21532
+ import { realpathSync as realpathSync3, readFileSync as readFileSync46, 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";
21428
21533
  import { createInterface } from "readline/promises";
21429
21534
 
21430
21535
  // src/periscope-cmd.ts
21431
- import { existsSync as existsSync45, mkdirSync as mkdirSync31, readFileSync as readFileSync41, rmSync as rmSync21, writeFileSync as writeFileSync34 } from "fs";
21536
+ import { existsSync as existsSync46, mkdirSync as mkdirSync32, readFileSync as readFileSync41, rmSync as rmSync21, writeFileSync as writeFileSync34 } from "fs";
21432
21537
  import { join as join47 } from "path";
21433
21538
  var PeriscopeError = class extends Error {
21434
21539
  };
@@ -21446,7 +21551,7 @@ function removePeriscopePrior(stateDir, ticketRecordId) {
21446
21551
  }
21447
21552
  function writePeriscopePrior(stateDir, ticketRecordId, prior) {
21448
21553
  try {
21449
- mkdirSync31(priorDir(stateDir), { recursive: true });
21554
+ mkdirSync32(priorDir(stateDir), { recursive: true });
21450
21555
  writeFileSync34(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
21451
21556
  } catch {
21452
21557
  }
@@ -21454,7 +21559,7 @@ function writePeriscopePrior(stateDir, ticketRecordId, prior) {
21454
21559
  function readPeriscopePrior(stateDir, ticketRecordId) {
21455
21560
  try {
21456
21561
  const p = priorPath(stateDir, ticketRecordId);
21457
- return existsSync45(p) ? JSON.parse(readFileSync41(p, "utf8")) : void 0;
21562
+ return existsSync46(p) ? JSON.parse(readFileSync41(p, "utf8")) : void 0;
21458
21563
  } catch {
21459
21564
  return void 0;
21460
21565
  }
@@ -22009,8 +22114,8 @@ import { dirname as dirOf2, resolve as resolvePath } from "path";
22009
22114
 
22010
22115
  // src/ssh-keys.ts
22011
22116
  import { execFileSync as execFileSync13 } from "child_process";
22012
- import { existsSync as existsSync46, mkdirSync as mkdirSync32, readFileSync as readFileSync42, chmodSync as chmodSync3 } from "fs";
22013
- import { dirname as dirname29, join as join49 } from "path";
22117
+ import { existsSync as existsSync47, mkdirSync as mkdirSync33, readFileSync as readFileSync42, chmodSync as chmodSync3 } from "fs";
22118
+ import { dirname as dirname30, join as join49 } from "path";
22014
22119
  var SshKeyError = class extends Error {
22015
22120
  };
22016
22121
  function shipSshKeyPathFor(stateDir) {
@@ -22019,8 +22124,8 @@ function shipSshKeyPathFor(stateDir) {
22019
22124
  function ensureShipSshKeypair(stateDir) {
22020
22125
  const privateKeyPath = shipSshKeyPathFor(stateDir);
22021
22126
  const publicKeyPath = `${privateKeyPath}.pub`;
22022
- if (!existsSync46(privateKeyPath)) {
22023
- mkdirSync32(dirname29(privateKeyPath), { recursive: true });
22127
+ if (!existsSync47(privateKeyPath)) {
22128
+ mkdirSync33(dirname30(privateKeyPath), { recursive: true });
22024
22129
  try {
22025
22130
  execFileSync13("ssh-keygen", [
22026
22131
  "-t",
@@ -22040,7 +22145,7 @@ function ensureShipSshKeypair(stateDir) {
22040
22145
  }
22041
22146
  chmodSync3(privateKeyPath, 384);
22042
22147
  }
22043
- if (!existsSync46(publicKeyPath)) {
22148
+ if (!existsSync47(publicKeyPath)) {
22044
22149
  throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
22045
22150
  }
22046
22151
  const publicKey = readFileSync42(publicKeyPath, "utf8").trim();
@@ -22207,46 +22312,9 @@ async function unregisterEndpoint(workspaceId, info, d) {
22207
22312
  import { createServer as createHttpServer } from "http";
22208
22313
  import { spawn as spawn9 } from "child_process";
22209
22314
  import { randomBytes } from "crypto";
22210
- import { existsSync as existsSync47, mkdirSync as mkdirSync34, readFileSync as readFileSync43, writeFileSync as writeFileSync35, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
22315
+ import { existsSync as existsSync48, mkdirSync as mkdirSync34, readFileSync as readFileSync43, writeFileSync as writeFileSync35, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
22211
22316
  import { join as join50 } from "path";
22212
22317
 
22213
- // src/passenger-mirror.ts
22214
- import { mkdirSync as mkdirSync33 } from "fs";
22215
- import { dirname as dirname30 } from "path";
22216
- function mirrorRepoOf(spec) {
22217
- const fromOrigin = spec.originUrl ? parseRemote(spec.originUrl) : null;
22218
- if (fromOrigin) return fromOrigin;
22219
- if (!spec.remote) return null;
22220
- return { host: normalizeForgeHost(spec.forgeHost) || DEFAULT_FORGE_HOST, path: spec.remote };
22221
- }
22222
- async function materializeMirrorCheckouts(specs, cacheFor = (s) => new SandboxCache(s.mirror.cache)) {
22223
- const out = [];
22224
- for (const spec of specs) {
22225
- if (!spec.mirror) continue;
22226
- const base = { name: spec.name, workspaceId: spec.workspaceId };
22227
- const repo = mirrorRepoOf(spec);
22228
- if (!repo) {
22229
- out.push({ ...base, action: "failed", detail: `no remote known for ${spec.name}, so its mirror cannot be found` });
22230
- continue;
22231
- }
22232
- try {
22233
- const parent = dirname30(spec.dir);
22234
- mkdirSync33(parent, { recursive: true });
22235
- mkdirSync33(spec.dir, { recursive: true });
22236
- const r = await cacheFor(spec).materialize(repo, spec.base, parent, spec.name);
22237
- out.push({ ...base, action: r.changed ? "updated" : "unchanged", detail: r.tip });
22238
- } catch (e) {
22239
- const err3 = e;
22240
- out.push({ ...base, action: "failed", detail: (err3.stderr?.trim() || err3.message || String(e)).split("\n").slice(-3).join(" / ") });
22241
- }
22242
- }
22243
- return out;
22244
- }
22245
- function mirrorOutcome(m) {
22246
- if (m.action === "failed") return { action: "clone-failed", detail: m.detail, repeat: false };
22247
- return { action: "cloned", detail: `${m.action} ${m.detail}` };
22248
- }
22249
-
22250
22318
  // src/secret-compare.ts
22251
22319
  import { timingSafeEqual } from "crypto";
22252
22320
  function secretsMatch(a, b) {
@@ -22342,7 +22410,7 @@ function syncDaemonStatePath(stateDir, workspaceId) {
22342
22410
  }
22343
22411
  function readPersistedSyncDaemon(stateDir, workspaceId) {
22344
22412
  const path = syncDaemonStatePath(stateDir, workspaceId);
22345
- if (!existsSync47(path)) return void 0;
22413
+ if (!existsSync48(path)) return void 0;
22346
22414
  try {
22347
22415
  return JSON.parse(readFileSync43(path, "utf8"));
22348
22416
  } catch {
@@ -22373,7 +22441,7 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
22373
22441
  }
22374
22442
  function readAllPersistedSyncWorkspaceIds(stateDir) {
22375
22443
  const dir = join50(stateDir, "passengers");
22376
- if (!existsSync47(dir)) return [];
22444
+ if (!existsSync48(dir)) return [];
22377
22445
  const suffix = "-sync.json";
22378
22446
  return readdirSync16(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
22379
22447
  }
@@ -22893,7 +22961,7 @@ function contractFixers(dir, baseRef, tickets, locate) {
22893
22961
  }
22894
22962
 
22895
22963
  // src/daemon.ts
22896
- import { existsSync as existsSync48, mkdirSync as mkdirSync36, readFileSync as readFileSync44, readdirSync as readdirSync17, rmSync as rmSync22, statSync as statSync14, writeFileSync as writeFileSync36 } from "fs";
22964
+ import { existsSync as existsSync49, mkdirSync as mkdirSync36, readFileSync as readFileSync44, readdirSync as readdirSync17, rmSync as rmSync22, statSync as statSync14, writeFileSync as writeFileSync36 } from "fs";
22897
22965
  import { join as join52 } from "path";
22898
22966
  import { randomBytes as randomBytes2 } from "crypto";
22899
22967
  var DEFAULT_FLOOR_MS = 5e3;
@@ -22991,7 +23059,7 @@ function computeTreeSignature(paths) {
22991
23059
  }
22992
23060
  function makeStaleChecker(paths, opts = {}) {
22993
23061
  const initial = computeTreeSignature(paths);
22994
- return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync48(opts.reloadSentinel);
23062
+ return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync49(opts.reloadSentinel);
22995
23063
  }
22996
23064
  function reloadSentinelFor(stateDir) {
22997
23065
  return join52(stateDir, "reload-requested");
@@ -23242,8 +23310,8 @@ async function runOnePassUnguarded(o) {
23242
23310
  set.add(run3);
23243
23311
  void run3.finally(() => set.delete(run3));
23244
23312
  }
23245
- const reported = TICKET_ROLES.has(role) ? await settled : true;
23246
- const tookTicket = reported !== false;
23313
+ const reported2 = TICKET_ROLES.has(role) ? await settled : true;
23314
+ const tookTicket = reported2 !== false;
23247
23315
  if (tookTicket) started.push(role);
23248
23316
  if (roleCap(role) <= 1) {
23249
23317
  live.delete(role);
@@ -23580,7 +23648,7 @@ import { createHash as createHash10 } from "crypto";
23580
23648
  import {
23581
23649
  chmodSync as chmodSync4,
23582
23650
  closeSync as closeSync6,
23583
- existsSync as existsSync49,
23651
+ existsSync as existsSync50,
23584
23652
  fstatSync,
23585
23653
  openSync as openSync6,
23586
23654
  readSync as readSync3,
@@ -23951,7 +24019,7 @@ function lastLines2(path, n) {
23951
24019
  }
23952
24020
  function readTail(live, opts) {
23953
24021
  const max = opts.maxBytes ?? MAX_TAIL_BYTES;
23954
- if (!existsSync49(live)) {
24022
+ if (!existsSync50(live)) {
23955
24023
  return { lines: [], cursor: opts.after ?? encodeCursor({ dev: 0, ino: 0, off: 0 }), reset: false };
23956
24024
  }
23957
24025
  const cur = opts.after ? decodeCursor(opts.after) : null;
@@ -24108,7 +24176,7 @@ async function route(deps, method, url, req, abort) {
24108
24176
  const run3 = decodeURIComponent(p.slice("/streams/".length));
24109
24177
  if (!RUN_NAME_RE.test(run3) || run3.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run3)) return err2(400, "not a run name");
24110
24178
  const file = join53(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
24111
- if (!existsSync49(file)) return err2(404, "no such run");
24179
+ if (!existsSync50(file)) return err2(404, "no such run");
24112
24180
  return tailReply(file, url.searchParams, false, abort);
24113
24181
  }
24114
24182
  if ((p === "/pause" || p === "/resume") && post3) {
@@ -24152,7 +24220,7 @@ function send(res, r) {
24152
24220
  res.end(body);
24153
24221
  }
24154
24222
  async function socketIsStale(path) {
24155
- if (!existsSync49(path)) return false;
24223
+ if (!existsSync50(path)) return false;
24156
24224
  return new Promise((resolve8) => {
24157
24225
  const c = createConnection(path);
24158
24226
  c.once("connect", () => {
@@ -24171,7 +24239,7 @@ async function startControlServer(deps) {
24171
24239
  return null;
24172
24240
  }
24173
24241
  if (await socketIsStale(path)) rmSync23(path, { force: true });
24174
- else if (existsSync49(path)) {
24242
+ else if (existsSync50(path)) {
24175
24243
  log(`control API not started: ${path} is in use by another process`);
24176
24244
  return null;
24177
24245
  }
@@ -24264,7 +24332,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
24264
24332
 
24265
24333
  // src/periscope-endpoints.ts
24266
24334
  import { randomBytes as randomBytes3 } from "crypto";
24267
- import { chmodSync as chmodSync5, existsSync as existsSync50, mkdirSync as mkdirSync37, readdirSync as readdirSync19, readFileSync as readFileSync45, rmSync as rmSync24, writeFileSync as writeFileSync37 } from "fs";
24335
+ import { chmodSync as chmodSync5, existsSync as existsSync51, mkdirSync as mkdirSync37, readdirSync as readdirSync19, readFileSync as readFileSync45, rmSync as rmSync24, writeFileSync as writeFileSync37 } from "fs";
24268
24336
  import { join as join54 } from "path";
24269
24337
  var secretDir = (stateDir) => join54(stateDir, "periscope-mcp");
24270
24338
  var secretFile = (stateDir, id) => join54(secretDir(stateDir), `${id}.json`);
@@ -24307,7 +24375,7 @@ function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
24307
24375
  }
24308
24376
  function listPeriscopeEndpoints(stateDir) {
24309
24377
  const dir = endpointDir(stateDir);
24310
- if (!existsSync50(dir)) return [];
24378
+ if (!existsSync51(dir)) return [];
24311
24379
  const out = [];
24312
24380
  for (const name of readdirSync19(dir)) {
24313
24381
  if (!name.endsWith(".json")) continue;
@@ -25290,7 +25358,7 @@ if (command === "passenger-sync-daemon") {
25290
25358
  } else {
25291
25359
  const toleratesNoRoutes = command === "credential" || command === "forge" || command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "deprovision" || command === "connect" && flag("sign-out");
25292
25360
  const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset") || command === "deprovision";
25293
- if (configless && !existsSync51(findConfigFile(CREW_HOME))) {
25361
+ if (configless && !existsSync52(findConfigFile(CREW_HOME))) {
25294
25362
  cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
25295
25363
  } else {
25296
25364
  cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
@@ -25634,7 +25702,7 @@ if (command === "passenger-sync-daemon") {
25634
25702
  if (host === "linux" && hasSystemd) {
25635
25703
  const label = labelFor(CREW_HOME, "run");
25636
25704
  const staleTimer = join56(homedir11(), ".config", "systemd", "user", `${label}.timer`);
25637
- if (existsSync51(staleTimer)) {
25705
+ if (existsSync52(staleTimer)) {
25638
25706
  process.stdout.write(
25639
25707
  `removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
25640
25708
  `
@@ -25663,7 +25731,7 @@ if (command === "passenger-sync-daemon") {
25663
25731
  const { settled: doctorRepos, errors: refusedRepos } = await settleRepos(reposOf(c), (t) => t.name, (t) => resolveOneRepo(c, t));
25664
25732
  for (const e of refusedRepos) problems.push(`repo ${e.repo}: ${e.detail}`);
25665
25733
  for (const r of doctorRepos) {
25666
- if (!existsSync51(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
25734
+ if (!existsSync52(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
25667
25735
  if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
25668
25736
  readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
25669
25737
  }
@@ -25690,7 +25758,7 @@ if (command === "passenger-sync-daemon") {
25690
25758
  }
25691
25759
  async function doctorFix(o) {
25692
25760
  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))));
25693
- const unitExists = (p) => existsSync51(p);
25761
+ const unitExists = (p) => existsSync52(p);
25694
25762
  const crontab = o.host === "linux" ? spawnSync11("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
25695
25763
  const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run", installContext()), unitExists, crontab) : true;
25696
25764
  const plan = planFix({ routes, schedulerAbsent, host: o.host });
@@ -25842,9 +25910,25 @@ ${changed2.map((c) => ` - ${c}
25842
25910
  }
25843
25911
  }
25844
25912
  const precheckTickets = /* @__PURE__ */ new Map();
25913
+ const scope0 = (c, t) => releaseLockScope(c.route, t.name);
25845
25914
  async function releasePhase(c, target, opts = {}) {
25846
25915
  const scope = releaseLockScope(c.route, target.name);
25847
25916
  const remit = emit.forRoute(c.route);
25917
+ if (lacksCheckout(cfg.ship, target.dir)) {
25918
+ let mode = null;
25919
+ const settings = cacheSettingsOf(cfg.ship.sandbox);
25920
+ const mirror = mirrorRepoOfTarget(target);
25921
+ if (settings && mirror) {
25922
+ try {
25923
+ mode = (await readMirrorContract(new SandboxCache(settings), mirror, c.branch?.base ?? "main"))?.release.mode ?? null;
25924
+ } catch (e) {
25925
+ if (firstReport(`${scope0(c, target)}:contract`)) remit.warn(`${target.name}: could not read the contract from the mirror: ${e.message}`, { step: "release" });
25926
+ }
25927
+ }
25928
+ const note = noCheckoutReleaseNote(target.name, mode);
25929
+ if (firstReport(`${scope0(c, target)}:release`)) remit.emit(note, { step: "release" });
25930
+ return { scope: releaseLockScope(c.route, target.name), tests: "skipped", outcome: "skipped", detail: note };
25931
+ }
25848
25932
  const relLock = dryRun ? void 0 : state.acquire(`release-${scope}`);
25849
25933
  if (relLock && !relLock.ok) {
25850
25934
  remit.emit(`a previous release (pid ${relLock.heldBy}) is still running \u2014 skipping`, { step: "release" });
@@ -26104,10 +26188,10 @@ ${changed2.map((c) => ` - ${c}
26104
26188
  if (outcome.gateRed && !dryRun) {
26105
26189
  try {
26106
26190
  const g = outcome.gateRed;
26107
- const reported = await tracker.events("gate_red");
26191
+ const reported2 = await tracker.events("gate_red");
26108
26192
  const seat = releaseSeat(c);
26109
26193
  for (const t of gateRedTickets(outcome.merged, outcome.decision.merges)) {
26110
- if (alreadyReported(t, g, reported)) continue;
26194
+ if (alreadyReported(t, g, reported2)) continue;
26111
26195
  await tracker.postEvent(t.id, gateRedComment(g), seat, "gate_red");
26112
26196
  remit.warn(`test gate red for ${g.count} cycles \u2014 noted on the ticket`, { ticket: t.issue_id, step: "merge" });
26113
26197
  }
@@ -26118,10 +26202,10 @@ ${changed2.map((c) => ` - ${c}
26118
26202
  }
26119
26203
  if (outcome.localRefSkips?.length && !dryRun) {
26120
26204
  try {
26121
- const reported = await tracker.events("local_ref_skip");
26205
+ const reported2 = await tracker.events("local_ref_skip");
26122
26206
  const seat = releaseSeat(c);
26123
26207
  for (const s of outcome.localRefSkips) {
26124
- if (localRefSkipReported(s.ticket, s.problem, reported)) continue;
26208
+ if (localRefSkipReported(s.ticket, s.problem, reported2)) continue;
26125
26209
  await tracker.postEvent(s.ticket.id, localRefSkipComment(cfg.ship.name, s.problem), seat, "local_ref_skip");
26126
26210
  }
26127
26211
  } catch (e) {
@@ -27191,7 +27275,7 @@ ${cu.tail.trimEnd()}
27191
27275
  `);
27192
27276
  if (!dryRun) {
27193
27277
  const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
27194
- const raw = existsSync51(resolvedPath) ? JSON.parse(readFileSync46(resolvedPath, "utf8")) : {};
27278
+ const raw = existsSync52(resolvedPath) ? JSON.parse(readFileSync46(resolvedPath, "utf8")) : {};
27195
27279
  raw.agentSkills = result.agentSkills;
27196
27280
  mkdirSync38(dirOf2(resolvedPath), { recursive: true });
27197
27281
  writeFileSync38(resolvedPath, `${JSON.stringify(raw, null, 2)}
@@ -27296,8 +27380,8 @@ ${cu.tail.trimEnd()}
27296
27380
  try {
27297
27381
  const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
27298
27382
  const dir = repoDirOf(configText, dirname34(cfg.configFile), a.route, a.name);
27299
- if (!existsSync51(dir)) throw new ReposError(`${dir} does not exist on disk`);
27300
- const files = { exists: (r) => existsSync51(resolvePath(dir, r)), list: (r) => {
27383
+ if (!existsSync52(dir)) throw new ReposError(`${dir} does not exist on disk`);
27384
+ const files = { exists: (r) => existsSync52(resolvePath(dir, r)), list: (r) => {
27301
27385
  try {
27302
27386
  return readdirSync20(resolvePath(dir, r));
27303
27387
  } catch {
@@ -27415,7 +27499,7 @@ ${cu.tail.trimEnd()}
27415
27499
  if (sub === "list" && !routeName) usage2();
27416
27500
  if (sub === "add") {
27417
27501
  try {
27418
- const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync51(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
27502
+ const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync52(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
27419
27503
  routeName = a.route;
27420
27504
  addPath = a.path;
27421
27505
  addName = a.name;
@@ -27445,7 +27529,7 @@ ${cu.tail.trimEnd()}
27445
27529
  const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
27446
27530
  if (sub === "list" || sub === "add") {
27447
27531
  const liveRoute = cfg.routes.find((r) => r.route === routeName);
27448
- if (liveRoute?.resolved?.reposModelId && existsSync51(resolvedFile) && !dryRun) {
27532
+ if (liveRoute?.resolved?.reposModelId && existsSync52(resolvedFile) && !dryRun) {
27449
27533
  try {
27450
27534
  await hydrateApiKeys([liveRoute]);
27451
27535
  persistRepoRows(cfg.ship.stateDir, liveRoute.route, await new Tracker(liveRoute, cfg.ship).repoRowsForMap());
@@ -27454,7 +27538,7 @@ ${cu.tail.trimEnd()}
27454
27538
  }
27455
27539
  }
27456
27540
  let resolvedRemotes;
27457
- if (existsSync51(resolvedFile)) {
27541
+ if (existsSync52(resolvedFile)) {
27458
27542
  try {
27459
27543
  resolvedRemotes = JSON.parse(readFileSync46(resolvedFile, "utf8"));
27460
27544
  } catch {
@@ -27463,7 +27547,7 @@ ${cu.tail.trimEnd()}
27463
27547
  const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
27464
27548
  const text2 = configText;
27465
27549
  const base = dirname34(cfg.configFile);
27466
- const isCheckout = (d) => existsSync51(`${d}/.git`);
27550
+ const isCheckout = (d) => existsSync52(`${d}/.git`);
27467
27551
  try {
27468
27552
  if (sub === "list") {
27469
27553
  if (flag("json")) {
@@ -27473,7 +27557,7 @@ ${cu.tail.trimEnd()}
27473
27557
  base,
27474
27558
  resolved: resolvedRemotes,
27475
27559
  originOf,
27476
- exists: (d) => existsSync51(d),
27560
+ exists: (d) => existsSync52(d),
27477
27561
  isCheckout,
27478
27562
  repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
27479
27563
  try {
@@ -27495,7 +27579,7 @@ ${cu.tail.trimEnd()}
27495
27579
  for (const r of repos) {
27496
27580
  const origin2 = originOf(r.dir);
27497
27581
  const row = matchRepoName(origin2, resolvedRemotes);
27498
- const note = !existsSync51(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
27582
+ const note = !existsSync52(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
27499
27583
  process.stdout.write(`${r.name} ${r.dir} ${note}
27500
27584
  `);
27501
27585
  }
@@ -27507,9 +27591,9 @@ ${cu.tail.trimEnd()}
27507
27591
  ` : `${human}
27508
27592
  `);
27509
27593
  const plan2 = planRepoClone({ text: text2, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
27510
- if (existsSync51(plan2.target) && (existsSync51(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
27594
+ if (existsSync52(plan2.target) && (existsSync52(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
27511
27595
  throw new ReposError(
27512
- 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`
27596
+ 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`
27513
27597
  );
27514
27598
  }
27515
27599
  if (dryRun) {
@@ -27534,7 +27618,7 @@ ${cu.tail.trimEnd()}
27534
27618
  break;
27535
27619
  }
27536
27620
  const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir11()));
27537
- const top = existsSync51(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
27621
+ const top = existsSync52(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
27538
27622
  if (!top) throw new ReposError(`${target} is not a git checkout`);
27539
27623
  const origin = originOf(top);
27540
27624
  const plan = planRepoAdd({
@@ -27586,7 +27670,7 @@ ${cu.tail.trimEnd()}
27586
27670
  const created = await rowTracker.createRepoRow(rowPayload);
27587
27671
  process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
27588
27672
  `);
27589
- if (existsSync51(resolvedFile)) {
27673
+ if (existsSync52(resolvedFile)) {
27590
27674
  const raw = JSON.parse(readFileSync46(resolvedFile, "utf8"));
27591
27675
  raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
27592
27676
  raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
@@ -28008,7 +28092,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
28008
28092
  }
28009
28093
  }
28010
28094
  let previous;
28011
- const hadPreviousFile = existsSync51(resolvedPath);
28095
+ const hadPreviousFile = existsSync52(resolvedPath);
28012
28096
  if (hadPreviousFile) {
28013
28097
  try {
28014
28098
  previous = JSON.parse(readFileSync46(resolvedPath, "utf8"));
@@ -28153,7 +28237,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
28153
28237
  let mintOutcomes = [];
28154
28238
  if (!dryRun && previous?.agentPersonas && existingRoute?.resolved) {
28155
28239
  try {
28156
- mintOutcomes = await mintMissingSeatKeys({ ...existingRoute, resolved: { ...existingRoute.resolved, seats: found.seats } }, { userAgent: cfg.ship.userAgent, shipName: cfg.ship.name });
28240
+ mintOutcomes = await mintMissingSeatKeys(withFoundSeats(existingRoute, found, previous.releaseAgentId), { userAgent: cfg.ship.userAgent, shipName: cfg.ship.name });
28157
28241
  for (const o of mintOutcomes) process.stderr.write(`${describeMintOutcome(o)}
28158
28242
  `);
28159
28243
  for (const p of await probeSeatReads({ ...existingRoute, resolved: { ...existingRoute.resolved, seats: found.seats } }, { userAgent: cfg.ship.userAgent })) {
@@ -28888,7 +28972,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
28888
28972
  prepare: async (path) => {
28889
28973
  for (const f of repoWt.config.worktrees.copy) {
28890
28974
  const src = join56(repoWt.dir, f);
28891
- if (!existsSync51(src) || existsSync51(join56(path, f))) continue;
28975
+ if (!existsSync52(src) || existsSync52(join56(path, f))) continue;
28892
28976
  mkdirSync38(dirOf2(join56(path, f)), { recursive: true });
28893
28977
  copyFileSync2(src, join56(path, f));
28894
28978
  }
@@ -29111,7 +29195,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
29111
29195
  const shellR = hit && resolveShell(hit.repo.config.shell ?? cfg.ship.shell);
29112
29196
  const resumed = await runPeriscopeResume({
29113
29197
  find: async () => hit && { row: hit.row, tag: hit.tag },
29114
- worktreeExists: (p) => existsSync51(p),
29198
+ worktreeExists: (p) => existsSync52(p),
29115
29199
  prepareDropFile,
29116
29200
  startProxy: (wt, url, n) => launchProxy(crewInvocation(CREW_HOME), wt, url, Number(n), { host: hit.row.host || "localhost", inject: hit.repo.config.periscope?.inspector !== "repo" }),
29117
29201
  caps: periscopeCaps(cfg.ship),
@@ -29208,7 +29292,7 @@ ${where.join("\n")}`);
29208
29292
  prepare: async (path) => {
29209
29293
  for (const f of repoP.config.worktrees.copy) {
29210
29294
  const src = join56(repoP.dir, f);
29211
- if (!existsSync51(src)) continue;
29295
+ if (!existsSync52(src)) continue;
29212
29296
  mkdirSync38(dirOf2(join56(path, f)), { recursive: true });
29213
29297
  copyFileSync2(src, join56(path, f));
29214
29298
  }
@@ -29700,7 +29784,7 @@ ${where.join("\n")}`);
29700
29784
  }
29701
29785
  if (row) {
29702
29786
  await tracker.deleteRepoRow(row.id);
29703
- if (existsSync51(resolvedFile)) {
29787
+ if (existsSync52(resolvedFile)) {
29704
29788
  const raw = JSON.parse(readFileSync46(resolvedFile, "utf8"));
29705
29789
  if (raw.repoNames) delete raw.repoNames[row.id];
29706
29790
  if (raw.repoRemotes) delete raw.repoRemotes[row.id];
@@ -29708,13 +29792,13 @@ ${where.join("\n")}`);
29708
29792
  `);
29709
29793
  }
29710
29794
  }
29711
- if (existsSync51(join56(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync25(dir, { recursive: true, force: true });
29795
+ if (existsSync52(join56(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync25(dir, { recursive: true, force: true });
29712
29796
  say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
29713
29797
  break;
29714
29798
  }
29715
29799
  const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
29716
- const fresh = !existsSync51(join56(dir, ".git"));
29717
- if (existsSync51(dir) && fresh && readdirSync20(dir).length > 0) fail2(`${dir} exists and is not a git checkout \u2014 move it aside`);
29800
+ const fresh = !existsSync52(join56(dir, ".git"));
29801
+ if (existsSync52(dir) && fresh && readdirSync20(dir).length > 0) fail2(`${dir} exists and is not a git checkout \u2014 move it aside`);
29718
29802
  if (dryRun) {
29719
29803
  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`);
29720
29804
  break;
@@ -29740,7 +29824,7 @@ ${where.join("\n")}`);
29740
29824
  spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
29741
29825
  });
29742
29826
  repoId = (await tracker.createRepoRow(payload)).id;
29743
- if (existsSync51(resolvedFile)) {
29827
+ if (existsSync52(resolvedFile)) {
29744
29828
  const raw = JSON.parse(readFileSync46(resolvedFile, "utf8"));
29745
29829
  raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
29746
29830
  raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
@@ -30096,7 +30180,7 @@ watermark: ${state.watermark()}
30096
30180
  }
30097
30181
  for (const r of repos) {
30098
30182
  const remote = r.config.branch.remote;
30099
- if (existsSync51(r.dir)) {
30183
+ if (existsSync52(r.dir)) {
30100
30184
  pushAuth.push({
30101
30185
  route: c.route,
30102
30186
  repo: r.name,
@@ -30197,7 +30281,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
30197
30281
  }
30198
30282
  try {
30199
30283
  const evFile = eventFileFor(cfg.ship.stateDir);
30200
- const evSize = existsSync51(evFile) ? statSync16(evFile).size : 0;
30284
+ const evSize = existsSync52(evFile) ? statSync16(evFile).size : 0;
30201
30285
  process.stdout.write(
30202
30286
  evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
30203
30287
  ` : "events log: ok\n"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tablation/crew",
3
- "version": "0.126.6",
3
+ "version": "0.126.7",
4
4
  "description": "A standing team of headless agents that picks work off a Tablation board, does it on a machine you control, and reports back.",
5
5
  "type": "module",
6
6
  "license": "MIT",