@tablation/crew 0.73.2 → 0.74.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +496 -438
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -128,176 +128,10 @@ function formatStaleOwnLock(scope, holder) {
128
128
  `;
129
129
  }
130
130
 
131
- // src/selftest.ts
132
- import { spawn as spawn3 } from "child_process";
133
- import { existsSync as existsSync19, mkdirSync as mkdirSync10, mkdtempSync, readFileSync as readFileSync16, rmSync as rmSync9, writeFileSync as writeFileSync11 } from "fs";
134
- import { tmpdir as tmpdir2 } from "os";
135
- import { join as join19 } from "path";
136
-
137
- // src/agent.ts
138
- import { spawn as spawn2, spawnSync as spawnSync5 } from "child_process";
139
- import { readFileSync as readFileSync15, existsSync as existsSync18, statSync as statSync5, mkdirSync as mkdirSync9, writeFileSync as writeFileSync10, createWriteStream, openSync as openSync3, closeSync as closeSync3, readSync } from "fs";
140
- import { StringDecoder } from "string_decoder";
141
- import { finished } from "stream/promises";
142
- import { join as join18, dirname as dirname7, basename as basename7 } from "path";
143
- import { createHash as createHash3 } from "crypto";
144
-
145
- // src/config.ts
146
- import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
147
- import { resolve, dirname as dirname3, isAbsolute, join as join8, basename as basename3 } from "path";
148
- import { homedir as homedir5, tmpdir } from "os";
149
- import { parse as parse2 } from "yaml";
150
-
151
- // src/events.ts
152
- import {
153
- appendFileSync,
154
- mkdirSync,
155
- existsSync as existsSync2,
156
- statSync,
157
- renameSync,
158
- openSync,
159
- closeSync,
160
- rmSync
161
- } from "fs";
162
- import { join, dirname } from "path";
163
- function render(e) {
164
- const scope = e.role ? `${e.step}[${e.role}]` : e.step;
165
- const ticket = e.ticket ? ` ${e.ticket}` : "";
166
- const level = e.level === "info" ? "" : `${e.level.toUpperCase()}: `;
167
- return `${e.at} ${scope}:${ticket} ${level}${e.message}`;
168
- }
169
- var DEFAULT_EVENTS_ROTATE_BYTES = 64 * 1024 * 1024;
170
- var ROTATED_EVENTS_RE = /^events\.\d{8}T\d{6}Z(?:-\d+)?\.jsonl$/;
171
- var ROTATE_LOCK_STALE_MS = 6e4;
172
- function rotateEventsFile(file, maxBytes, now = /* @__PURE__ */ new Date()) {
173
- try {
174
- if (!existsSync2(file) || statSync(file).size <= maxBytes) return void 0;
175
- } catch {
176
- return void 0;
177
- }
178
- const lock = `${file}.rotate.lock`;
179
- try {
180
- try {
181
- closeSync(openSync(lock, "wx"));
182
- } catch {
183
- try {
184
- if (Date.now() - statSync(lock).mtimeMs < ROTATE_LOCK_STALE_MS) return void 0;
185
- rmSync(lock, { force: true });
186
- closeSync(openSync(lock, "wx"));
187
- } catch {
188
- return void 0;
189
- }
190
- }
191
- try {
192
- if (statSync(file).size <= maxBytes) return void 0;
193
- const stamp = now.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
194
- let dest = join(dirname(file), `events.${stamp}.jsonl`);
195
- for (let n = 1; existsSync2(dest); n++) dest = join(dirname(file), `events.${stamp}-${n}.jsonl`);
196
- renameSync(file, dest);
197
- return dest;
198
- } finally {
199
- rmSync(lock, { force: true });
200
- }
201
- } catch {
202
- return void 0;
203
- }
204
- }
205
- var Emitter = class _Emitter {
206
- cycle;
207
- opts;
208
- now;
209
- step = "poll";
210
- role;
211
- constructor(opts) {
212
- this.opts = opts;
213
- this.now = opts.now ?? (() => /* @__PURE__ */ new Date());
214
- this.cycle = opts.cycleId ?? this.now().toISOString().replace(/[-:.TZ]/g, "").slice(0, 14);
215
- for (const f of [opts.eventFile, opts.logFile]) {
216
- if (!f) continue;
217
- try {
218
- mkdirSync(dirname(f), { recursive: true });
219
- } catch {
220
- }
221
- }
222
- }
223
- /** Everything emitted after this belongs to `step`, until the next one. */
224
- enter(step, role) {
225
- this.step = step;
226
- this.role = role;
227
- }
228
- emit(message, extra = {}) {
229
- const e = {
230
- at: this.now().toISOString(),
231
- cycle: this.cycle,
232
- route: this.opts.route,
233
- step: extra.step ?? this.step,
234
- level: extra.level ?? "info",
235
- message,
236
- ...extra.role ?? this.role ? { role: extra.role ?? this.role } : {},
237
- ...extra.ticket ? { ticket: extra.ticket } : {},
238
- ...extra.data ? { data: extra.data } : {}
239
- };
240
- const line = render(e);
241
- try {
242
- if (this.opts.eventFile && this.opts.rotateBytes) rotateEventsFile(this.opts.eventFile, this.opts.rotateBytes, this.now());
243
- } catch {
244
- }
245
- try {
246
- if (this.opts.eventFile) appendFileSync(this.opts.eventFile, `${JSON.stringify(e)}
247
- `);
248
- } catch {
249
- }
250
- try {
251
- if (this.opts.logFile) appendFileSync(this.opts.logFile, `${line}
252
- `);
253
- } catch {
254
- }
255
- try {
256
- (this.opts.console ?? ((l) => process.stderr.write(`${l}
257
- `)))(line);
258
- } catch {
259
- }
260
- return e;
261
- }
262
- warn(message, extra = {}) {
263
- return this.emit(message, { ...extra, level: "warn" });
264
- }
265
- error(message, extra = {}) {
266
- return this.emit(message, { ...extra, level: "error" });
267
- }
268
- /**
269
- * A view of this emitter labelling every event with a different route,
270
- * sharing the same sinks and cycle id. For a fleet-wide run, the top-level
271
- * Emitter is stamped with just one route (see cli.ts) — a phase that
272
- * actually knows which route it's working (release, per-repo poll) uses
273
- * this instead, so its events aren't all attributed to `routes[0]`.
274
- */
275
- forRoute(route) {
276
- return new _Emitter({ ...this.opts, route, cycleId: this.cycle });
277
- }
278
- /** Times a step and emits its outcome, so a view can show duration. */
279
- async timed(step, message, fn, role) {
280
- this.enter(step, role);
281
- const started = this.now().getTime();
282
- try {
283
- const result = await fn();
284
- this.emit(message, { data: { ms: this.now().getTime() - started, ok: true } });
285
- return result;
286
- } catch (err) {
287
- this.error(`${message} \u2014 ${err.message}`, {
288
- data: { ms: this.now().getTime() - started, ok: false }
289
- });
290
- throw err;
291
- }
292
- }
293
- };
294
- var eventFileFor = (stateDir) => join(stateDir, "events.jsonl");
295
-
296
- // src/pair-install.ts
297
- import { existsSync as existsSync5, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
298
- import { spawnSync as spawnSync3 } from "child_process";
299
- import { homedir as homedir4 } from "os";
300
- import { join as join6 } from "path";
131
+ // src/adapters/claude.ts
132
+ import { readFileSync as readFileSync4 } from "fs";
133
+ import { homedir as homedir3 } from "os";
134
+ import { join as join4 } from "path";
301
135
 
302
136
  // src/stream.ts
303
137
  function toolTarget(name, input) {
@@ -349,19 +183,19 @@ function extractResult(line) {
349
183
 
350
184
  // src/claude-mcp.ts
351
185
  import { spawnSync } from "child_process";
352
- import { readFileSync as readFileSync3 } from "fs";
186
+ import { readFileSync as readFileSync2 } from "fs";
353
187
  import { homedir } from "os";
354
- import { join as join3 } from "path";
188
+ import { join as join2 } from "path";
355
189
 
356
190
  // src/mcp-registrations.ts
357
191
  import { createHash } from "crypto";
358
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
359
- import { join as join2 } from "path";
360
- var fileFor = (stateDir) => join2(stateDir, "mcp-registrations.json");
192
+ import { mkdirSync, readFileSync, writeFileSync } from "fs";
193
+ import { join } from "path";
194
+ var fileFor = (stateDir) => join(stateDir, "mcp-registrations.json");
361
195
  var keyFingerprint = (key) => createHash("sha256").update(key).digest("hex").slice(0, 8);
362
196
  function readMcpRegistrations(stateDir) {
363
197
  try {
364
- const j = JSON.parse(readFileSync2(fileFor(stateDir), "utf8"));
198
+ const j = JSON.parse(readFileSync(fileFor(stateDir), "utf8"));
365
199
  return Array.isArray(j) ? j : [];
366
200
  } catch {
367
201
  return [];
@@ -369,7 +203,7 @@ function readMcpRegistrations(stateDir) {
369
203
  }
370
204
  function recordMcpRegistration(stateDir, r, now = /* @__PURE__ */ new Date()) {
371
205
  const rest = readMcpRegistrations(stateDir).filter((x) => !(x.harness === r.harness && x.scope === r.scope && x.name === r.name));
372
- mkdirSync2(stateDir, { recursive: true });
206
+ mkdirSync(stateDir, { recursive: true });
373
207
  writeFileSync(fileFor(stateDir), `${JSON.stringify([...rest, { ...r, at: now.toISOString() }], null, 2)}
374
208
  `, { mode: 384 });
375
209
  }
@@ -393,7 +227,7 @@ function runClaude(bin, args) {
393
227
  }
394
228
  function userScopeEntry(home) {
395
229
  try {
396
- const j = JSON.parse(readFileSync3(join3(home, ".claude.json"), "utf8"));
230
+ const j = JSON.parse(readFileSync2(join2(home, ".claude.json"), "utf8"));
397
231
  return j.mcpServers?.[MCP_SERVER_NAME];
398
232
  } catch {
399
233
  return void 0;
@@ -477,8 +311,8 @@ function removeClaudeMcp(o) {
477
311
  }
478
312
 
479
313
  // src/launcher.ts
480
- import { basename, join as join4 } from "path";
481
- import { existsSync as existsSync3, readFileSync as readFileSync4, readdirSync } from "fs";
314
+ import { basename, join as join3 } from "path";
315
+ import { existsSync as existsSync2, readFileSync as readFileSync3, readdirSync } from "fs";
482
316
  import { homedir as homedir2 } from "os";
483
317
  function isOriLauncher(spec) {
484
318
  return !!spec.launcher && basename(spec.launcher) === "ori";
@@ -487,7 +321,7 @@ function isOmlxLauncher(spec) {
487
321
  return !!spec.launcher && basename(spec.launcher) === "omlx";
488
322
  }
489
323
  function defaultOmlxBin(home = homedir2()) {
490
- return join4(home, ".omlx", "bin", "omlx");
324
+ return join3(home, ".omlx", "bin", "omlx");
491
325
  }
492
326
  function launcherExecutable(spec, home) {
493
327
  if (!spec.launcher) return void 0;
@@ -540,8 +374,8 @@ function omlxModelPresent(model, dirs) {
540
374
  });
541
375
  }
542
376
  function readOmlxSettings(home = homedir2()) {
543
- const p = join4(home, ".omlx", "settings.json");
544
- return existsSync3(p) ? parseOmlxSettings(readFileSync4(p, "utf8")) : void 0;
377
+ const p = join3(home, ".omlx", "settings.json");
378
+ return existsSync2(p) ? parseOmlxSettings(readFileSync3(p, "utf8")) : void 0;
545
379
  }
546
380
 
547
381
  // src/capability.ts
@@ -592,6 +426,24 @@ var claudePair = {
592
426
  },
593
427
  removePair: (o) => removeClaudeMcp({ bin: o.bin ?? "claude", dryRun: o.dryRun })
594
428
  };
429
+ function subscriptionSettings() {
430
+ return JSON.stringify({ env: Object.fromEntries(BILLING_VARS_TO_UNSET.map((v) => [v, ""])) });
431
+ }
432
+ function describeClaudeAuth(spec, env = process.env, home = homedir3()) {
433
+ if (spec.auth === "subscription") return "subscription (settings override)";
434
+ let fromSettings;
435
+ try {
436
+ const parsed = JSON.parse(readFileSync4(join4(home, ".claude", "settings.json"), "utf8"));
437
+ const e = parsed.env ?? {};
438
+ const on = (k) => e[k] !== void 0 && e[k] !== "" && e[k] !== "0";
439
+ if (on("CLAUDE_CODE_USE_VERTEX")) fromSettings = "vertex from ~/.claude/settings.json";
440
+ else if (on("ANTHROPIC_API_KEY") || on("ANTHROPIC_AUTH_TOKEN")) fromSettings = "API key from ~/.claude/settings.json";
441
+ } catch {
442
+ }
443
+ if (fromSettings) return `inherit: ${fromSettings}`;
444
+ const shell = BILLING_VARS_TO_UNSET.find((v) => v.startsWith("ANTHROPIC_API") && env[v]);
445
+ return shell ? `inherit: ${shell} in env (unset at exec)` : "inherit: subscription login";
446
+ }
595
447
  var claudeAdapter = {
596
448
  harness: "claude",
597
449
  displayName: "Claude Code",
@@ -646,6 +498,7 @@ var claudeAdapter = {
646
498
  }
647
499
  }
648
500
  })] : [],
501
+ ...spec.auth === "subscription" ? ["--settings", subscriptionSettings()] : [],
649
502
  ...spec.args
650
503
  ];
651
504
  return wrapInvocation(spec, { model: o.model, args });
@@ -672,14 +525,185 @@ var claudeAdapter = {
672
525
  ensureMcpResult: ensureClaudeMcpResult
673
526
  };
674
527
 
528
+ // src/selftest.ts
529
+ import { spawn as spawn3 } from "child_process";
530
+ import { existsSync as existsSync19, mkdirSync as mkdirSync10, mkdtempSync, readFileSync as readFileSync17, rmSync as rmSync9, writeFileSync as writeFileSync11 } from "fs";
531
+ import { tmpdir as tmpdir2 } from "os";
532
+ import { join as join20 } from "path";
533
+
534
+ // src/agent.ts
535
+ import { spawn as spawn2, spawnSync as spawnSync5 } from "child_process";
536
+ import { readFileSync as readFileSync16, existsSync as existsSync18, statSync as statSync5, mkdirSync as mkdirSync9, writeFileSync as writeFileSync10, createWriteStream, openSync as openSync3, closeSync as closeSync3, readSync } from "fs";
537
+ import { StringDecoder } from "string_decoder";
538
+ import { finished } from "stream/promises";
539
+ import { join as join19, dirname as dirname7, basename as basename7 } from "path";
540
+ import { createHash as createHash3 } from "crypto";
541
+
542
+ // src/config.ts
543
+ import { readFileSync as readFileSync8, existsSync as existsSync7 } from "fs";
544
+ import { resolve, dirname as dirname3, isAbsolute, join as join9, basename as basename3 } from "path";
545
+ import { homedir as homedir6, tmpdir } from "os";
546
+ import { parse as parse2 } from "yaml";
547
+
548
+ // src/events.ts
549
+ import {
550
+ appendFileSync,
551
+ mkdirSync as mkdirSync2,
552
+ existsSync as existsSync3,
553
+ statSync,
554
+ renameSync,
555
+ openSync,
556
+ closeSync,
557
+ rmSync
558
+ } from "fs";
559
+ import { join as join5, dirname } from "path";
560
+ function render(e) {
561
+ const scope = e.role ? `${e.step}[${e.role}]` : e.step;
562
+ const ticket = e.ticket ? ` ${e.ticket}` : "";
563
+ const level = e.level === "info" ? "" : `${e.level.toUpperCase()}: `;
564
+ return `${e.at} ${scope}:${ticket} ${level}${e.message}`;
565
+ }
566
+ var DEFAULT_EVENTS_ROTATE_BYTES = 64 * 1024 * 1024;
567
+ var ROTATED_EVENTS_RE = /^events\.\d{8}T\d{6}Z(?:-\d+)?\.jsonl$/;
568
+ var ROTATE_LOCK_STALE_MS = 6e4;
569
+ function rotateEventsFile(file, maxBytes, now = /* @__PURE__ */ new Date()) {
570
+ try {
571
+ if (!existsSync3(file) || statSync(file).size <= maxBytes) return void 0;
572
+ } catch {
573
+ return void 0;
574
+ }
575
+ const lock = `${file}.rotate.lock`;
576
+ try {
577
+ try {
578
+ closeSync(openSync(lock, "wx"));
579
+ } catch {
580
+ try {
581
+ if (Date.now() - statSync(lock).mtimeMs < ROTATE_LOCK_STALE_MS) return void 0;
582
+ rmSync(lock, { force: true });
583
+ closeSync(openSync(lock, "wx"));
584
+ } catch {
585
+ return void 0;
586
+ }
587
+ }
588
+ try {
589
+ if (statSync(file).size <= maxBytes) return void 0;
590
+ const stamp = now.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
591
+ let dest = join5(dirname(file), `events.${stamp}.jsonl`);
592
+ for (let n = 1; existsSync3(dest); n++) dest = join5(dirname(file), `events.${stamp}-${n}.jsonl`);
593
+ renameSync(file, dest);
594
+ return dest;
595
+ } finally {
596
+ rmSync(lock, { force: true });
597
+ }
598
+ } catch {
599
+ return void 0;
600
+ }
601
+ }
602
+ var Emitter = class _Emitter {
603
+ cycle;
604
+ opts;
605
+ now;
606
+ step = "poll";
607
+ role;
608
+ constructor(opts) {
609
+ this.opts = opts;
610
+ this.now = opts.now ?? (() => /* @__PURE__ */ new Date());
611
+ this.cycle = opts.cycleId ?? this.now().toISOString().replace(/[-:.TZ]/g, "").slice(0, 14);
612
+ for (const f of [opts.eventFile, opts.logFile]) {
613
+ if (!f) continue;
614
+ try {
615
+ mkdirSync2(dirname(f), { recursive: true });
616
+ } catch {
617
+ }
618
+ }
619
+ }
620
+ /** Everything emitted after this belongs to `step`, until the next one. */
621
+ enter(step, role) {
622
+ this.step = step;
623
+ this.role = role;
624
+ }
625
+ emit(message, extra = {}) {
626
+ const e = {
627
+ at: this.now().toISOString(),
628
+ cycle: this.cycle,
629
+ route: this.opts.route,
630
+ step: extra.step ?? this.step,
631
+ level: extra.level ?? "info",
632
+ message,
633
+ ...extra.role ?? this.role ? { role: extra.role ?? this.role } : {},
634
+ ...extra.ticket ? { ticket: extra.ticket } : {},
635
+ ...extra.data ? { data: extra.data } : {}
636
+ };
637
+ const line = render(e);
638
+ try {
639
+ if (this.opts.eventFile && this.opts.rotateBytes) rotateEventsFile(this.opts.eventFile, this.opts.rotateBytes, this.now());
640
+ } catch {
641
+ }
642
+ try {
643
+ if (this.opts.eventFile) appendFileSync(this.opts.eventFile, `${JSON.stringify(e)}
644
+ `);
645
+ } catch {
646
+ }
647
+ try {
648
+ if (this.opts.logFile) appendFileSync(this.opts.logFile, `${line}
649
+ `);
650
+ } catch {
651
+ }
652
+ try {
653
+ (this.opts.console ?? ((l) => process.stderr.write(`${l}
654
+ `)))(line);
655
+ } catch {
656
+ }
657
+ return e;
658
+ }
659
+ warn(message, extra = {}) {
660
+ return this.emit(message, { ...extra, level: "warn" });
661
+ }
662
+ error(message, extra = {}) {
663
+ return this.emit(message, { ...extra, level: "error" });
664
+ }
665
+ /**
666
+ * A view of this emitter labelling every event with a different route,
667
+ * sharing the same sinks and cycle id. For a fleet-wide run, the top-level
668
+ * Emitter is stamped with just one route (see cli.ts) — a phase that
669
+ * actually knows which route it's working (release, per-repo poll) uses
670
+ * this instead, so its events aren't all attributed to `routes[0]`.
671
+ */
672
+ forRoute(route) {
673
+ return new _Emitter({ ...this.opts, route, cycleId: this.cycle });
674
+ }
675
+ /** Times a step and emits its outcome, so a view can show duration. */
676
+ async timed(step, message, fn, role) {
677
+ this.enter(step, role);
678
+ const started = this.now().getTime();
679
+ try {
680
+ const result = await fn();
681
+ this.emit(message, { data: { ms: this.now().getTime() - started, ok: true } });
682
+ return result;
683
+ } catch (err) {
684
+ this.error(`${message} \u2014 ${err.message}`, {
685
+ data: { ms: this.now().getTime() - started, ok: false }
686
+ });
687
+ throw err;
688
+ }
689
+ }
690
+ };
691
+ var eventFileFor = (stateDir) => join5(stateDir, "events.jsonl");
692
+
693
+ // src/pair-install.ts
694
+ import { existsSync as existsSync5, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
695
+ import { spawnSync as spawnSync3 } from "child_process";
696
+ import { homedir as homedir5 } from "os";
697
+ import { join as join7 } from "path";
698
+
675
699
  // src/adapters/codex.ts
676
700
  import { spawnSync as spawnSync2 } from "child_process";
677
- import { homedir as homedir3 } from "os";
678
- import { join as join5 } from "path";
701
+ import { homedir as homedir4 } from "os";
702
+ import { join as join6 } from "path";
679
703
  import { rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
680
704
 
681
705
  // src/pair-files.ts
682
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync2, rmSync as rmSync2 } from "fs";
706
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync2, rmSync as rmSync2 } from "fs";
683
707
  import { dirname as dirname2 } from "path";
684
708
  var PAIR_BLOCK_START = "<!-- crew:pair start -->";
685
709
  var PAIR_BLOCK_END = "<!-- crew:pair end -->";
@@ -720,7 +744,7 @@ function removePairBlock(text) {
720
744
  return text.replace(new RegExp(`\\n*${BLOCK_RE.source}`), "\n").replace(/^\n+/, "").replace(/\n{3,}/g, "\n\n");
721
745
  }
722
746
  function readIfExists(path) {
723
- return existsSync4(path) ? readFileSync5(path, "utf8") : void 0;
747
+ return existsSync4(path) ? readFileSync6(path, "utf8") : void 0;
724
748
  }
725
749
  function writeFileMkdir(path, text) {
726
750
  mkdirSync3(dirname2(path), { recursive: true });
@@ -867,8 +891,8 @@ function codexTable(url) {
867
891
  return [`[mcp_servers.${SERVER}]`, `url = ${tomlString(url)}`, `bearer_token_env_var = ${tomlString(PAIR_KEY_VAR)}`];
868
892
  }
869
893
  var codexPaths = (home) => ({
870
- instructions: join5(home, ".codex", "AGENTS.md"),
871
- config: join5(home, ".codex", "config.toml")
894
+ instructions: join6(home, ".codex", "AGENTS.md"),
895
+ config: join6(home, ".codex", "config.toml")
872
896
  });
873
897
  function codexMcpState(config, url) {
874
898
  const lines = config.split("\n");
@@ -884,7 +908,7 @@ var codexPair = {
884
908
  harness: "codex",
885
909
  displayName: "Codex",
886
910
  installPair(o) {
887
- const p = codexPaths(o.home ?? homedir3());
911
+ const p = codexPaths(o.home ?? homedir4());
888
912
  const config = readIfExists(p.config) ?? "";
889
913
  const st = codexMcpState(config, o.url);
890
914
  if (st.state === "different" && !o.replace) {
@@ -901,7 +925,7 @@ var codexPair = {
901
925
  return `pair(codex): ${o.dryRun ? "(dry run) would install" : "installed"} the Pair block in ${p.instructions} and ${SERVER} in ${p.config} (key read from $${PAIR_KEY_VAR})`;
902
926
  },
903
927
  verifyPair(o) {
904
- const p = codexPaths(o.home ?? homedir3());
928
+ const p = codexPaths(o.home ?? homedir4());
905
929
  const b = blockVerdict(p.instructions, "codex");
906
930
  if (!b.ok) return b;
907
931
  const st = codexMcpState(readIfExists(p.config) ?? "", o.url);
@@ -910,7 +934,7 @@ var codexPair = {
910
934
  return { ok: true, detail: `${p.instructions}, ${p.config}` };
911
935
  },
912
936
  removePair(o) {
913
- const p = codexPaths(o.home ?? homedir3());
937
+ const p = codexPaths(o.home ?? homedir4());
914
938
  const hadBlock = removeBlockFile(p.instructions, o.dryRun);
915
939
  const config = readIfExists(p.config);
916
940
  const st = codexMcpState(config ?? "", o.url);
@@ -996,11 +1020,11 @@ var codexAdapter = {
996
1020
  extractTurnTokens: extractCodexTurnTokens,
997
1021
  inspectMcp(o) {
998
1022
  if (binMissing(o.bin)) return { state: "no-binary" };
999
- const st = codexMcpState(readIfExists(codexPaths(o.home ?? homedir3()).config) ?? "", o.url);
1023
+ const st = codexMcpState(readIfExists(codexPaths(o.home ?? homedir4()).config) ?? "", o.url);
1000
1024
  return st.state === "registered" ? { state: "registered", url: o.url } : st;
1001
1025
  },
1002
1026
  ensureMcp(o) {
1003
- const p = codexPaths(o.home ?? homedir3());
1027
+ const p = codexPaths(o.home ?? homedir4());
1004
1028
  const config = readIfExists(p.config) ?? "";
1005
1029
  const st = codexMcpState(config, o.url);
1006
1030
  if (st.state === "registered") return `mcp: ${SERVER} already registered with Codex \u2192 ${o.url}`;
@@ -1015,8 +1039,8 @@ var codexAdapter = {
1015
1039
 
1016
1040
  // src/pair-install.ts
1017
1041
  var geminiPaths = (home) => ({
1018
- instructions: join6(home, ".gemini", "GEMINI.md"),
1019
- config: join6(home, ".gemini", "settings.json")
1042
+ instructions: join7(home, ".gemini", "GEMINI.md"),
1043
+ config: join7(home, ".gemini", "settings.json")
1020
1044
  });
1021
1045
  function readGeminiSettings(path) {
1022
1046
  const text = readIfExists(path);
@@ -1037,14 +1061,14 @@ function geminiMcpState(s, url) {
1037
1061
  return { state: "registered" };
1038
1062
  }
1039
1063
  function inspectGeminiMcp(o) {
1040
- const p = geminiPaths(o.home ?? homedir4());
1064
+ const p = geminiPaths(o.home ?? homedir5());
1041
1065
  const settings = readGeminiSettings(p.config);
1042
1066
  if (!settings) return { state: "different", detail: `${p.config} is not a JSON object` };
1043
1067
  const st = geminiMcpState(settings, o.url);
1044
1068
  return st.state === "registered" ? { state: "registered", url: o.url } : st;
1045
1069
  }
1046
1070
  function ensureGeminiMcp(o) {
1047
- const p = geminiPaths(o.home ?? homedir4());
1071
+ const p = geminiPaths(o.home ?? homedir5());
1048
1072
  const settings = readGeminiSettings(p.config);
1049
1073
  if (!settings) return `gemini: ${p.config} is not a JSON object \u2014 fix it by hand; nothing was written`;
1050
1074
  const st = geminiMcpState(settings, o.url);
@@ -1062,7 +1086,7 @@ var geminiPair = {
1062
1086
  harness: "gemini",
1063
1087
  displayName: "Gemini CLI",
1064
1088
  installPair(o) {
1065
- const p = geminiPaths(o.home ?? homedir4());
1089
+ const p = geminiPaths(o.home ?? homedir5());
1066
1090
  const settings = readGeminiSettings(p.config);
1067
1091
  if (!settings) return `pair(gemini): ${p.config} is not a JSON object \u2014 fix it by hand; nothing was written`;
1068
1092
  const st = geminiMcpState(settings, o.url);
@@ -1081,7 +1105,7 @@ var geminiPair = {
1081
1105
  return `pair(gemini): ${o.dryRun ? "(dry run) would install" : "installed"} the Pair block in ${p.instructions} and ${SERVER} in ${p.config} (key read from $${PAIR_KEY_VAR})`;
1082
1106
  },
1083
1107
  verifyPair(o) {
1084
- const p = geminiPaths(o.home ?? homedir4());
1108
+ const p = geminiPaths(o.home ?? homedir5());
1085
1109
  const b = blockVerdict(p.instructions, "gemini");
1086
1110
  if (!b.ok) return b;
1087
1111
  const settings = readGeminiSettings(p.config);
@@ -1092,7 +1116,7 @@ var geminiPair = {
1092
1116
  return { ok: true, detail: `${p.instructions}, ${p.config}` };
1093
1117
  },
1094
1118
  removePair(o) {
1095
- const p = geminiPaths(o.home ?? homedir4());
1119
+ const p = geminiPaths(o.home ?? homedir5());
1096
1120
  const hadBlock = removeBlockFile(p.instructions, o.dryRun);
1097
1121
  const settings = readGeminiSettings(p.config);
1098
1122
  const st = settings ? geminiMcpState(settings, o.url) : { state: "missing" };
@@ -1113,10 +1137,10 @@ var geminiPair = {
1113
1137
  };
1114
1138
  var VSCODE_FLAVOURS = ["Code", "Code - Insiders"];
1115
1139
  function vscodeUserDirs(o) {
1116
- const home = o.home ?? homedir4();
1140
+ const home = o.home ?? homedir5();
1117
1141
  const platform2 = o.platform ?? process.platform;
1118
- const root = platform2 === "darwin" ? join6(home, "Library", "Application Support") : platform2 === "win32" ? process.env.APPDATA ?? join6(home, "AppData", "Roaming") : join6(home, ".config");
1119
- const all = VSCODE_FLAVOURS.map((f) => join6(root, f, "User"));
1142
+ const root = platform2 === "darwin" ? join7(home, "Library", "Application Support") : platform2 === "win32" ? process.env.APPDATA ?? join7(home, "AppData", "Roaming") : join7(home, ".config");
1143
+ const all = VSCODE_FLAVOURS.map((f) => join7(root, f, "User"));
1120
1144
  const present = all.filter((d) => existsSync5(d));
1121
1145
  return present.length > 0 ? present : [all[0]];
1122
1146
  }
@@ -1164,8 +1188,8 @@ var copilotPair = {
1164
1188
  installPair(o) {
1165
1189
  const lines = [];
1166
1190
  for (const dir of vscodeUserDirs(o)) {
1167
- const mcpPath = join6(dir, "mcp.json");
1168
- const instrPath = join6(dir, "prompts", "crew-pair.instructions.md");
1191
+ const mcpPath = join7(dir, "mcp.json");
1192
+ const instrPath = join7(dir, "prompts", "crew-pair.instructions.md");
1169
1193
  const settings = readVscodeMcp(mcpPath);
1170
1194
  if (!settings) {
1171
1195
  lines.push(`pair(copilot): ${mcpPath} is not a plain JSON object (comments are not supported) \u2014 fix it by hand; nothing was written there`);
@@ -1193,8 +1217,8 @@ var copilotPair = {
1193
1217
  },
1194
1218
  verifyPair(o) {
1195
1219
  const verdicts = vscodeUserDirs(o).map((dir) => {
1196
- const instrPath = join6(dir, "prompts", "crew-pair.instructions.md");
1197
- const mcpPath = join6(dir, "mcp.json");
1220
+ const instrPath = join7(dir, "prompts", "crew-pair.instructions.md");
1221
+ const mcpPath = join7(dir, "mcp.json");
1198
1222
  const b = blockVerdict(instrPath, "copilot");
1199
1223
  if (!b.ok) return b;
1200
1224
  const settings = readVscodeMcp(mcpPath);
@@ -1209,8 +1233,8 @@ var copilotPair = {
1209
1233
  removePair(o) {
1210
1234
  const lines = [];
1211
1235
  for (const dir of vscodeUserDirs(o)) {
1212
- const mcpPath = join6(dir, "mcp.json");
1213
- const hadBlock = removeCopilotInstructions(join6(dir, "prompts", "crew-pair.instructions.md"), o.dryRun);
1236
+ const mcpPath = join7(dir, "mcp.json");
1237
+ const hadBlock = removeCopilotInstructions(join7(dir, "prompts", "crew-pair.instructions.md"), o.dryRun);
1214
1238
  const settings = readVscodeMcp(mcpPath);
1215
1239
  const st = settings ? copilotMcpState(settings, o.url) : { state: "missing" };
1216
1240
  const hadMcp = st.state === "registered";
@@ -1509,8 +1533,8 @@ function explain(ship, required) {
1509
1533
  }
1510
1534
 
1511
1535
  // src/repo-config.ts
1512
- import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
1513
- import { join as join7, basename as basename2 } from "path";
1536
+ import { readFileSync as readFileSync7, existsSync as existsSync6 } from "fs";
1537
+ import { join as join8, basename as basename2 } from "path";
1514
1538
  import { parse } from "yaml";
1515
1539
  var SPEC_VERSION = 1;
1516
1540
  var REPO_CONFIG_FILENAMES = [".crew.yaml", ".crew.yml", ".crew.json"];
@@ -1593,7 +1617,7 @@ var MODES = ["local", "integrate", "ci_manual", "ci_auto", "external"];
1593
1617
  var PROVIDERS = ["github", "buildkite", "other", "none"];
1594
1618
  function findRepoConfig(dir) {
1595
1619
  for (const name of REPO_CONFIG_FILENAMES) {
1596
- const p = join7(dir, name);
1620
+ const p = join8(dir, name);
1597
1621
  if (existsSync6(p)) return p;
1598
1622
  }
1599
1623
  return null;
@@ -1832,7 +1856,7 @@ function parseRepoConfig(text, file) {
1832
1856
  function loadRepoConfig(dir) {
1833
1857
  const file = findRepoConfig(dir);
1834
1858
  if (!file) return null;
1835
- return parseRepoConfig(readFileSync6(file, "utf8"), file);
1859
+ return parseRepoConfig(readFileSync7(file, "utf8"), file);
1836
1860
  }
1837
1861
  function hookLabel(cfg, hook2) {
1838
1862
  return cfg.labels[hook2] ?? cfg.hooks[hook2] ?? hook2;
@@ -2281,16 +2305,16 @@ function mergeRouteRelease(route, override) {
2281
2305
  };
2282
2306
  }
2283
2307
  function expand(p, base) {
2284
- let out = p.startsWith("~") ? p.replace(/^~/, homedir5()) : p;
2285
- out = out.replace(/\$\{?HOME\}?/g, homedir5());
2308
+ let out = p.startsWith("~") ? p.replace(/^~/, homedir6()) : p;
2309
+ out = out.replace(/\$\{?HOME\}?/g, homedir6());
2286
2310
  return isAbsolute(out) ? out : resolve(base, out);
2287
2311
  }
2288
2312
  function resolvePromptsDir(promptSet, crewHome, base) {
2289
- if (!promptSet) return join8(crewHome, "prompts", "default");
2313
+ if (!promptSet) return join9(crewHome, "prompts", "default");
2290
2314
  if (promptSet.includes("/") || promptSet.startsWith(".") || promptSet.startsWith("~")) {
2291
2315
  return expand(promptSet, base);
2292
2316
  }
2293
- return join8(crewHome, "prompts", promptSet);
2317
+ return join9(crewHome, "prompts", promptSet);
2294
2318
  }
2295
2319
  var Missing = class {
2296
2320
  paths = [];
@@ -2314,7 +2338,7 @@ var Missing = class {
2314
2338
  };
2315
2339
  function crewVersion(crewHome) {
2316
2340
  try {
2317
- const pkg = JSON.parse(readFileSync7(join8(crewHome, "package.json"), "utf8"));
2341
+ const pkg = JSON.parse(readFileSync8(join9(crewHome, "package.json"), "utf8"));
2318
2342
  return pkg.version ?? "0.0.0";
2319
2343
  } catch {
2320
2344
  return "0.0.0";
@@ -2324,8 +2348,8 @@ function configSearchPath(crewHome) {
2324
2348
  if (process.env.CREW_CONFIG) return [resolve(process.env.CREW_CONFIG)];
2325
2349
  const xdg = process.env.XDG_CONFIG_HOME;
2326
2350
  return [
2327
- ...xdg ? [join8(xdg, "crew", "crew.yaml")] : [],
2328
- join8(homedir5(), ".config", "crew", "crew.yaml"),
2351
+ ...xdg ? [join9(xdg, "crew", "crew.yaml")] : [],
2352
+ join9(homedir6(), ".config", "crew", "crew.yaml"),
2329
2353
  resolve(crewHome, "crew.yaml")
2330
2354
  ];
2331
2355
  }
@@ -2333,17 +2357,17 @@ function findConfigFile(crewHome) {
2333
2357
  const candidates = configSearchPath(crewHome);
2334
2358
  return candidates.find((c) => existsSync7(c)) ?? candidates[candidates.length - 1];
2335
2359
  }
2336
- var defaultConfigPath = () => join8(process.env.XDG_CONFIG_HOME ?? join8(homedir5(), ".config"), "crew", "crew.yaml");
2360
+ var defaultConfigPath = () => join9(process.env.XDG_CONFIG_HOME ?? join9(homedir6(), ".config"), "crew", "crew.yaml");
2337
2361
  function routeSlug(route) {
2338
2362
  return route.replace(/\//g, "-");
2339
2363
  }
2340
2364
  function resolvedPathFor(stateDir, route) {
2341
2365
  const [workspace, project] = splitRoute(route);
2342
- return join8(stateDir, "resolved", workspace, `${project}.json`);
2366
+ return join9(stateDir, "resolved", workspace, `${project}.json`);
2343
2367
  }
2344
2368
  function apiKeyPathFor(stateDir, route) {
2345
2369
  const [workspace, project] = splitRoute(route);
2346
- return join8(stateDir, "keys", workspace, `${project}.env`);
2370
+ return join9(stateDir, "keys", workspace, `${project}.env`);
2347
2371
  }
2348
2372
  function splitRoute(route) {
2349
2373
  const parts = route.split("/");
@@ -2356,7 +2380,7 @@ function readResolvedFile(stateDir, route) {
2356
2380
  const path = resolvedPathFor(stateDir, route);
2357
2381
  if (!existsSync7(path)) return void 0;
2358
2382
  try {
2359
- return JSON.parse(readFileSync7(path, "utf8"));
2383
+ return JSON.parse(readFileSync8(path, "utf8"));
2360
2384
  } catch (e) {
2361
2385
  throw new ConfigError(`${path}: not valid JSON \u2014 ${e.message}`);
2362
2386
  }
@@ -2421,12 +2445,12 @@ function loadConfig(crewHome, configFile, opts = {}) {
2421
2445
  ` + configSearchPath(crewHome).map((c) => ` - ${c}`).join("\n") + `
2422
2446
 
2423
2447
  Create the first of those:
2424
- mkdir -p ${dirname3(defaultConfigPath())} && cp ${join8(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
2448
+ mkdir -p ${dirname3(defaultConfigPath())} && cp ${join9(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
2425
2449
  );
2426
2450
  }
2427
2451
  let raw;
2428
2452
  try {
2429
- raw = parse2(opts.text ?? readFileSync7(file, "utf8"));
2453
+ raw = parse2(opts.text ?? readFileSync8(file, "utf8"));
2430
2454
  } catch (e) {
2431
2455
  throw new ConfigError(`${file}: not valid YAML \u2014 ${e.message}`);
2432
2456
  }
@@ -2504,7 +2528,7 @@ Create the first of those:
2504
2528
  useNvm: shipRaw.useNvm !== false,
2505
2529
  nvmSh: shipRaw.nvmSh,
2506
2530
  stateDir,
2507
- logFile: expand(shipRaw.logFile ?? join8(tmpdir(), "crew.log"), base),
2531
+ logFile: expand(shipRaw.logFile ?? join9(tmpdir(), "crew.log"), base),
2508
2532
  userAgent: shipRaw.userAgent ?? `Mozilla/5.0 CrewAgent/${crewVersion(crewHome)}`,
2509
2533
  maxConcurrentAgents,
2510
2534
  streamRetentionDays,
@@ -2526,7 +2550,7 @@ Create the first of those:
2526
2550
  function expandCommand(cmd, base) {
2527
2551
  return cmd.includes("/") || cmd.startsWith("~") ? expand(cmd, base) : cmd;
2528
2552
  }
2529
- var TOOLCHAIN_KEYS = /* @__PURE__ */ new Set(["harness", "bin", "launcher", "launcherBin", "model", "tiers", "args", "env", "maxThinkingTokens"]);
2553
+ var TOOLCHAIN_KEYS = /* @__PURE__ */ new Set(["harness", "bin", "launcher", "launcherBin", "model", "tiers", "args", "env", "maxThinkingTokens", "auth"]);
2530
2554
  function parsePair(raw, missing) {
2531
2555
  if (raw === void 0) return void 0;
2532
2556
  if (!Array.isArray(raw)) {
@@ -2583,9 +2607,20 @@ function parseToolchains(raw, base, missing, file) {
2583
2607
  if (!Number.isInteger(maxThinkingTokens) || maxThinkingTokens < 0) {
2584
2608
  missing.add(`${where}.maxThinkingTokens must be a non-negative integer (got "${entryRaw.maxThinkingTokens}")`);
2585
2609
  }
2610
+ let auth;
2611
+ if (entryRaw.auth !== void 0) {
2612
+ if (entryRaw.auth !== "subscription" && entryRaw.auth !== "inherit") {
2613
+ missing.add(`${where}.auth must be subscription|inherit (got "${entryRaw.auth}")`);
2614
+ } else if (harness !== "claude") {
2615
+ missing.add(`${where}.auth is only supported on the claude harness`);
2616
+ } else {
2617
+ auth = entryRaw.auth;
2618
+ }
2619
+ }
2586
2620
  out.push({
2587
2621
  name,
2588
2622
  harness,
2623
+ ...auth ? { auth } : {},
2589
2624
  bin: expandCommand(String(entryRaw.bin ?? adapter.defaultBin), base),
2590
2625
  ...entryRaw.launcher ? { launcher: expandCommand(String(entryRaw.launcher), base) } : {},
2591
2626
  ...entryRaw.launcher && entryRaw.launcherBin ? { launcherBin: expandCommand(String(entryRaw.launcherBin), base) } : {},
@@ -2645,7 +2680,7 @@ function parseOneRoute(c, where, file, base, stateDir, raw, missing, crewHome, t
2645
2680
  const hasRepos = Object.keys(repoDirs).length > 0;
2646
2681
  const reposBasePath = expand(String(c?.reposBasePath ?? raw.ship?.reposBasePath ?? DEFAULT_REPOS_BASE_PATH), base);
2647
2682
  const dirIsCloneBase = !c?.dir && !hasRepos && c?.repos && typeof c.repos === "object" && !Array.isArray(c.repos);
2648
- const dir = c?.dir ? expand(String(c.dir), base) : hasRepos ? Object.values(repoDirs)[0] : dirIsCloneBase ? join8(reposBasePath, splitRoute(route)[0]) : missing.req(void 0, `${where}.dir (or ${where}.repos)`) ?? "";
2683
+ const dir = c?.dir ? expand(String(c.dir), base) : hasRepos ? Object.values(repoDirs)[0] : dirIsCloneBase ? join9(reposBasePath, splitRoute(route)[0]) : missing.req(void 0, `${where}.dir (or ${where}.repos)`) ?? "";
2649
2684
  const weight = c?.weight === void 0 ? void 0 : Number(c.weight);
2650
2685
  if (weight !== void 0 && !(Number.isFinite(weight) && weight > 0)) {
2651
2686
  missing.add(`${where}.weight must be a positive number (got "${c?.weight}")`);
@@ -2729,7 +2764,7 @@ function repoIdForName(r, name) {
2729
2764
  function resolveApiKey(r) {
2730
2765
  if (r.apiKey) return r.apiKey;
2731
2766
  if (r.apiKeyFile && r.apiKeyVar && existsSync7(r.apiKeyFile)) {
2732
- for (const line of readFileSync7(r.apiKeyFile, "utf8").split("\n")) {
2767
+ for (const line of readFileSync8(r.apiKeyFile, "utf8").split("\n")) {
2733
2768
  const m = line.match(new RegExp(`^${r.apiKeyVar}=(.*)$`));
2734
2769
  if (m) return (m[1] ?? "").trim().replace(/^["']|["']$/g, "");
2735
2770
  }
@@ -2744,7 +2779,7 @@ function credentialSource(r) {
2744
2779
  if (r.apiKey) return KEYCHAIN_ELIGIBLE_ROUTES.has(r) ? "keychain" : "inline";
2745
2780
  if (r.apiKeyFile && r.apiKeyVar && existsSync7(r.apiKeyFile)) {
2746
2781
  const re = new RegExp(`^${r.apiKeyVar}=`);
2747
- if (readFileSync7(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
2782
+ if (readFileSync8(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
2748
2783
  }
2749
2784
  return process.env.CREW_API_KEY?.trim() ? "CREW_API_KEY" : "none";
2750
2785
  }
@@ -2800,7 +2835,7 @@ function configuredMembers(rt) {
2800
2835
  return out;
2801
2836
  }
2802
2837
  function defaultRepoDir(basePath, workspace, repoName) {
2803
- return join8(basePath, workspace, repoName);
2838
+ return join9(basePath, workspace, repoName);
2804
2839
  }
2805
2840
  function reposOf(r) {
2806
2841
  const named = Object.entries(r.repos);
@@ -2833,7 +2868,7 @@ function passengerRepoTargets(r) {
2833
2868
  return reposOf(r).filter((t) => r.repoOverrides[t.name]?.hostPassengers !== false);
2834
2869
  }
2835
2870
  function passengerCheckoutDir(basePath, workspace, repoName) {
2836
- return join8(basePath, workspace, ".passenger-checkouts", repoName);
2871
+ return join9(basePath, workspace, ".passenger-checkouts", repoName);
2837
2872
  }
2838
2873
  function passengerCheckoutTargets(r) {
2839
2874
  if (!r.hostPassengers) return [];
@@ -3181,8 +3216,8 @@ function renderEnvironment(i) {
3181
3216
  }
3182
3217
 
3183
3218
  // src/agent-log.ts
3184
- import { existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync8, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
3185
- import { join as join9 } from "path";
3219
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync9, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
3220
+ import { join as join10 } from "path";
3186
3221
 
3187
3222
  // src/pending-backoff.ts
3188
3223
  var sleepBackoff = (attempts) => Math.min(6e4 * 2 ** Math.max(0, attempts - 1), 30 * 6e4);
@@ -3249,20 +3284,20 @@ async function reportAgentRun(target, run2) {
3249
3284
  }
3250
3285
  return { agentLogId: entry.id };
3251
3286
  }
3252
- var queueDir = (stateDir) => join9(stateDir, "pending-agent-logs");
3287
+ var queueDir = (stateDir) => join10(stateDir, "pending-agent-logs");
3253
3288
  function queueAgentRun(stateDir, name, q) {
3254
3289
  mkdirSync4(queueDir(stateDir), { recursive: true });
3255
- writeFileSync5(join9(queueDir(stateDir), `${name}.json`), JSON.stringify(q));
3290
+ writeFileSync5(join10(queueDir(stateDir), `${name}.json`), JSON.stringify(q));
3256
3291
  }
3257
3292
  async function replayQueuedAgentRuns(stateDir, route, keyFor, log, now = /* @__PURE__ */ new Date()) {
3258
3293
  const dir = queueDir(stateDir);
3259
3294
  if (!existsSync8(dir)) return 0;
3260
3295
  let sent = 0;
3261
3296
  for (const f of readdirSync2(dir).filter((n) => n.endsWith(".json")).sort()) {
3262
- const path = join9(dir, f);
3297
+ const path = join10(dir, f);
3263
3298
  let q;
3264
3299
  try {
3265
- q = JSON.parse(readFileSync8(path, "utf8"));
3300
+ q = JSON.parse(readFileSync9(path, "utf8"));
3266
3301
  } catch {
3267
3302
  continue;
3268
3303
  }
@@ -3462,8 +3497,8 @@ var HandoffTranscriptTracker = class {
3462
3497
  };
3463
3498
 
3464
3499
  // src/pending-handoff.ts
3465
- import { existsSync as existsSync15, mkdirSync as mkdirSync8, readdirSync as readdirSync5, readFileSync as readFileSync12, rmSync as rmSync8, writeFileSync as writeFileSync9 } from "fs";
3466
- import { join as join15 } from "path";
3500
+ import { existsSync as existsSync15, mkdirSync as mkdirSync8, readdirSync as readdirSync5, readFileSync as readFileSync13, rmSync as rmSync8, writeFileSync as writeFileSync9 } from "fs";
3501
+ import { join as join16 } from "path";
3467
3502
 
3468
3503
  // src/git.ts
3469
3504
  import { execFileSync as execFileSync2 } from "child_process";
@@ -3475,12 +3510,12 @@ import {
3475
3510
  writeSync,
3476
3511
  statSync as statSync2,
3477
3512
  unlinkSync,
3478
- readFileSync as readFileSync9,
3513
+ readFileSync as readFileSync10,
3479
3514
  writeFileSync as writeFileSync6,
3480
3515
  readdirSync as readdirSync3,
3481
3516
  rmSync as rmSync6
3482
3517
  } from "fs";
3483
- import { dirname as dirname4, basename as basename5, join as join10 } from "path";
3518
+ import { dirname as dirname4, basename as basename5, join as join11 } from "path";
3484
3519
  var GitError = class extends Error {
3485
3520
  };
3486
3521
  function streamText(x) {
@@ -3878,11 +3913,11 @@ function sweepStaleReleaseRoots(tmp, now = Date.now(), maxAgeMs = 60 * 60 * 1e3)
3878
3913
  }
3879
3914
  for (const name of names) {
3880
3915
  if (!name.startsWith("crew-release-")) continue;
3881
- const root = join10(tmp, name);
3916
+ const root = join11(tmp, name);
3882
3917
  try {
3883
3918
  const st = statSync2(root);
3884
3919
  if (!st.isDirectory() || now - st.mtimeMs < maxAgeMs) continue;
3885
- if (existsSync9(join10(root, "checkout"))) continue;
3920
+ if (existsSync9(join11(root, "checkout"))) continue;
3886
3921
  rmSync6(root, { recursive: true, force: true });
3887
3922
  removed.push(name);
3888
3923
  } catch {
@@ -3996,8 +4031,8 @@ function syncPassengerCheckout(dir, githubRemote, gitRemoteName, base, clone = {
3996
4031
  if (!existsSync9(`${dir}/.git`) && githubRemote) {
3997
4032
  const url = cloneUrlFor(githubRemote, clone);
3998
4033
  const marker = cloneFailureMarker(dir);
3999
- if (existsSync9(marker) && readFileSync9(marker, "utf8").split("\n")[0] === url) {
4000
- return { action: "clone-failed", detail: readFileSync9(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
4034
+ if (existsSync9(marker) && readFileSync10(marker, "utf8").split("\n")[0] === url) {
4035
+ return { action: "clone-failed", detail: readFileSync10(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
4001
4036
  }
4002
4037
  try {
4003
4038
  ensureRepoCheckout(dir, githubRemote, clone);
@@ -4239,7 +4274,7 @@ function describeComposition(c) {
4239
4274
 
4240
4275
  ${rows}
4241
4276
 
4242
- The gate runs on the base plus every verified branch above, not on the base alone: reproduce on that composition.`;
4277
+ The gate runs on the base plus every verified branch above, not on the base alone: reproduce on that composition. A fix that compiles only on the composition belongs on one of those branches (the one owning the failing file, after merging the others into it), not on a new branch cut from the base: a separate fix branch conflicts with the branches it contains when the release squash-merges them all. Re-queue the touched ticket to QA and close this alert with a note.`;
4243
4278
  }
4244
4279
 
4245
4280
  // src/shell.ts
@@ -4424,9 +4459,9 @@ function recoveredFailureHeadlines(o, route) {
4424
4459
  }
4425
4460
 
4426
4461
  // src/install.ts
4427
- import { dirname as dirname5, join as join11, extname } from "path";
4428
- import { homedir as homedir6 } from "os";
4429
- import { accessSync, constants, existsSync as existsSync10, mkdirSync as mkdirSync6, readdirSync as readdirSync4, readFileSync as readFileSync10, statSync as statSync3, writeFileSync as writeFileSync7, unlinkSync as unlinkSync2, realpathSync } from "fs";
4462
+ import { dirname as dirname5, join as join12, extname } from "path";
4463
+ import { homedir as homedir7 } from "os";
4464
+ import { accessSync, constants, existsSync as existsSync10, mkdirSync as mkdirSync6, readdirSync as readdirSync4, readFileSync as readFileSync11, statSync as statSync3, writeFileSync as writeFileSync7, unlinkSync as unlinkSync2, realpathSync } from "fs";
4430
4465
  import { spawnSync as spawnSync4 } from "child_process";
4431
4466
  import { createHash as createHash2 } from "crypto";
4432
4467
 
@@ -4438,7 +4473,7 @@ function isCompiledBinary(moduleUrl) {
4438
4473
  // src/install.ts
4439
4474
  function crewInvocation(crewHome) {
4440
4475
  if (isCompiledBinary(import.meta.url)) return [process.execPath];
4441
- return [process.execPath, join11(crewHome, "bin", "crew")];
4476
+ return [process.execPath, join12(crewHome, "bin", "crew")];
4442
4477
  }
4443
4478
  var INTERVAL_SECONDS = 120;
4444
4479
  function labelFor(crewHome, job) {
@@ -4476,7 +4511,7 @@ function pathFor(ship) {
4476
4511
  return parts.join(":");
4477
4512
  }
4478
4513
  function findOnPath(bin, searchPath) {
4479
- const candidates = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) => join11(dir, bin));
4514
+ const candidates = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) => join12(dir, bin));
4480
4515
  for (const candidate of candidates) {
4481
4516
  try {
4482
4517
  accessSync(candidate, constants.X_OK);
@@ -4486,7 +4521,7 @@ function findOnPath(bin, searchPath) {
4486
4521
  }
4487
4522
  return void 0;
4488
4523
  }
4489
- var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin", join11(homedir6(), ".docker", "bin")].join(":");
4524
+ var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin", join12(homedir7(), ".docker", "bin")].join(":");
4490
4525
  function dockerPathProblem(ship, shellPath = process.env.PATH ?? "") {
4491
4526
  const schedulerPath = pathFor(ship);
4492
4527
  if (findOnPath("docker", schedulerPath)) return void 0;
@@ -4498,7 +4533,7 @@ function xmlEscape(s) {
4498
4533
  }
4499
4534
  function planLaunchd(ship, crewHome, job) {
4500
4535
  const label = labelFor(crewHome, job);
4501
- const unitPath = join11(homedir6(), "Library", "LaunchAgents", `${label}.plist`);
4536
+ const unitPath = join12(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
4502
4537
  const crewLog = ship.logFile;
4503
4538
  const schedulerLog = schedulerLogFor(crewLog, job);
4504
4539
  const persistent = job === "run";
@@ -4552,8 +4587,8 @@ ${scheduleKeys}
4552
4587
  }
4553
4588
  function planSystemd(ship, crewHome, job) {
4554
4589
  const label = labelFor(crewHome, job);
4555
- const unitDir = join11(homedir6(), ".config", "systemd", "user");
4556
- const servicePath = join11(unitDir, `${label}.service`);
4590
+ const unitDir = join12(homedir7(), ".config", "systemd", "user");
4591
+ const servicePath = join12(unitDir, `${label}.service`);
4557
4592
  const crewLog = ship.logFile;
4558
4593
  const schedulerLog = schedulerLogFor(crewLog, job);
4559
4594
  const description = job === "release" ? "Tablation crew \u2014 release" : job === "passengers" ? "Tablation crew \u2014 Host Passengers sync" : "Tablation crew \u2014 persistent supervisor loop";
@@ -4593,7 +4628,7 @@ WantedBy=default.target
4593
4628
  schedulerLog
4594
4629
  };
4595
4630
  }
4596
- const timerPath = join11(unitDir, `${label}.timer`);
4631
+ const timerPath = join12(unitDir, `${label}.timer`);
4597
4632
  const service = `# Written by \`crew install\` \u2014 re-run it rather than hand-editing this file.
4598
4633
  [Unit]
4599
4634
  Description=${description}
@@ -4667,7 +4702,7 @@ function planInstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
4667
4702
  function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
4668
4703
  const label = labelFor(crewHome, job);
4669
4704
  if (hostShip === "macos") {
4670
- const unitPath = join11(homedir6(), "Library", "LaunchAgents", `${label}.plist`);
4705
+ const unitPath = join12(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
4671
4706
  return {
4672
4707
  mechanism: "launchd",
4673
4708
  unitPaths: [unitPath],
@@ -4677,18 +4712,18 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
4677
4712
  };
4678
4713
  }
4679
4714
  if (hostShip === "linux" && hasSystemd) {
4680
- const unitDir = join11(homedir6(), ".config", "systemd", "user");
4715
+ const unitDir = join12(homedir7(), ".config", "systemd", "user");
4681
4716
  if (job === "run") {
4682
4717
  return {
4683
4718
  mechanism: "systemd",
4684
- unitPaths: [join11(unitDir, `${label}.service`)],
4719
+ unitPaths: [join12(unitDir, `${label}.service`)],
4685
4720
  crontabLine: null,
4686
4721
  unloadCommands: [["systemctl", "--user", "disable", "--now", `${label}.service`]]
4687
4722
  };
4688
4723
  }
4689
4724
  return {
4690
4725
  mechanism: "systemd",
4691
- unitPaths: [join11(unitDir, `${label}.service`), join11(unitDir, `${label}.timer`)],
4726
+ unitPaths: [join12(unitDir, `${label}.service`), join12(unitDir, `${label}.timer`)],
4692
4727
  crontabLine: null,
4693
4728
  unloadCommands: [["systemctl", "--user", "disable", "--now", `${label}.timer`]]
4694
4729
  };
@@ -4706,7 +4741,7 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
4706
4741
  function planDaemonControl(crewHome, hostShip, hasSystemd) {
4707
4742
  const label = labelFor(crewHome, "run");
4708
4743
  if (hostShip === "macos") {
4709
- const unitPath = join11(homedir6(), "Library", "LaunchAgents", `${label}.plist`);
4744
+ const unitPath = join12(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
4710
4745
  return {
4711
4746
  mechanism: "launchd",
4712
4747
  startCommand: ["launchctl", "load", unitPath],
@@ -4820,7 +4855,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
4820
4855
  }
4821
4856
  var CREW_LAUNCHD_PREFIX = "com.tablation.crew";
4822
4857
  function launchAgentsDir() {
4823
- return join11(homedir6(), "Library", "LaunchAgents");
4858
+ return join12(homedir7(), "Library", "LaunchAgents");
4824
4859
  }
4825
4860
  function xmlUnescape(s) {
4826
4861
  return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
@@ -4839,10 +4874,10 @@ function loadedCrewLabels() {
4839
4874
  function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
4840
4875
  const files = existsSync10(dir) ? readdirSync4(dir) : [];
4841
4876
  const units = files.filter((f) => f.startsWith(CREW_LAUNCHD_PREFIX) && f.endsWith(".plist")).sort().map((f) => {
4842
- const plistPath = join11(dir, f);
4877
+ const plistPath = join12(dir, f);
4843
4878
  let program = null;
4844
4879
  try {
4845
- program = programFromPlist(readFileSync10(plistPath, "utf8"));
4880
+ program = programFromPlist(readFileSync11(plistPath, "utf8"));
4846
4881
  } catch {
4847
4882
  }
4848
4883
  return {
@@ -4855,7 +4890,7 @@ function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
4855
4890
  const seen = new Set(units.map((u) => u.label));
4856
4891
  for (const label of [...loaded].sort()) {
4857
4892
  if (!label.startsWith(CREW_LAUNCHD_PREFIX) || seen.has(label)) continue;
4858
- units.push({ label, plistPath: join11(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
4893
+ units.push({ label, plistPath: join12(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
4859
4894
  }
4860
4895
  return units;
4861
4896
  }
@@ -5201,8 +5236,8 @@ var SHELL_WORDS = /* @__PURE__ */ new Set([
5201
5236
  ]);
5202
5237
 
5203
5238
  // src/state.ts
5204
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, existsSync as existsSync12, mkdirSync as mkdirSync7, rmSync as rmSync7, statSync as statSync4 } from "fs";
5205
- import { join as join12 } from "path";
5239
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, existsSync as existsSync12, mkdirSync as mkdirSync7, rmSync as rmSync7, statSync as statSync4 } from "fs";
5240
+ import { join as join13 } from "path";
5206
5241
  var EPOCH = "1970-01-01T00:00:00.000Z";
5207
5242
  var State = class _State {
5208
5243
  dir;
@@ -5211,7 +5246,7 @@ var State = class _State {
5211
5246
  mkdirSync7(dir, { recursive: true });
5212
5247
  }
5213
5248
  path(...parts) {
5214
- return join12(this.dir, ...parts);
5249
+ return join13(this.dir, ...parts);
5215
5250
  }
5216
5251
  /**
5217
5252
  * The timestamp every "new since last poll" check is measured against.
@@ -5222,7 +5257,7 @@ var State = class _State {
5222
5257
  */
5223
5258
  watermark() {
5224
5259
  try {
5225
- const v = readFileSync11(this.path(".poll-watermark"), "utf8").trim();
5260
+ const v = readFileSync12(this.path(".poll-watermark"), "utf8").trim();
5226
5261
  return v || EPOCH;
5227
5262
  } catch {
5228
5263
  return EPOCH;
@@ -5308,7 +5343,7 @@ var State = class _State {
5308
5343
  const name = _State.safe(rawName);
5309
5344
  const file = this.path(`.${name}.lock`);
5310
5345
  try {
5311
- const existing = Number.parseInt(readFileSync11(file, "utf8").trim(), 10);
5346
+ const existing = Number.parseInt(readFileSync12(file, "utf8").trim(), 10);
5312
5347
  if (Number.isFinite(existing) && existing > 0) {
5313
5348
  try {
5314
5349
  process.kill(existing, 0);
@@ -5402,7 +5437,7 @@ var State = class _State {
5402
5437
  const heldReported = `.release-held-${suffix}`;
5403
5438
  const read = (f) => {
5404
5439
  try {
5405
- return readFileSync11(this.path(f), "utf8").trim() || null;
5440
+ return readFileSync12(this.path(f), "utf8").trim() || null;
5406
5441
  } catch {
5407
5442
  return null;
5408
5443
  }
@@ -5452,7 +5487,7 @@ var State = class _State {
5452
5487
  /** Whether the board was already told about this head's red gate. */
5453
5488
  heldReported: () => {
5454
5489
  try {
5455
- return readFileSync11(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
5490
+ return readFileSync12(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
5456
5491
  } catch {
5457
5492
  return null;
5458
5493
  }
@@ -5489,7 +5524,7 @@ var State = class _State {
5489
5524
  const ticketStreakFile = `.fairness-ticket-streak-${suffix}`;
5490
5525
  const read = (f) => {
5491
5526
  try {
5492
- return readFileSync11(this.path(f), "utf8").trim() || null;
5527
+ return readFileSync12(this.path(f), "utf8").trim() || null;
5493
5528
  } catch {
5494
5529
  return null;
5495
5530
  }
@@ -5554,7 +5589,7 @@ var State = class _State {
5554
5589
  return {
5555
5590
  previous: () => {
5556
5591
  try {
5557
- const parsed = JSON.parse(readFileSync11(this.path(file), "utf8"));
5592
+ const parsed = JSON.parse(readFileSync12(this.path(file), "utf8"));
5558
5593
  return parsed && typeof parsed === "object" ? parsed : {};
5559
5594
  } catch {
5560
5595
  return {};
@@ -5575,7 +5610,7 @@ var State = class _State {
5575
5610
  return {
5576
5611
  previous: () => {
5577
5612
  try {
5578
- const parsed = JSON.parse(readFileSync11(this.path(file), "utf8"));
5613
+ const parsed = JSON.parse(readFileSync12(this.path(file), "utf8"));
5579
5614
  return Array.isArray(parsed) ? parsed : [];
5580
5615
  } catch {
5581
5616
  return [];
@@ -5608,7 +5643,7 @@ var State = class _State {
5608
5643
  }
5609
5644
  readInt(file) {
5610
5645
  try {
5611
- return Number.parseInt(readFileSync11(this.path(file), "utf8").trim(), 10) || 0;
5646
+ return Number.parseInt(readFileSync12(this.path(file), "utf8").trim(), 10) || 0;
5612
5647
  } catch {
5613
5648
  return 0;
5614
5649
  }
@@ -6611,12 +6646,12 @@ function holdWarnings(tickets, ships, contract, humanHolds, now = Date.now()) {
6611
6646
 
6612
6647
  // src/run-worktree.ts
6613
6648
  import { existsSync as existsSync14 } from "fs";
6614
- import { dirname as dirname6, join as join14 } from "path";
6649
+ import { dirname as dirname6, join as join15 } from "path";
6615
6650
 
6616
6651
  // src/ports.ts
6617
6652
  import { execFileSync as execFileSync3 } from "child_process";
6618
6653
  import { existsSync as existsSync13 } from "fs";
6619
- import { join as join13 } from "path";
6654
+ import { join as join14 } from "path";
6620
6655
  var run = (cmd, args) => {
6621
6656
  try {
6622
6657
  return execFileSync3(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
@@ -6661,7 +6696,7 @@ function ticketForPort(port, s = DEFAULT_PORTS) {
6661
6696
  }
6662
6697
  function worktreeExists(parent, prefix, n) {
6663
6698
  for (const name of [`${prefix}${n}`, `${prefix}${String(n).padStart(3, "0")}`]) {
6664
- if (existsSync13(join13(parent, name, ".git"))) return true;
6699
+ if (existsSync13(join14(parent, name, ".git"))) return true;
6665
6700
  }
6666
6701
  return false;
6667
6702
  }
@@ -6753,7 +6788,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
6753
6788
  if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
6754
6789
  return { action: "recut", path: existing.path, branch, base: baseRef };
6755
6790
  }
6756
- const path = join14(dirname6(dir), worktreeForTicket(dir, cfg, t, role));
6791
+ const path = join15(dirname6(dir), worktreeForTicket(dir, cfg, t, role));
6757
6792
  if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
6758
6793
  return { action: "cut-remote", path, branch: located.branch };
6759
6794
  }
@@ -7116,12 +7151,12 @@ async function guardRunStrays(route, ship, tracker, role, workingId, since2, log
7116
7151
 
7117
7152
  // src/pending-handoff.ts
7118
7153
  var PENDING_MAX_AGE_MS = 48 * 36e5;
7119
- var pendingDirFor = (stateDir) => join15(stateDir, "pending-handoffs");
7120
- var stateDirOfStream = (streamPath) => join15(streamPath, "..", "..");
7154
+ var pendingDirFor = (stateDir) => join16(stateDir, "pending-handoffs");
7155
+ var stateDirOfStream = (streamPath) => join16(streamPath, "..", "..");
7121
7156
  function writePendingHandoff(stateDir, rec) {
7122
7157
  const dir = pendingDirFor(stateDir);
7123
7158
  mkdirSync8(dir, { recursive: true });
7124
- writeFileSync9(join15(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
7159
+ writeFileSync9(join16(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
7125
7160
  }
7126
7161
  function listPendingHandoffs(stateDir, route) {
7127
7162
  const dir = pendingDirFor(stateDir);
@@ -7129,7 +7164,7 @@ function listPendingHandoffs(stateDir, route) {
7129
7164
  const out = [];
7130
7165
  for (const f of readdirSync5(dir).filter((n) => n.endsWith(".json")).sort()) {
7131
7166
  try {
7132
- const rec = JSON.parse(readFileSync12(join15(dir, f), "utf8"));
7167
+ const rec = JSON.parse(readFileSync13(join16(dir, f), "utf8"));
7133
7168
  if (rec.version === 1 && rec.ticketRecordId && (!route || rec.route === route)) out.push(rec);
7134
7169
  } catch {
7135
7170
  }
@@ -7137,7 +7172,7 @@ function listPendingHandoffs(stateDir, route) {
7137
7172
  return out;
7138
7173
  }
7139
7174
  function removePendingHandoff(stateDir, ticketRecordId) {
7140
- rmSync8(join15(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
7175
+ rmSync8(join16(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
7141
7176
  }
7142
7177
  function pendingHandoffFor(stateDir, ticketRecordId) {
7143
7178
  return listPendingHandoffs(stateDir).find((r) => r.ticketRecordId === ticketRecordId);
@@ -7247,8 +7282,8 @@ function expandSkillMarkers(text, lookup, opts = {}) {
7247
7282
  }
7248
7283
 
7249
7284
  // src/skills.ts
7250
- import { readFileSync as readFileSync14, readdirSync as readdirSync6, existsSync as existsSync17 } from "fs";
7251
- import { join as join17 } from "path";
7285
+ import { readFileSync as readFileSync15, readdirSync as readdirSync6, existsSync as existsSync17 } from "fs";
7286
+ import { join as join18 } from "path";
7252
7287
  import { parse as parseYaml } from "yaml";
7253
7288
  import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError3, TablationApiError as TablationApiError2 } from "@tablation/client";
7254
7289
 
@@ -7264,8 +7299,8 @@ ${additions}`;
7264
7299
  }
7265
7300
 
7266
7301
  // src/agents.ts
7267
- import { readFileSync as readFileSync13, existsSync as existsSync16 } from "fs";
7268
- import { join as join16 } from "path";
7302
+ import { readFileSync as readFileSync14, existsSync as existsSync16 } from "fs";
7303
+ import { join as join17 } from "path";
7269
7304
  import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError2, TablationApiError } from "@tablation/client";
7270
7305
  var PERSONA_NAME = {
7271
7306
  dev: "Developer",
@@ -7279,12 +7314,12 @@ var CREW_AGENT_COLUMN = "agent_id";
7279
7314
  var AgentsSyncError = class extends Error {
7280
7315
  };
7281
7316
  function personaDefaultPrompt(promptsDir, role) {
7282
- const briefPath = join16(promptsDir, "personas", `lane-${role}.md`);
7317
+ const briefPath = join17(promptsDir, "personas", `lane-${role}.md`);
7283
7318
  if (!existsSync16(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
7284
- if (SELF_CONTAINED_ROLES.has(role)) return readFileSync13(briefPath, "utf8");
7285
- const commonPath = join16(promptsDir, "personas", "common.md");
7286
- if (!existsSync16(commonPath)) return readFileSync13(briefPath, "utf8");
7287
- return `${readFileSync13(commonPath, "utf8")}${readFileSync13(briefPath, "utf8")}`;
7319
+ if (SELF_CONTAINED_ROLES.has(role)) return readFileSync14(briefPath, "utf8");
7320
+ const commonPath = join17(promptsDir, "personas", "common.md");
7321
+ if (!existsSync16(commonPath)) return readFileSync14(briefPath, "utf8");
7322
+ return `${readFileSync14(commonPath, "utf8")}${readFileSync14(briefPath, "utf8")}`;
7288
7323
  }
7289
7324
  async function resolveAgentId(route, role, opts) {
7290
7325
  const cached = route.resolved?.agentPersonas?.[role]?.agentId;
@@ -7731,7 +7766,7 @@ function describeCrewLink(o) {
7731
7766
  var SkillSyncError = class extends Error {
7732
7767
  };
7733
7768
  function parseSkillFile(path) {
7734
- const text = readFileSync14(path, "utf8");
7769
+ const text = readFileSync15(path, "utf8");
7735
7770
  const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text);
7736
7771
  if (!match) throw new SkillSyncError(`${path}: missing YAML frontmatter (expected a leading --- block)`);
7737
7772
  const frontmatter = match[1];
@@ -7744,9 +7779,9 @@ function parseSkillFile(path) {
7744
7779
  return { name: meta.name, description: meta.description, prompt: body, file: path };
7745
7780
  }
7746
7781
  function listLocalSkills(promptsDir) {
7747
- const dir = join17(promptsDir, "skills");
7782
+ const dir = join18(promptsDir, "skills");
7748
7783
  if (!existsSync17(dir)) return [];
7749
- return readdirSync6(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join17(dir, f)));
7784
+ return readdirSync6(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join18(dir, f)));
7750
7785
  }
7751
7786
  async function syncSkills(route, opts) {
7752
7787
  if (!route.resolved) {
@@ -7949,23 +7984,23 @@ var AgentError = class extends Error {
7949
7984
  var AgentSpawnError = class extends AgentError {
7950
7985
  };
7951
7986
  function planAgentRun(o) {
7952
- const briefPath = join18(o.route.promptsDir, "personas", `lane-${o.role}.md`);
7987
+ const briefPath = join19(o.route.promptsDir, "personas", `lane-${o.role}.md`);
7953
7988
  if (!existsSync18(briefPath)) {
7954
7989
  throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
7955
7990
  }
7956
- const commonPath = join18(o.route.promptsDir, "personas", "common.md");
7991
+ const commonPath = join19(o.route.promptsDir, "personas", "common.md");
7957
7992
  const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync18(commonPath);
7958
7993
  const now = o.now ?? (() => Date.now());
7959
7994
  const routeLabel = routeSlug(o.route.route);
7960
- const digestPath2 = join18(o.stateDir, `digest-${routeLabel}-${o.role}.md`);
7995
+ const digestPath2 = join19(o.stateDir, `digest-${routeLabel}-${o.role}.md`);
7961
7996
  let digest;
7962
7997
  let ageSeconds;
7963
7998
  if (existsSync18(digestPath2)) {
7964
7999
  ageSeconds = Math.floor((now() - statSync5(digestPath2).mtimeMs) / 1e3);
7965
- if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync15(digestPath2, "utf8");
8000
+ if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync16(digestPath2, "utf8");
7966
8001
  }
7967
- const common = usesCommon ? readFileSync15(commonPath, "utf8") : "";
7968
- const brief = readFileSync15(briefPath, "utf8");
8002
+ const common = usesCommon ? readFileSync16(commonPath, "utf8") : "";
8003
+ const brief = readFileSync16(briefPath, "utf8");
7969
8004
  const effectiveCommon = o.divergedPrompt ? "" : common;
7970
8005
  const effectiveBrief = appendAdditions(o.divergedPrompt ?? brief, o.personaAdditions);
7971
8006
  const localSkills = new Map(listLocalSkills(o.route.promptsDir).map((s) => [s.name, s.prompt]));
@@ -7987,7 +8022,7 @@ function planAgentRun(o) {
7987
8022
  digest
7988
8023
  });
7989
8024
  const promptSha = createHash3("sha256").update(expanded.text).digest("hex");
7990
- const streamsDir = join18(o.stateDir, "streams");
8025
+ const streamsDir = join19(o.stateDir, "streams");
7991
8026
  const base = `${routeLabel}-${o.role}-${o.cycle}`;
7992
8027
  const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
7993
8028
  const { toolchain, model } = seat;
@@ -8043,8 +8078,8 @@ function planAgentRun(o) {
8043
8078
  digestAttached: digest !== void 0,
8044
8079
  digestAgeSeconds: ageSeconds,
8045
8080
  model,
8046
- streamPath: join18(streamsDir, `${base}.jsonl`),
8047
- eventsPath: join18(streamsDir, `${base}.events.jsonl`),
8081
+ streamPath: join19(streamsDir, `${base}.jsonl`),
8082
+ eventsPath: join19(streamsDir, `${base}.events.jsonl`),
8048
8083
  ticket: o.ticket,
8049
8084
  ticketRecordId: o.ticketRecordId ?? null,
8050
8085
  agentLog,
@@ -8073,7 +8108,7 @@ function describePlan(p) {
8073
8108
  var pidPathFor = (streamPath) => `${streamPath}.pid`;
8074
8109
  function readRunSidecar(streamPath) {
8075
8110
  try {
8076
- const sc = JSON.parse(readFileSync15(pidPathFor(streamPath), "utf8"));
8111
+ const sc = JSON.parse(readFileSync16(pidPathFor(streamPath), "utf8"));
8077
8112
  return typeof sc.pid === "number" && typeof sc.spawnedAt === "string" && typeof sc.role === "string" ? sc : null;
8078
8113
  } catch {
8079
8114
  return null;
@@ -8657,7 +8692,7 @@ async function runSelftest(i, deps = realDeps) {
8657
8692
  ...adapter.runEnv(spec),
8658
8693
  [KEY_VAR]: i.apiKey
8659
8694
  };
8660
- const cwd = mkdtempSync(join19(tmpdir2(), "crew-selftest-"));
8695
+ const cwd = mkdtempSync(join20(tmpdir2(), "crew-selftest-"));
8661
8696
  try {
8662
8697
  const run2 = await deps.run({
8663
8698
  bin: invocation.bin,
@@ -8672,7 +8707,8 @@ async function runSelftest(i, deps = realDeps) {
8672
8707
  const answer = [seen.finalText ?? "", ...seen.texts].join("\n");
8673
8708
  const usage2 = { ...seen.tokens !== void 0 ? { tokens: seen.tokens } : {}, ...seen.costUsd !== void 0 ? { costUsd: seen.costUsd } : {} };
8674
8709
  if (!run2.spawnError && !run2.timedOut && answer.toLowerCase().includes(i.workspaceName.toLowerCase())) {
8675
- return finish({ ok: true, workspace: i.workspaceName, detail: `read workspace "${i.workspaceName}"`, ...usage2 });
8710
+ const auth = spec.harness === "claude" ? ` on ${describeClaudeAuth(spec)}` : "";
8711
+ return finish({ ok: true, workspace: i.workspaceName, detail: `read workspace "${i.workspaceName}"${auth}`, ...usage2 });
8676
8712
  }
8677
8713
  const { failure, detail } = classify(spec, run2, seen);
8678
8714
  return finish({ ok: false, failure, detail, ...usage2 });
@@ -8689,11 +8725,11 @@ function formatResult(r) {
8689
8725
  const secs = `${(r.ms / 1e3).toFixed(1)}s`;
8690
8726
  return r.ok ? `${r.toolchain}: PASS ${r.detail} (${secs}${tok})` : `${r.toolchain}: FAIL ${r.failure} \u2014 ${r.detail} (${secs})`;
8691
8727
  }
8692
- var fileFor2 = (stateDir) => join19(stateDir, "selftest.json");
8728
+ var fileFor2 = (stateDir) => join20(stateDir, "selftest.json");
8693
8729
  function readSelftest(stateDir) {
8694
8730
  try {
8695
8731
  const p = fileFor2(stateDir);
8696
- return existsSync19(p) ? JSON.parse(readFileSync16(p, "utf8")) : {};
8732
+ return existsSync19(p) ? JSON.parse(readFileSync17(p, "utf8")) : {};
8697
8733
  } catch {
8698
8734
  return {};
8699
8735
  }
@@ -8714,9 +8750,9 @@ function doctorSelftestLines(stored, toolchains) {
8714
8750
  }
8715
8751
 
8716
8752
  // src/cli.ts
8717
- import { resolve as resolve5, dirname as dirname20, join as join36, basename as basename11 } from "path";
8753
+ import { resolve as resolve5, dirname as dirname20, join as join37, basename as basename11 } from "path";
8718
8754
  import { fileURLToPath } from "url";
8719
- import { hostname as hostname2, homedir as homedir9 } from "os";
8755
+ import { hostname as hostname2, homedir as homedir10 } from "os";
8720
8756
  import { spawnSync as spawnSync6 } from "child_process";
8721
8757
  import { parse as parseYaml2 } from "yaml";
8722
8758
 
@@ -8894,14 +8930,14 @@ function rosterMarkdown(roster, meId) {
8894
8930
  }
8895
8931
 
8896
8932
  // src/connect-resume.ts
8897
- import { mkdirSync as mkdirSync11, readFileSync as readFileSync17, rmSync as rmSync10, writeFileSync as writeFileSync12 } from "fs";
8898
- import { dirname as dirname8, join as join20 } from "path";
8933
+ import { mkdirSync as mkdirSync11, readFileSync as readFileSync18, rmSync as rmSync10, writeFileSync as writeFileSync12 } from "fs";
8934
+ import { dirname as dirname8, join as join21 } from "path";
8899
8935
  function lastSessionFile(stateDir) {
8900
- return join20(stateDir, "connect-session.json");
8936
+ return join21(stateDir, "connect-session.json");
8901
8937
  }
8902
8938
  function readPointers(stateDir) {
8903
8939
  try {
8904
- const parsed = JSON.parse(readFileSync17(lastSessionFile(stateDir), "utf8"));
8940
+ const parsed = JSON.parse(readFileSync18(lastSessionFile(stateDir), "utf8"));
8905
8941
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
8906
8942
  const out = {};
8907
8943
  for (const [host, v] of Object.entries(parsed)) {
@@ -8972,15 +9008,15 @@ async function signOutSessions(o) {
8972
9008
  }
8973
9009
 
8974
9010
  // src/reset.ts
8975
- import { existsSync as existsSync20, mkdirSync as mkdirSync12, readFileSync as readFileSync18, readdirSync as readdirSync7, rmSync as rmSync11, statSync as statSync6, writeFileSync as writeFileSync13 } from "fs";
8976
- import { dirname as dirname9, join as join21 } from "path";
9011
+ import { existsSync as existsSync20, mkdirSync as mkdirSync12, readFileSync as readFileSync19, readdirSync as readdirSync7, rmSync as rmSync11, statSync as statSync6, writeFileSync as writeFileSync13 } from "fs";
9012
+ import { dirname as dirname9, join as join22 } from "path";
8977
9013
  import { hostFromUrl as hostFromUrl2 } from "@tablation/client";
8978
9014
  function clonedCheckoutsFile(stateDir) {
8979
- return join21(stateDir, "cloned-checkouts.json");
9015
+ return join22(stateDir, "cloned-checkouts.json");
8980
9016
  }
8981
9017
  function readClonedCheckouts(stateDir) {
8982
9018
  try {
8983
- const parsed = JSON.parse(readFileSync18(clonedCheckoutsFile(stateDir), "utf8"));
9019
+ const parsed = JSON.parse(readFileSync19(clonedCheckoutsFile(stateDir), "utf8"));
8984
9020
  return Array.isArray(parsed) ? parsed.filter((d) => typeof d === "string") : [];
8985
9021
  } catch {
8986
9022
  return [];
@@ -9065,10 +9101,10 @@ function removeCheckout(dir, dryRun, fx) {
9065
9101
  }
9066
9102
  function removeState(stateDir, dryRun) {
9067
9103
  const out = [];
9068
- const targets = [join21(stateDir, "resolved"), join21(stateDir, "keys"), clonedCheckoutsFile(stateDir), join21(stateDir, "connect-session.json"), join21(stateDir, "reload-requested"), join21(stateDir, "daemon.started")];
9104
+ const targets = [join22(stateDir, "resolved"), join22(stateDir, "keys"), clonedCheckoutsFile(stateDir), join22(stateDir, "connect-session.json"), join22(stateDir, "reload-requested"), join22(stateDir, "daemon.started")];
9069
9105
  try {
9070
9106
  for (const name of readdirSync7(stateDir)) {
9071
- if (name.startsWith(".") && statSync6(join21(stateDir, name)).isFile()) targets.push(join21(stateDir, name));
9107
+ if (name.startsWith(".") && statSync6(join22(stateDir, name)).isFile()) targets.push(join22(stateDir, name));
9072
9108
  }
9073
9109
  } catch {
9074
9110
  }
@@ -9854,7 +9890,7 @@ ${waiting.map((t) => `| ${displayKey2(t)} | ${t.status} | ${blockers(t, i)} |`).
9854
9890
 
9855
9891
  // src/poll.ts
9856
9892
  import { writeFileSync as writeFileSync14 } from "fs";
9857
- import { join as join22 } from "path";
9893
+ import { join as join23 } from "path";
9858
9894
 
9859
9895
  // src/stalled.ts
9860
9896
  var STALLED_AFTER_CYCLES = 30;
@@ -10145,7 +10181,7 @@ async function decideCycle(o) {
10145
10181
  }
10146
10182
  return decision;
10147
10183
  }
10148
- var digestPath = (stateDir, route, role) => join22(stateDir, `digest-${route}-${role}.md`);
10184
+ var digestPath = (stateDir, route, role) => join23(stateDir, `digest-${route}-${role}.md`);
10149
10185
  function writeDigest(o, d, role, stateDir) {
10150
10186
  const me = o.route.resolved?.seats[role];
10151
10187
  if (!me) return false;
@@ -10331,7 +10367,8 @@ var lastActedHandBack = (ticket, events) => {
10331
10367
  return mine.filter((e) => e.event_type === "conflict_handed_back" && pushes.some((p) => p > stampOf(e))).sort((a, b) => stampOf(b).localeCompare(stampOf(a)))[0];
10332
10368
  };
10333
10369
  var MAX_ABSORBED_HANDBACKS = 2;
10334
- function planConflictBounce(cwd, ticket, branch, base, paths, events) {
10370
+ function planConflictBounce(cwd, ticket, branch, base, paths, events, merged = []) {
10371
+ const siblings = merged.filter((m) => m.branch !== branch && paths.some((p) => (gitOk(cwd, ["diff", "--name-only", `${base}...${m.branch}`, "--", p]) ?? "").trim() !== ""));
10335
10372
  return {
10336
10373
  ticket,
10337
10374
  branch,
@@ -10341,7 +10378,8 @@ function planConflictBounce(cwd, ticket, branch, base, paths, events) {
10341
10378
  priorBounces: priorBounces(ticket, events),
10342
10379
  cwd,
10343
10380
  baseSha: gitOk(cwd, ["rev-parse", "--verify", "-q", base]) || null,
10344
- priorBaseSha: baseShaOf(lastActedHandBack(ticket, events) ?? { record_id: "", event_type: "" })
10381
+ priorBaseSha: baseShaOf(lastActedHandBack(ticket, events) ?? { record_id: "", event_type: "" }),
10382
+ siblings
10345
10383
  };
10346
10384
  }
10347
10385
  function refreshBranch(worktree, base) {
@@ -10370,7 +10408,7 @@ function handBackComment(b, merged) {
10370
10408
 
10371
10409
  **Back with the dev lane \u2014 this branch no longer merges.**
10372
10410
 
10373
- \`${b.branch}\` was verified, but squash-merging it into \`${b.base}\` conflicts in ${list}. Work that landed on \`${b.base}\` after this branch was cut has changed the same lines.
10411
+ \`${b.branch}\` was verified, but squash-merging it into \`${b.base}\` conflicts in ${list}. ${b.siblings?.length ? `This conflicts with ${b.siblings.map((x) => `${x.key} (\`${x.branch}\`)`).join(", ")}, merged earlier in the same release, not with a \`${b.base}\` that moved: merging \`${b.base}\` in will change nothing. Merge ${b.siblings.length === 1 ? "that branch" : "those branches"} into this one (or move this change onto ${b.siblings.length === 1 ? "it" : "one of them"}), resolve, and push.` : `Work that landed on \`${b.base}\` after this branch was cut has changed the same lines.`}
10374
10412
 
10375
10413
  ${merged ? `\`${b.base}\` has been merged into \`${b.branch}\` in its worktree and the conflict is left in place, so \`git status\` there shows exactly what disagrees. Resolve it, commit, push as a plain fast-forward (the branch is pushed, so rebasing it is not an option) and hand back to QA as usual \u2014 \`git merge --abort\` backs the merge out if you would rather start differently.` : `This ship has no worktree for \`${b.branch}\`, so nothing has been merged for you. Merge \`${b.base}\` into the branch wherever it lives, resolve the conflict, and commit.`}
10376
10414
 
@@ -10632,7 +10670,7 @@ Ship ${shipName2} found: ${problem}. The release refuses to squash a ref nobody
10632
10670
  var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "local_ref_skip" && (e.message ?? "").includes(problem));
10633
10671
 
10634
10672
  // src/connect-wizard.ts
10635
- import { delimiter, dirname as dirname10, join as join23 } from "path";
10673
+ import { delimiter, dirname as dirname10, join as join24 } from "path";
10636
10674
  import { accessSync as accessSync2, constants as constants2, existsSync as existsSync21, mkdirSync as mkdirSync13, writeFileSync as writeFileSync15 } from "fs";
10637
10675
  import { stringify } from "yaml";
10638
10676
  var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
@@ -10655,7 +10693,7 @@ function defaultIsExecutable(p) {
10655
10693
  function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
10656
10694
  for (const dir of pathEnv.split(delimiter)) {
10657
10695
  if (!dir) continue;
10658
- const candidate = join23(dir, name);
10696
+ const candidate = join24(dir, name);
10659
10697
  if (isExecutable(candidate)) return candidate;
10660
10698
  }
10661
10699
  return void 0;
@@ -10751,8 +10789,8 @@ Wrote ${o.configPath}.
10751
10789
 
10752
10790
  // src/connect-json.ts
10753
10791
  import { accessSync as accessSync3, constants as constants3 } from "fs";
10754
- import { homedir as homedir7 } from "os";
10755
- import { dirname as dirname11, join as join24 } from "path";
10792
+ import { homedir as homedir8 } from "os";
10793
+ import { dirname as dirname11, join as join25 } from "path";
10756
10794
  import { delimiter as delimiter2 } from "path";
10757
10795
  var CHOICE_EXIT_CODE = 3;
10758
10796
  function createEmitter(json, write) {
@@ -10791,17 +10829,17 @@ function agentBinSearchDirs(home, pathEnv, extraPath) {
10791
10829
  const dirs = [
10792
10830
  ...pathEnv.split(delimiter2),
10793
10831
  ...(extraPath ?? "").split(delimiter2),
10794
- join24(home, ".local/bin"),
10795
- join24(home, ".claude/local"),
10832
+ join25(home, ".local/bin"),
10833
+ join25(home, ".claude/local"),
10796
10834
  "/opt/homebrew/bin",
10797
10835
  "/usr/local/bin",
10798
- join24(home, ".npm-global/bin"),
10799
- join24(home, ".volta/bin")
10836
+ join25(home, ".npm-global/bin"),
10837
+ join25(home, ".volta/bin")
10800
10838
  ];
10801
10839
  return [...new Set(dirs.filter(Boolean))];
10802
10840
  }
10803
10841
  function findAgentBin(name, env, extraPath, isExec) {
10804
- const searched = agentBinSearchDirs(env.home ?? homedir7(), env.pathEnv, extraPath);
10842
+ const searched = agentBinSearchDirs(env.home ?? homedir8(), env.pathEnv, extraPath);
10805
10843
  return { bin: findOnPath2(name, searched.join(delimiter2), isExec), searched };
10806
10844
  }
10807
10845
  function flagWizardAnswers(f, env) {
@@ -10832,8 +10870,8 @@ function flagWizardAnswers(f, env) {
10832
10870
  }
10833
10871
 
10834
10872
  // src/release-run.ts
10835
- import { readFileSync as readFileSync19, writeFileSync as writeFileSync16, existsSync as existsSync22, copyFileSync, mkdirSync as mkdirSync14, mkdtempSync as mkdtempSync2, rmSync as rmSync12 } from "fs";
10836
- import { dirname as dirname12, join as join25 } from "path";
10873
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync16, existsSync as existsSync22, copyFileSync, mkdirSync as mkdirSync14, mkdtempSync as mkdtempSync2, rmSync as rmSync12 } from "fs";
10874
+ import { dirname as dirname12, join as join26 } from "path";
10837
10875
  import { tmpdir as tmpdir3 } from "os";
10838
10876
 
10839
10877
  // src/release.ts
@@ -11551,13 +11589,13 @@ async function bump(o, size) {
11551
11589
  }
11552
11590
  return printed;
11553
11591
  }
11554
- const files = o.repo.release.versionFiles.map((f) => join25(o.cwd, f));
11592
+ const files = o.repo.release.versionFiles.map((f) => join26(o.cwd, f));
11555
11593
  const first = files[0];
11556
11594
  if (!existsSync22(first)) {
11557
11595
  o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
11558
11596
  return null;
11559
11597
  }
11560
- const current = JSON.parse(readFileSync19(first, "utf8")).version;
11598
+ const current = JSON.parse(readFileSync20(first, "utf8")).version;
11561
11599
  if (!current) {
11562
11600
  o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
11563
11601
  return null;
@@ -11570,7 +11608,7 @@ async function bump(o, size) {
11570
11608
  return version;
11571
11609
  }
11572
11610
  for (const f of files) {
11573
- const src = readFileSync19(f, "utf8");
11611
+ const src = readFileSync20(f, "utf8");
11574
11612
  const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
11575
11613
  if (out === src) {
11576
11614
  o.emit.error(`no version field in ${f}`);
@@ -11583,13 +11621,13 @@ async function bump(o, size) {
11583
11621
  function writeChangelog(o, version, entries) {
11584
11622
  const file = o.repo.release.changelog;
11585
11623
  if (!file || entries.length === 0) return;
11586
- const path = join25(o.cwd, file);
11624
+ const path = join26(o.cwd, file);
11587
11625
  const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
11588
11626
  if (o.dryRun) {
11589
11627
  o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
11590
11628
  return;
11591
11629
  }
11592
- const existing = existsSync22(path) ? readFileSync19(path, "utf8") : "# Changelog\n";
11630
+ const existing = existsSync22(path) ? readFileSync20(path, "utf8") : "# Changelog\n";
11593
11631
  writeFileSync16(path, insertChangelogSection(existing, section));
11594
11632
  }
11595
11633
  async function confirm(o, expected) {
@@ -11683,8 +11721,8 @@ function closeReleaseCheckout(cwd, root, dir, branch, run2 = gitOk) {
11683
11721
  function openReleaseCheckout(o) {
11684
11722
  const { remote, base } = o.repo.branch;
11685
11723
  const branch = `crew/release-${Date.now()}`;
11686
- const root = mkdtempSync2(join25(tmpdir3(), "crew-release-"));
11687
- const dir = join25(root, "checkout");
11724
+ const root = mkdtempSync2(join26(tmpdir3(), "crew-release-"));
11725
+ const dir = join26(root, "checkout");
11688
11726
  o.emit.emit(`release checkout: ${dir}`, { step: "release" });
11689
11727
  const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
11690
11728
  try {
@@ -11694,10 +11732,10 @@ function openReleaseCheckout(o) {
11694
11732
  throw e;
11695
11733
  }
11696
11734
  for (const f of o.repo.worktrees.copy) {
11697
- const src = join25(o.cwd, f);
11735
+ const src = join26(o.cwd, f);
11698
11736
  if (!existsSync22(src)) continue;
11699
- mkdirSync14(dirname12(join25(dir, f)), { recursive: true });
11700
- copyFileSync(src, join25(dir, f));
11737
+ mkdirSync14(dirname12(join26(dir, f)), { recursive: true });
11738
+ copyFileSync(src, join26(dir, f));
11701
11739
  }
11702
11740
  return { dir, branch, close };
11703
11741
  }
@@ -12778,17 +12816,17 @@ function nextRoles(decision, ran) {
12778
12816
  }
12779
12817
 
12780
12818
  // src/ship-name.ts
12781
- import { existsSync as existsSync25, mkdirSync as mkdirSync16, readFileSync as readFileSync21, renameSync as renameSync2, writeFileSync as writeFileSync18 } from "fs";
12819
+ import { existsSync as existsSync25, mkdirSync as mkdirSync16, readFileSync as readFileSync22, renameSync as renameSync2, writeFileSync as writeFileSync18 } from "fs";
12782
12820
  import { dirname as dirname14 } from "path";
12783
12821
  import { parseDocument } from "yaml";
12784
12822
 
12785
12823
  // src/machine-id.ts
12786
12824
  import { execFileSync as execFileSync4 } from "child_process";
12787
12825
  import { createHash as createHash4 } from "crypto";
12788
- import { existsSync as existsSync24, mkdirSync as mkdirSync15, readFileSync as readFileSync20, writeFileSync as writeFileSync17 } from "fs";
12789
- import { dirname as dirname13, join as join26 } from "path";
12826
+ import { existsSync as existsSync24, mkdirSync as mkdirSync15, readFileSync as readFileSync21, writeFileSync as writeFileSync17 } from "fs";
12827
+ import { dirname as dirname13, join as join27 } from "path";
12790
12828
  function machineIdPathFor(stateDir) {
12791
- return join26(stateDir, "machine-id");
12829
+ return join27(stateDir, "machine-id");
12792
12830
  }
12793
12831
  var sha256 = (s) => createHash4("sha256").update(s).digest("hex");
12794
12832
  function readRawMachineId(platform2 = process.platform) {
@@ -12802,7 +12840,7 @@ function readRawMachineId(platform2 = process.platform) {
12802
12840
  }
12803
12841
  for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
12804
12842
  if (existsSync24(p)) {
12805
- const id = readFileSync20(p, "utf8").trim();
12843
+ const id = readFileSync21(p, "utf8").trim();
12806
12844
  if (id) return id;
12807
12845
  }
12808
12846
  }
@@ -12814,7 +12852,7 @@ function ensureMachineId(stateDir, opts = {}) {
12814
12852
  if (opts.override?.trim()) return sha256(opts.override.trim());
12815
12853
  const path = machineIdPathFor(stateDir);
12816
12854
  if (existsSync24(path)) {
12817
- const cached = readFileSync20(path, "utf8").trim();
12855
+ const cached = readFileSync21(path, "utf8").trim();
12818
12856
  if (/^[0-9a-f]{64}$/.test(cached)) return cached;
12819
12857
  }
12820
12858
  const raw = (opts.read ?? readRawMachineId)();
@@ -12894,7 +12932,7 @@ async function syncRouteName(i) {
12894
12932
  function readSyncedName(resolvedFile) {
12895
12933
  if (!existsSync25(resolvedFile)) return void 0;
12896
12934
  try {
12897
- const v = JSON.parse(readFileSync21(resolvedFile, "utf8")).shipName;
12935
+ const v = JSON.parse(readFileSync22(resolvedFile, "utf8")).shipName;
12898
12936
  return typeof v === "string" ? v : void 0;
12899
12937
  } catch {
12900
12938
  return void 0;
@@ -12904,7 +12942,7 @@ function writeSyncedName(resolvedFile, name) {
12904
12942
  if (!existsSync25(resolvedFile)) return;
12905
12943
  let raw;
12906
12944
  try {
12907
- raw = JSON.parse(readFileSync21(resolvedFile, "utf8"));
12945
+ raw = JSON.parse(readFileSync22(resolvedFile, "utf8"));
12908
12946
  } catch {
12909
12947
  return;
12910
12948
  }
@@ -12919,7 +12957,7 @@ function writeSyncedName(resolvedFile, name) {
12919
12957
  async function reconcileShipName(i) {
12920
12958
  let local = i.ship.name;
12921
12959
  try {
12922
- local = shipNameOfYaml(readFileSync21(i.configFile, "utf8")) ?? local;
12960
+ local = shipNameOfYaml(readFileSync22(i.configFile, "utf8")) ?? local;
12923
12961
  } catch {
12924
12962
  }
12925
12963
  const resolvedFile = resolvedPathFor(i.stateDir, i.route);
@@ -12943,7 +12981,7 @@ async function reconcileShipName(i) {
12943
12981
  }
12944
12982
  if (result.status !== "ok") return result;
12945
12983
  if (result.action === "adopt" || result.action === "row-wins") {
12946
- const text = readFileSync21(i.configFile, "utf8");
12984
+ const text = readFileSync22(i.configFile, "utf8");
12947
12985
  writeFileSync18(i.configFile, setShipNameInYaml(text, result.name));
12948
12986
  i.ship.name = result.name;
12949
12987
  if (result.action === "row-wins") {
@@ -13852,7 +13890,7 @@ function describeCapability(i) {
13852
13890
  }
13853
13891
 
13854
13892
  // src/agents-sync-run.ts
13855
- import { existsSync as existsSync26, mkdirSync as mkdirSync17, readFileSync as readFileSync22, writeFileSync as writeFileSync19 } from "fs";
13893
+ import { existsSync as existsSync26, mkdirSync as mkdirSync17, readFileSync as readFileSync23, writeFileSync as writeFileSync19 } from "fs";
13856
13894
  import { dirname as dirname15 } from "path";
13857
13895
  async function runAgentsSync(target, o) {
13858
13896
  const dryRun = o.dryRun ?? false;
@@ -13886,7 +13924,7 @@ async function runAgentsSync(target, o) {
13886
13924
  for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
13887
13925
  if (!dryRun) {
13888
13926
  const resolvedPath = resolvedPathFor(o.stateDir, target.route);
13889
- const raw = existsSync26(resolvedPath) ? JSON.parse(readFileSync22(resolvedPath, "utf8")) : {};
13927
+ const raw = existsSync26(resolvedPath) ? JSON.parse(readFileSync23(resolvedPath, "utf8")) : {};
13890
13928
  raw.agentPersonas = personaResult.agentPersonas;
13891
13929
  if (skillResult) raw.agentSkills = skillResult.agentSkills;
13892
13930
  if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
@@ -13997,8 +14035,8 @@ async function showLogEntry(route, entryId, opts = {}) {
13997
14035
  import { isMap as isMap2, parseDocument as parseDocument3, YAMLMap } from "yaml";
13998
14036
 
13999
14037
  // src/repos-cmd.ts
14000
- import { homedir as homedir8 } from "os";
14001
- import { basename as basename8, join as join27, resolve as resolve3 } from "path";
14038
+ import { homedir as homedir9 } from "os";
14039
+ import { basename as basename8, join as join28, resolve as resolve3 } from "path";
14002
14040
  import { isMap, isSeq, parseDocument as parseDocument2, stringify as stringify2 } from "yaml";
14003
14041
  var ReposError = class extends Error {
14004
14042
  };
@@ -14044,7 +14082,7 @@ function parseAddArgs(args, routes, isDir) {
14044
14082
  return { route, path: "." };
14045
14083
  }
14046
14084
  function expandDir(p, base) {
14047
- const home = p === "~" ? homedir8() : p.startsWith("~/") ? `${homedir8()}/${p.slice(2)}` : p;
14085
+ const home = p === "~" ? homedir9() : p.startsWith("~/") ? `${homedir9()}/${p.slice(2)}` : p;
14048
14086
  return resolve3(base, home);
14049
14087
  }
14050
14088
  function routeMapOf(doc, route) {
@@ -14276,7 +14314,7 @@ function planRepoClone(i) {
14276
14314
  }
14277
14315
  const ship = doc.get("ship", true);
14278
14316
  const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
14279
- const target = i.dir ? expandDir(i.dir, i.base) : join27(expandDir(basePath, i.base), i.route.split("/")[0], name);
14317
+ const target = i.dir ? expandDir(i.dir, i.base) : join28(expandDir(basePath, i.base), i.route.split("/")[0], name);
14280
14318
  const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name, isCheckout: i.isCheckout });
14281
14319
  return { ...add, remote, target };
14282
14320
  }
@@ -14675,7 +14713,7 @@ ${run2.err}` });
14675
14713
  }
14676
14714
 
14677
14715
  // src/sample-ticket.ts
14678
- import { basename as basename9, dirname as dirname17, join as join28 } from "path";
14716
+ import { basename as basename9, dirname as dirname17, join as join29 } from "path";
14679
14717
  var SAMPLE_REPO = "crew-sample";
14680
14718
  var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
14681
14719
  var SAMPLE_MARKER = "sample: true";
@@ -14698,8 +14736,8 @@ has something safe to work on: its test and build hooks always pass, it has no
14698
14736
  remote, and \`crew sample-ticket --remove\` deletes it.
14699
14737
  `;
14700
14738
  function sampleDir(existing, reposBase, workspace) {
14701
- if (existing.length > 0) return join28(dirname17(existing[0]), SAMPLE_REPO);
14702
- return join28(reposBase, workspace, SAMPLE_REPO);
14739
+ if (existing.length > 0) return join29(dirname17(existing[0]), SAMPLE_REPO);
14740
+ return join29(reposBase, workspace, SAMPLE_REPO);
14703
14741
  }
14704
14742
  var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
14705
14743
  function sampleDescription() {
@@ -14749,7 +14787,15 @@ function planFix(f) {
14749
14787
  const actions = [];
14750
14788
  const notes = [];
14751
14789
  for (const r of f.routes) {
14752
- if (!r.enabled && sound(r)) actions.push({ kind: "enable", route: r.route });
14790
+ if (!r.enabled && sound(r)) {
14791
+ actions.push({ kind: "enable", route: r.route });
14792
+ const gaps = r.readiness ?? [];
14793
+ if (gaps.length) {
14794
+ notes.push(`${r.route}: ${gaps.length} release-readiness gap${gaps.length === 1 ? "" : "s"} (${gaps[0]}${gaps.length > 1 ? ", ..." : ""}) \u2014 the route will run, but releases stop at the first missing hook until it is added`);
14795
+ }
14796
+ } else if (!sound(r) && !r.enabled) {
14797
+ notes.push(`${r.route}: not enabled \u2014 ${r.hasRepos ? r.problems[0] : "no checkout is configured"}`);
14798
+ }
14753
14799
  }
14754
14800
  if (f.schedulerAbsent && f.routes.some(sound)) {
14755
14801
  if (f.host === "windows") notes.push("scheduler support is not built for Windows yet \u2014 nothing to install; run `crew run` by hand");
@@ -14759,12 +14805,15 @@ function planFix(f) {
14759
14805
  }
14760
14806
  async function gatherHealth(c, check) {
14761
14807
  let problems;
14808
+ let readiness = [];
14762
14809
  try {
14763
- problems = await check();
14810
+ const r = await check();
14811
+ if (Array.isArray(r)) problems = r;
14812
+ else ({ problems, readiness } = r);
14764
14813
  } catch (e) {
14765
14814
  problems = [`checks could not run: ${e instanceof Error ? e.message : String(e)}`];
14766
14815
  }
14767
- return { route: c.route, enabled: c.enabled, hasRepos: c.hasRepos, problems };
14816
+ return { route: c.route, enabled: c.enabled, hasRepos: c.hasRepos, problems, readiness };
14768
14817
  }
14769
14818
  function routesInScope(routes, named) {
14770
14819
  return named ? routes.filter((r) => r.route === named) : routes;
@@ -14780,8 +14829,8 @@ function schedulerInstalled(plan, exists, crontab) {
14780
14829
  }
14781
14830
 
14782
14831
  // src/worktree-sweep.ts
14783
- import { existsSync as existsSync27, readFileSync as readFileSync23, readdirSync as readdirSync8 } from "fs";
14784
- import { basename as basename10, dirname as dirname18, join as join29, resolve as resolve4 } from "path";
14832
+ import { existsSync as existsSync27, readFileSync as readFileSync24, readdirSync as readdirSync8 } from "fs";
14833
+ import { basename as basename10, dirname as dirname18, join as join30, resolve as resolve4 } from "path";
14785
14834
  function planWorktreeSweep(target, tickets, contract) {
14786
14835
  const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
14787
14836
  const verified = contract.statuses.verified;
@@ -14848,7 +14897,7 @@ function siblingWorktreeDirs(target) {
14848
14897
  for (const name of names) {
14849
14898
  if (!name.startsWith(stem)) continue;
14850
14899
  const m = DIR_NUMBER_SUFFIXED.exec(name);
14851
- const path = join29(parent, name);
14900
+ const path = join30(parent, name);
14852
14901
  if (m && !registered.has(resolve4(path))) out.push({ path, number: m[1] });
14853
14902
  }
14854
14903
  return out;
@@ -14951,7 +15000,7 @@ function isHandBackMerge(path, base) {
14951
15000
  if (!msgPath) return false;
14952
15001
  let first;
14953
15002
  try {
14954
- first = readFileSync23(resolve4(path, msgPath), "utf8").split("\n")[0] ?? "";
15003
+ first = readFileSync24(resolve4(path, msgPath), "utf8").split("\n")[0] ?? "";
14955
15004
  } catch {
14956
15005
  return false;
14957
15006
  }
@@ -15057,13 +15106,13 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
15057
15106
 
15058
15107
  // src/stream-sweep.ts
15059
15108
  import { existsSync as existsSync28, readdirSync as readdirSync9, rmSync as rmSync13, statSync as statSync8 } from "fs";
15060
- import { join as join30 } from "path";
15109
+ import { join as join31 } from "path";
15061
15110
  var DAY_MS2 = 24 * 60 * 60 * 1e3;
15062
15111
  function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
15063
- const dir = join30(stateDir, "streams");
15112
+ const dir = join31(stateDir, "streams");
15064
15113
  if (!existsSync28(dir)) return [];
15065
15114
  const cutoff = now - retentionDays * DAY_MS2;
15066
- return readdirSync9(dir).map((name) => join30(dir, name)).filter((path) => {
15115
+ return readdirSync9(dir).map((name) => join31(dir, name)).filter((path) => {
15067
15116
  try {
15068
15117
  return statSync8(path).mtimeMs < cutoff;
15069
15118
  } catch {
@@ -15091,7 +15140,7 @@ function applyStreamSweep(paths, dryRun, log) {
15091
15140
  function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
15092
15141
  if (!existsSync28(stateDir)) return [];
15093
15142
  const cutoff = now - retentionDays * DAY_MS2;
15094
- return readdirSync9(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join30(stateDir, name)).filter((path) => {
15143
+ return readdirSync9(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join31(stateDir, name)).filter((path) => {
15095
15144
  try {
15096
15145
  return statSync8(path).mtimeMs < cutoff;
15097
15146
  } catch {
@@ -15101,18 +15150,18 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
15101
15150
  }
15102
15151
 
15103
15152
  // src/cli.ts
15104
- import { realpathSync as realpathSync3, readFileSync as readFileSync29, existsSync as existsSync34, mkdirSync as mkdirSync22, writeFileSync as writeFileSync23, rmSync as rmSync15, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync3, statSync as statSync10, readdirSync as readdirSync14 } from "fs";
15153
+ import { realpathSync as realpathSync3, readFileSync as readFileSync30, existsSync as existsSync34, mkdirSync as mkdirSync22, writeFileSync as writeFileSync23, rmSync as rmSync15, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync3, statSync as statSync10, readdirSync as readdirSync14 } from "fs";
15105
15154
  import { createInterface } from "readline/promises";
15106
15155
  import { dirname as dirOf, resolve as resolvePath } from "path";
15107
15156
 
15108
15157
  // src/ssh-keys.ts
15109
15158
  import { execFileSync as execFileSync6 } from "child_process";
15110
- import { existsSync as existsSync29, mkdirSync as mkdirSync18, readFileSync as readFileSync24, chmodSync } from "fs";
15111
- import { dirname as dirname19, join as join31 } from "path";
15159
+ import { existsSync as existsSync29, mkdirSync as mkdirSync18, readFileSync as readFileSync25, chmodSync } from "fs";
15160
+ import { dirname as dirname19, join as join32 } from "path";
15112
15161
  var SshKeyError = class extends Error {
15113
15162
  };
15114
15163
  function shipSshKeyPathFor(stateDir) {
15115
- return join31(stateDir, "ssh", "id_ed25519");
15164
+ return join32(stateDir, "ssh", "id_ed25519");
15116
15165
  }
15117
15166
  function ensureShipSshKeypair(stateDir) {
15118
15167
  const privateKeyPath = shipSshKeyPathFor(stateDir);
@@ -15141,7 +15190,7 @@ function ensureShipSshKeypair(stateDir) {
15141
15190
  if (!existsSync29(publicKeyPath)) {
15142
15191
  throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
15143
15192
  }
15144
- const publicKey = readFileSync24(publicKeyPath, "utf8").trim();
15193
+ const publicKey = readFileSync25(publicKeyPath, "utf8").trim();
15145
15194
  return { privateKeyPath, publicKeyPath, publicKey };
15146
15195
  }
15147
15196
  function sshKeygenAvailable() {
@@ -15357,9 +15406,9 @@ function syncAllPassengerCheckouts(cfg) {
15357
15406
 
15358
15407
  // src/tunnel.ts
15359
15408
  import { spawn as spawn5 } from "child_process";
15360
- import { existsSync as existsSync30, mkdirSync as mkdirSync19, readFileSync as readFileSync25, writeFileSync as writeFileSync20, unlinkSync as unlinkSync3, readdirSync as readdirSync10 } from "fs";
15409
+ import { existsSync as existsSync30, mkdirSync as mkdirSync19, readFileSync as readFileSync26, writeFileSync as writeFileSync20, unlinkSync as unlinkSync3, readdirSync as readdirSync10 } from "fs";
15361
15410
  import { randomInt } from "crypto";
15362
- import { join as join32 } from "path";
15411
+ import { join as join33 } from "path";
15363
15412
  var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
15364
15413
  var defaultSpawn = (cmd, args) => spawn5(cmd, args, DEFAULT_SPAWN_OPTIONS);
15365
15414
  var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
@@ -15426,20 +15475,20 @@ function startTunnel(opts) {
15426
15475
  };
15427
15476
  }
15428
15477
  function tunnelStatePath(stateDir, workspaceId) {
15429
- return join32(stateDir, "passengers", `${workspaceId}.json`);
15478
+ return join33(stateDir, "passengers", `${workspaceId}.json`);
15430
15479
  }
15431
15480
  function readPersistedTunnel(stateDir, workspaceId) {
15432
15481
  const path = tunnelStatePath(stateDir, workspaceId);
15433
15482
  if (!existsSync30(path)) return void 0;
15434
15483
  try {
15435
- return JSON.parse(readFileSync25(path, "utf8"));
15484
+ return JSON.parse(readFileSync26(path, "utf8"));
15436
15485
  } catch {
15437
15486
  return void 0;
15438
15487
  }
15439
15488
  }
15440
15489
  function writePersistedTunnel(stateDir, state) {
15441
15490
  const path = tunnelStatePath(stateDir, state.workspaceId);
15442
- mkdirSync19(join32(stateDir, "passengers"), { recursive: true });
15491
+ mkdirSync19(join33(stateDir, "passengers"), { recursive: true });
15443
15492
  writeFileSync20(path, `${JSON.stringify(state, null, 2)}
15444
15493
  `);
15445
15494
  }
@@ -15537,7 +15586,7 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
15537
15586
  }
15538
15587
  }
15539
15588
  function readAllPersistedWorkspaceIds(stateDir) {
15540
- const dir = join32(stateDir, "passengers");
15589
+ const dir = join33(stateDir, "passengers");
15541
15590
  if (!existsSync30(dir)) return [];
15542
15591
  return readdirSync10(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
15543
15592
  }
@@ -15659,8 +15708,8 @@ async function unregisterEndpoint(workspaceId, info, d) {
15659
15708
  import { createServer as createHttpServer } from "http";
15660
15709
  import { spawn as spawn6 } from "child_process";
15661
15710
  import { randomBytes } from "crypto";
15662
- import { existsSync as existsSync31, mkdirSync as mkdirSync20, readFileSync as readFileSync26, writeFileSync as writeFileSync21, unlinkSync as unlinkSync4, readdirSync as readdirSync11 } from "fs";
15663
- import { join as join33 } from "path";
15711
+ import { existsSync as existsSync31, mkdirSync as mkdirSync20, readFileSync as readFileSync27, writeFileSync as writeFileSync21, unlinkSync as unlinkSync4, readdirSync as readdirSync11 } from "fs";
15712
+ import { join as join34 } from "path";
15664
15713
 
15665
15714
  // src/secret-compare.ts
15666
15715
  import { timingSafeEqual } from "crypto";
@@ -15749,20 +15798,20 @@ var defaultSpawn2 = (cmd, args, env) => {
15749
15798
  return child;
15750
15799
  };
15751
15800
  function syncDaemonStatePath(stateDir, workspaceId) {
15752
- return join33(stateDir, "passengers", `${workspaceId}-sync.json`);
15801
+ return join34(stateDir, "passengers", `${workspaceId}-sync.json`);
15753
15802
  }
15754
15803
  function readPersistedSyncDaemon(stateDir, workspaceId) {
15755
15804
  const path = syncDaemonStatePath(stateDir, workspaceId);
15756
15805
  if (!existsSync31(path)) return void 0;
15757
15806
  try {
15758
- return JSON.parse(readFileSync26(path, "utf8"));
15807
+ return JSON.parse(readFileSync27(path, "utf8"));
15759
15808
  } catch {
15760
15809
  return void 0;
15761
15810
  }
15762
15811
  }
15763
15812
  function writePersistedSyncDaemon(stateDir, state) {
15764
15813
  const path = syncDaemonStatePath(stateDir, state.workspaceId);
15765
- mkdirSync20(join33(stateDir, "passengers"), { recursive: true });
15814
+ mkdirSync20(join34(stateDir, "passengers"), { recursive: true });
15766
15815
  writeFileSync21(path, `${JSON.stringify(state, null, 2)}
15767
15816
  `);
15768
15817
  }
@@ -15783,7 +15832,7 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
15783
15832
  }
15784
15833
  }
15785
15834
  function readAllPersistedSyncWorkspaceIds(stateDir) {
15786
- const dir = join33(stateDir, "passengers");
15835
+ const dir = join34(stateDir, "passengers");
15787
15836
  if (!existsSync31(dir)) return [];
15788
15837
  const suffix = "-sync.json";
15789
15838
  return readdirSync11(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
@@ -15872,12 +15921,12 @@ function describeCause(err) {
15872
15921
  }
15873
15922
 
15874
15923
  // src/daemon.ts
15875
- import { existsSync as existsSync33, mkdirSync as mkdirSync21, readFileSync as readFileSync28, readdirSync as readdirSync13, rmSync as rmSync14, statSync as statSync9, writeFileSync as writeFileSync22 } from "fs";
15876
- import { join as join35 } from "path";
15924
+ import { existsSync as existsSync33, mkdirSync as mkdirSync21, readFileSync as readFileSync29, readdirSync as readdirSync13, rmSync as rmSync14, statSync as statSync9, writeFileSync as writeFileSync22 } from "fs";
15925
+ import { join as join36 } from "path";
15877
15926
 
15878
15927
  // src/interrupted.ts
15879
- import { existsSync as existsSync32, readdirSync as readdirSync12, readFileSync as readFileSync27 } from "fs";
15880
- import { join as join34 } from "path";
15928
+ import { existsSync as existsSync32, readdirSync as readdirSync12, readFileSync as readFileSync28 } from "fs";
15929
+ import { join as join35 } from "path";
15881
15930
  var INTERRUPTED_MARKER = calloutHeader("NOTE", "Interrupted");
15882
15931
  function interruptedComment(b) {
15883
15932
  const tree = b.dirty === null ? "The worktree could not be checked." : b.dirty ? "The worktree has uncommitted changes \u2014 they are this seat's own, from that run." : "The worktree is clean.";
@@ -15898,19 +15947,19 @@ function runFinished(streamPath) {
15898
15947
  const path = `${streamPath}.meta.json`;
15899
15948
  if (!existsSync32(path)) return false;
15900
15949
  try {
15901
- return JSON.parse(readFileSync27(path, "utf8")).running !== true;
15950
+ return JSON.parse(readFileSync28(path, "utf8")).running !== true;
15902
15951
  } catch {
15903
15952
  return true;
15904
15953
  }
15905
15954
  }
15906
15955
  function orphanedStreams(o) {
15907
- const dir = join34(o.stateDir, "streams");
15956
+ const dir = join35(o.stateDir, "streams");
15908
15957
  if (!existsSync32(dir)) return [];
15909
15958
  const ownerAlive = o.isOwnerAlive ?? pidAlive;
15910
15959
  const out = [];
15911
15960
  for (const name of readdirSync12(dir)) {
15912
15961
  if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
15913
- const streamPath = join34(dir, name);
15962
+ const streamPath = join35(dir, name);
15914
15963
  if (runFinished(streamPath)) continue;
15915
15964
  const sidecar = readRunSidecar(streamPath);
15916
15965
  if (!sidecar || sidecar.route !== o.routeLabel) continue;
@@ -16030,7 +16079,7 @@ function latestMtime(path) {
16030
16079
  if (!stat.isDirectory()) return 0;
16031
16080
  let latest = stat.mtimeMs;
16032
16081
  for (const entry of readdirSync13(path, { withFileTypes: true })) {
16033
- latest = Math.max(latest, latestMtime(join35(path, entry.name)));
16082
+ latest = Math.max(latest, latestMtime(join36(path, entry.name)));
16034
16083
  }
16035
16084
  return latest;
16036
16085
  }
@@ -16042,10 +16091,10 @@ function makeStaleChecker(paths, opts = {}) {
16042
16091
  return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync33(opts.reloadSentinel);
16043
16092
  }
16044
16093
  function reloadSentinelFor(stateDir) {
16045
- return join35(stateDir, "reload-requested");
16094
+ return join36(stateDir, "reload-requested");
16046
16095
  }
16047
16096
  function daemonStampFor(stateDir) {
16048
- return join35(stateDir, "daemon.started");
16097
+ return join36(stateDir, "daemon.started");
16049
16098
  }
16050
16099
  function noteDaemonStarted(stateDir, now = Date.now()) {
16051
16100
  mkdirSync21(stateDir, { recursive: true });
@@ -16055,7 +16104,7 @@ function noteDaemonStarted(stateDir, now = Date.now()) {
16055
16104
  }
16056
16105
  function readDaemonStarted(stateDir) {
16057
16106
  try {
16058
- const v = JSON.parse(readFileSync28(daemonStampFor(stateDir), "utf8")).startedAt;
16107
+ const v = JSON.parse(readFileSync29(daemonStampFor(stateDir), "utf8")).startedAt;
16059
16108
  return typeof v === "number" ? v : null;
16060
16109
  } catch {
16061
16110
  return null;
@@ -17076,7 +17125,7 @@ if (command === "passenger-sync-daemon") {
17076
17125
  }
17077
17126
  if (host === "linux" && hasSystemd) {
17078
17127
  const label = labelFor(CREW_HOME, "run");
17079
- const staleTimer = join36(homedir9(), ".config", "systemd", "user", `${label}.timer`);
17128
+ const staleTimer = join37(homedir10(), ".config", "systemd", "user", `${label}.timer`);
17080
17129
  if (existsSync34(staleTimer)) {
17081
17130
  process.stdout.write(
17082
17131
  `removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
@@ -17096,6 +17145,7 @@ if (command === "passenger-sync-daemon") {
17096
17145
  async function routeProblems(c) {
17097
17146
  const host = hostPlatform();
17098
17147
  const problems = [];
17148
+ const readiness = [];
17099
17149
  if (c.hostPassengers) {
17100
17150
  if (!dockerAvailable()) problems.push("host passengers: docker is not available");
17101
17151
  if (!sshKeygenAvailable()) problems.push("host passengers: ssh-keygen is not available");
@@ -17105,7 +17155,7 @@ if (command === "passenger-sync-daemon") {
17105
17155
  for (const r of await resolvedRepos(c)) {
17106
17156
  if (!existsSync34(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
17107
17157
  if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
17108
- problems.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
17158
+ readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
17109
17159
  }
17110
17160
  try {
17111
17161
  const ships = await tracker.shipRows();
@@ -17117,7 +17167,7 @@ if (command === "passenger-sync-daemon") {
17117
17167
  } catch (e) {
17118
17168
  problems.push(`tracker unreachable: ${e instanceof Error ? e.message : String(e)}`);
17119
17169
  }
17120
- return problems;
17170
+ return { problems, readiness };
17121
17171
  }
17122
17172
  async function doctorFix(o) {
17123
17173
  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))));
@@ -17138,7 +17188,7 @@ if (command === "passenger-sync-daemon") {
17138
17188
  for (const a of plan.actions) {
17139
17189
  if (a.kind === "enable") {
17140
17190
  if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
17141
- writeFileSync23(cfg.configFile, planEnable(readFileSync29(cfg.configFile, "utf8"), a.route));
17191
+ writeFileSync23(cfg.configFile, planEnable(readFileSync30(cfg.configFile, "utf8"), a.route));
17142
17192
  enabledSound.add(a.route);
17143
17193
  changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
17144
17194
  } else {
@@ -17559,7 +17609,8 @@ ${changed2.map((c) => ` - ${c}
17559
17609
  f.candidate.branch,
17560
17610
  repo.branch.base,
17561
17611
  f.paths,
17562
- handedBack
17612
+ handedBack,
17613
+ outcome.merged.filter((m) => m.branch).map((m) => ({ key: m.ticket.issue_id, branch: m.branch }))
17563
17614
  );
17564
17615
  const r = await applyConflictBounce(tracker, bounce, tracker.contract, seat, remit, dryRun);
17565
17616
  if (r.kind === "failed") {
@@ -18159,7 +18210,7 @@ ${changed2.map((c) => ` - ${c}
18159
18210
  process.exit(1);
18160
18211
  }
18161
18212
  requireArmed("run the daemon loop");
18162
- const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join36(CREW_HOME, "src"), join36(CREW_HOME, "dist"), join36(CREW_HOME, "bin")];
18213
+ const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join37(CREW_HOME, "src"), join37(CREW_HOME, "dist"), join37(CREW_HOME, "bin")];
18163
18214
  const configPaths = daemonConfigPaths(cfg);
18164
18215
  watchPaths.push(...configPaths);
18165
18216
  noteDaemonStarted(cfg.ship.stateDir);
@@ -18378,7 +18429,7 @@ ${changed2.map((c) => ` - ${c}
18378
18429
  `);
18379
18430
  if (!dryRun) {
18380
18431
  const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
18381
- const raw = existsSync34(resolvedPath) ? JSON.parse(readFileSync29(resolvedPath, "utf8")) : {};
18432
+ const raw = existsSync34(resolvedPath) ? JSON.parse(readFileSync30(resolvedPath, "utf8")) : {};
18382
18433
  raw.agentSkills = result.agentSkills;
18383
18434
  mkdirSync22(dirOf(resolvedPath), { recursive: true });
18384
18435
  writeFileSync23(resolvedPath, `${JSON.stringify(raw, null, 2)}
@@ -18404,7 +18455,7 @@ ${changed2.map((c) => ` - ${c}
18404
18455
  throw e;
18405
18456
  }
18406
18457
  const oldName = cfg.ship.name;
18407
- writeFileSync23(cfg.configFile, setShipNameInYaml(readFileSync29(cfg.configFile, "utf8"), newName));
18458
+ writeFileSync23(cfg.configFile, setShipNameInYaml(readFileSync30(cfg.configFile, "utf8"), newName));
18408
18459
  cfg.ship.name = newName;
18409
18460
  let renameMachineId;
18410
18461
  try {
@@ -18451,7 +18502,7 @@ ${changed2.map((c) => ` - ${c}
18451
18502
  process.exit(2);
18452
18503
  };
18453
18504
  if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks") usage2();
18454
- const configText = readFileSync29(cfg.configFile, "utf8");
18505
+ const configText = readFileSync30(cfg.configFile, "utf8");
18455
18506
  if (sub === "hooks") {
18456
18507
  try {
18457
18508
  const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
@@ -18465,7 +18516,7 @@ ${changed2.map((c) => ` - ${c}
18465
18516
  }
18466
18517
  }, read: (r) => {
18467
18518
  try {
18468
- return readFileSync29(resolvePath(dir, r), "utf8");
18519
+ return readFileSync30(resolvePath(dir, r), "utf8");
18469
18520
  } catch {
18470
18521
  return void 0;
18471
18522
  }
@@ -18575,7 +18626,7 @@ ${changed2.map((c) => ` - ${c}
18575
18626
  if (sub === "list" && !routeName) usage2();
18576
18627
  if (sub === "add") {
18577
18628
  try {
18578
- const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync34(resolvePath(p.replace(/^~(?=\/|$)/, homedir9()))));
18629
+ const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync34(resolvePath(p.replace(/^~(?=\/|$)/, homedir10()))));
18579
18630
  routeName = a.route;
18580
18631
  addPath = a.path;
18581
18632
  addName = a.name;
@@ -18606,7 +18657,7 @@ ${changed2.map((c) => ` - ${c}
18606
18657
  let resolvedRemotes;
18607
18658
  if (existsSync34(resolvedFile)) {
18608
18659
  try {
18609
- resolvedRemotes = JSON.parse(readFileSync29(resolvedFile, "utf8"));
18660
+ resolvedRemotes = JSON.parse(readFileSync30(resolvedFile, "utf8"));
18610
18661
  } catch {
18611
18662
  }
18612
18663
  }
@@ -18627,7 +18678,7 @@ ${changed2.map((c) => ` - ${c}
18627
18678
  isCheckout,
18628
18679
  repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
18629
18680
  try {
18630
- return Object.keys(parseRepoConfig(readFileSync29(resolvePath(d, f), "utf8"), f).hooks).length > 0;
18681
+ return Object.keys(parseRepoConfig(readFileSync30(resolvePath(d, f), "utf8"), f).hooks).length > 0;
18631
18682
  } catch {
18632
18683
  return false;
18633
18684
  }
@@ -18682,7 +18733,7 @@ ${changed2.map((c) => ` - ${c}
18682
18733
  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}`);
18683
18734
  break;
18684
18735
  }
18685
- const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir9()));
18736
+ const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir10()));
18686
18737
  const top = existsSync34(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
18687
18738
  if (!top) throw new ReposError(`${target} is not a git checkout`);
18688
18739
  const origin = originOf(top);
@@ -18736,7 +18787,7 @@ ${changed2.map((c) => ` - ${c}
18736
18787
  process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
18737
18788
  `);
18738
18789
  if (existsSync34(resolvedFile)) {
18739
- const raw = JSON.parse(readFileSync29(resolvedFile, "utf8"));
18790
+ const raw = JSON.parse(readFileSync30(resolvedFile, "utf8"));
18740
18791
  raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
18741
18792
  raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
18742
18793
  writeFileSync23(resolvedFile, `${JSON.stringify(raw, null, 2)}
@@ -19094,7 +19145,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
19094
19145
  process.exit(2);
19095
19146
  }
19096
19147
  if (found.shipNameAdopted && !dryRun) {
19097
- writeFileSync23(cfg.configFile, setShipNameInYaml(readFileSync29(cfg.configFile, "utf8"), found.shipNameAdopted));
19148
+ writeFileSync23(cfg.configFile, setShipNameInYaml(readFileSync30(cfg.configFile, "utf8"), found.shipNameAdopted));
19098
19149
  cfg.ship.name = found.shipNameAdopted;
19099
19150
  }
19100
19151
  if (found.shipRow) {
@@ -19140,7 +19191,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
19140
19191
  const hadPreviousFile = existsSync34(resolvedPath);
19141
19192
  if (hadPreviousFile) {
19142
19193
  try {
19143
- previous = JSON.parse(readFileSync29(resolvedPath, "utf8"));
19194
+ previous = JSON.parse(readFileSync30(resolvedPath, "utf8"));
19144
19195
  } catch {
19145
19196
  }
19146
19197
  }
@@ -19998,7 +20049,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
19998
20049
  };
19999
20050
  const droppedRoutes = [];
20000
20051
  try {
20001
- const rawCfg = parseYaml2(readFileSync29(cfg.configFile, "utf8"));
20052
+ const rawCfg = parseYaml2(readFileSync30(cfg.configFile, "utf8"));
20002
20053
  for (const r of rawCfg?.routes ?? []) {
20003
20054
  if (typeof r?.route !== "string" || cfg.routes.some((x) => x.route === r.route)) continue;
20004
20055
  try {
@@ -20092,7 +20143,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
20092
20143
  }
20093
20144
  case "log": {
20094
20145
  try {
20095
- const text = readFileSync29(cfg.ship.logFile, "utf8").split("\n");
20146
+ const text = readFileSync30(cfg.ship.logFile, "utf8").split("\n");
20096
20147
  process.stdout.write(`${text.slice(-(Number(value("tail")) || 40)).join("\n")}
20097
20148
  `);
20098
20149
  } catch {
@@ -20115,7 +20166,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
20115
20166
  };
20116
20167
  if (!route.resolved) fail(`route ${route.route} is not connected \u2014 run \`crew connect\` first`);
20117
20168
  const tracker = new Tracker(route, cfg.ship);
20118
- const configText = readFileSync29(cfg.configFile, "utf8");
20169
+ const configText = readFileSync30(cfg.configFile, "utf8");
20119
20170
  const cfgBase = dirname20(cfg.configFile);
20120
20171
  const configured = configuredRepos(configText, route.route, cfgBase);
20121
20172
  const dir = configured.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured.map((r) => r.dir), route.reposBasePath, route.route.split("/")[0]);
@@ -20153,19 +20204,19 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
20153
20204
  if (row) {
20154
20205
  await tracker.deleteRepoRow(row.id);
20155
20206
  if (existsSync34(resolvedFile)) {
20156
- const raw = JSON.parse(readFileSync29(resolvedFile, "utf8"));
20207
+ const raw = JSON.parse(readFileSync30(resolvedFile, "utf8"));
20157
20208
  if (raw.repoNames) delete raw.repoNames[row.id];
20158
20209
  if (raw.repoRemotes) delete raw.repoRemotes[row.id];
20159
20210
  writeFileSync23(resolvedFile, `${JSON.stringify(raw, null, 2)}
20160
20211
  `);
20161
20212
  }
20162
20213
  }
20163
- if (existsSync34(join36(dir, ".git")) && basename11(dir) === SAMPLE_REPO) rmSync15(dir, { recursive: true, force: true });
20214
+ if (existsSync34(join37(dir, ".git")) && basename11(dir) === SAMPLE_REPO) rmSync15(dir, { recursive: true, force: true });
20164
20215
  say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
20165
20216
  break;
20166
20217
  }
20167
20218
  const projectId = route.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
20168
- const fresh = !existsSync34(join36(dir, ".git"));
20219
+ const fresh = !existsSync34(join37(dir, ".git"));
20169
20220
  if (existsSync34(dir) && fresh && readdirSync14(dir).length > 0) fail(`${dir} exists and is not a git checkout \u2014 move it aside`);
20170
20221
  if (dryRun) {
20171
20222
  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`);
@@ -20173,8 +20224,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
20173
20224
  }
20174
20225
  if (fresh) {
20175
20226
  mkdirSync22(dir, { recursive: true });
20176
- writeFileSync23(join36(dir, "README.md"), SAMPLE_README);
20177
- writeFileSync23(join36(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
20227
+ writeFileSync23(join37(dir, "README.md"), SAMPLE_README);
20228
+ writeFileSync23(join37(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
20178
20229
  const g = (...a) => {
20179
20230
  const r = spawnSync6("git", a, { cwd: dir, encoding: "utf8" });
20180
20231
  if (r.status !== 0) fail(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
@@ -20193,7 +20244,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
20193
20244
  });
20194
20245
  repoId = (await tracker.createRepoRow(payload)).id;
20195
20246
  if (existsSync34(resolvedFile)) {
20196
- const raw = JSON.parse(readFileSync29(resolvedFile, "utf8"));
20247
+ const raw = JSON.parse(readFileSync30(resolvedFile, "utf8"));
20197
20248
  raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
20198
20249
  raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
20199
20250
  writeFileSync23(resolvedFile, `${JSON.stringify(raw, null, 2)}
@@ -20404,6 +20455,9 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
20404
20455
  // Additive (CREW-1379): `unsafe` means a ship must not cut a
20405
20456
  // worktree or release from this base. `version` stays 1.
20406
20457
  baseBranch: baseHealth(r),
20458
+ // Additive (CREW-2537): gaps that only matter once a ticket reaches
20459
+ // release (no deploy hook, no CI provider...). They never stop a poll.
20460
+ releaseReadiness: validateEffective(r.config),
20407
20461
  // Additive (CREW-2051): worktrees the sweep refuses to remove.
20408
20462
  heldWorktrees: openShipAttention(state).filter((i) => i.kind === "worktree_held" && i.held?.repo === r.name).map((i) => ({
20409
20463
  path: i.held.path,
@@ -20556,7 +20610,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
20556
20610
  const unitPlan = planInstall(cfg.ship, CREW_HOME, host, detectSystemd(), "run");
20557
20611
  const abandons = unitAbandonsAgents(unitPlan, (p) => {
20558
20612
  try {
20559
- return readFileSync29(p, "utf8");
20613
+ return readFileSync30(p, "utf8");
20560
20614
  } catch {
20561
20615
  return null;
20562
20616
  }
@@ -20613,7 +20667,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
20613
20667
  );
20614
20668
  }
20615
20669
  for (const p2 of validateEffective(config)) {
20616
- process.stdout.write(` - ${p2}
20670
+ process.stdout.write(` - release readiness: ${p2}
20617
20671
  `);
20618
20672
  }
20619
20673
  const dh = doctorBaseReport(r, config.branch.base, baseHealth);
@@ -20944,6 +20998,10 @@ ${foreign.join("\n")}
20944
20998
  }
20945
20999
  for (const l of doctorSelftestLines(readSelftest(cfg.ship.stateDir), shipToolchains(cfg.ship).map((t) => t.name))) {
20946
21000
  process.stdout.write(`${l}
21001
+ `);
21002
+ }
21003
+ for (const t of shipToolchains(cfg.ship).filter((t2) => t2.harness === "claude")) {
21004
+ process.stdout.write(`auth: ${t.name}: ${describeClaudeAuth(t)}
20947
21005
  `);
20948
21006
  }
20949
21007
  const interactive = !!(process.stdin.isTTY && process.stdout.isTTY);