@tablation/crew 0.41.0 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { resolve as resolve4, dirname as dirname12, join as join22, basename as basename9 } from "path";
4
+ import { resolve as resolve4, dirname as dirname12, join as join23, basename as basename9 } from "path";
5
5
  import { fileURLToPath } from "url";
6
- import { hostname as hostname2, homedir as homedir7 } from "os";
6
+ import { hostname as hostname2, homedir as homedir8 } from "os";
7
7
  import { spawnSync as spawnSync5 } from "child_process";
8
8
 
9
9
  // src/argv.ts
@@ -40,11 +40,31 @@ function positionalArgs(argv2) {
40
40
  }
41
41
  return out;
42
42
  }
43
+ var WORKTREE_ROLES = ["dev", "design", "qa"];
44
+ function splitWorktreeArgs(positional2, routeNames) {
45
+ const args = positional2.slice(1);
46
+ if (args.length > 1 && WORKTREE_ROLES.includes(args[args.length - 1] ?? "")) args.pop();
47
+ const [a, b] = [args[0], args[1]];
48
+ const isTicket = (s) => !!s && /^(?:[A-Za-z]+-)?\d+$/.test(s.trim());
49
+ if (b !== void 0) return { route: a, ticket: b };
50
+ if (a !== void 0 && isTicket(a) && !routeNames.includes(a)) return { ticket: a };
51
+ return { route: a, ticket: void 0 };
52
+ }
53
+ function pickWorktreeRoute(ticketNumber, candidates) {
54
+ for (const c of candidates) {
55
+ if (c.hit && (candidates.length === 1 || c.servedRepoIds.includes(c.hit.repoId ?? ""))) return { route: c.route };
56
+ }
57
+ const looked = candidates.map((c) => c.route).join(", ");
58
+ const found = candidates.filter((c) => c.hit).map((c) => `${c.route} (repo ${c.hit?.repoId ?? "none"})`);
59
+ return {
60
+ error: `no open ticket ISSUE-${ticketNumber} served by this ship \u2014 looked on ${looked}` + (found.length ? `; found it only on ${found.join(", ")}, which has no checkout for its repo` : "")
61
+ };
62
+ }
43
63
 
44
64
  // src/config.ts
45
- import { readFileSync as readFileSync5, existsSync as existsSync5 } from "fs";
46
- import { resolve, dirname as dirname3, isAbsolute, join as join6, basename as basename3 } from "path";
47
- import { homedir as homedir4, tmpdir } from "os";
65
+ import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
66
+ import { resolve, dirname as dirname3, isAbsolute, join as join7, basename as basename3 } from "path";
67
+ import { homedir as homedir5, tmpdir } from "os";
48
68
  import { parse as parse2 } from "yaml";
49
69
 
50
70
  // src/events.ts
@@ -193,10 +213,10 @@ var Emitter = class _Emitter {
193
213
  var eventFileFor = (stateDir) => join(stateDir, "events.jsonl");
194
214
 
195
215
  // src/pair-install.ts
196
- import { existsSync as existsSync3, rmSync as rmSync4, writeFileSync as writeFileSync3 } from "fs";
216
+ import { existsSync as existsSync4, rmSync as rmSync4, writeFileSync as writeFileSync3 } from "fs";
197
217
  import { spawnSync as spawnSync3 } from "child_process";
198
- import { homedir as homedir3 } from "os";
199
- import { join as join4 } from "path";
218
+ import { homedir as homedir4 } from "os";
219
+ import { join as join5 } from "path";
200
220
 
201
221
  // src/stream.ts
202
222
  function toolTarget(name, input) {
@@ -320,6 +340,74 @@ function removeClaudeMcp(o) {
320
340
  return rm.status === 0 ? `mcp: removed the ${MCP_SERVER_NAME} MCP server from Claude Code (user scope)` : `mcp: \`claude mcp remove\` failed (${rm.stderr.trim() || `exit ${rm.status}`})`;
321
341
  }
322
342
 
343
+ // src/launcher.ts
344
+ import { basename, join as join3 } from "path";
345
+ import { existsSync as existsSync2, readFileSync as readFileSync3, readdirSync } from "fs";
346
+ import { homedir as homedir2 } from "os";
347
+ function isOriLauncher(spec) {
348
+ return !!spec.launcher && basename(spec.launcher) === "ori";
349
+ }
350
+ function isOmlxLauncher(spec) {
351
+ return !!spec.launcher && basename(spec.launcher) === "omlx";
352
+ }
353
+ function defaultOmlxBin(home = homedir2()) {
354
+ return join3(home, ".omlx", "bin", "omlx");
355
+ }
356
+ function launcherExecutable(spec, home) {
357
+ if (!spec.launcher) return void 0;
358
+ if (spec.launcherBin) return spec.launcherBin;
359
+ return isOmlxLauncher(spec) ? defaultOmlxBin(home) : spec.launcher;
360
+ }
361
+ function wrapInvocation(spec, o) {
362
+ const exe = launcherExecutable(spec);
363
+ if (!exe) return { bin: spec.bin, args: o.args };
364
+ if (isOmlxLauncher(spec)) return { bin: exe, args: ["launch", spec.harness, "--model", o.model, "--", ...o.args] };
365
+ return { bin: exe, args: [spec.bin, ...o.args] };
366
+ }
367
+ function selectsCustomModelProvider(args) {
368
+ for (let i = 0; i < args.length; i++) {
369
+ const a = args[i];
370
+ const v = a === "-c" || a === "--config" ? args[i + 1] : a.startsWith("--config=") ? a.slice("--config=".length) : void 0;
371
+ const m = v && /^model_provider\s*=\s*"?([^"\s]+)"?$/.exec(v.trim());
372
+ if (m && m[1] !== "openai") return true;
373
+ }
374
+ return false;
375
+ }
376
+ function parseOmlxSettings(text) {
377
+ let raw;
378
+ try {
379
+ raw = JSON.parse(text);
380
+ } catch {
381
+ return void 0;
382
+ }
383
+ if (!raw || typeof raw !== "object") return void 0;
384
+ const { server, model } = raw;
385
+ const port = Number(server?.port);
386
+ if (!Number.isInteger(port) || port <= 0) return void 0;
387
+ const host = typeof server?.host === "string" && server.host ? server.host : "127.0.0.1";
388
+ const dirs = Array.isArray(model?.model_dirs) ? model.model_dirs.filter((d) => typeof d === "string") : [];
389
+ return { host: host === "0.0.0.0" ? "127.0.0.1" : host, port, modelDirs: dirs };
390
+ }
391
+ function interpretOmlxProbe(status2) {
392
+ if (status2 === void 0) return { up: false, detail: "not reachable" };
393
+ if (status2 === 401) return { up: true, detail: "up (key required)" };
394
+ if (status2 >= 200 && status2 < 300) return { up: true, detail: "up (no key required)" };
395
+ return { up: false, detail: `answered HTTP ${status2}` };
396
+ }
397
+ function omlxModelPresent(model, dirs) {
398
+ return dirs.some((d) => {
399
+ try {
400
+ return readdirSync(d).includes(model);
401
+ } catch {
402
+ return false;
403
+ }
404
+ });
405
+ }
406
+ function readOmlxSettings(home = homedir2()) {
407
+ const p = join3(home, ".omlx", "settings.json");
408
+ return existsSync2(p) ? parseOmlxSettings(readFileSync3(p, "utf8")) : void 0;
409
+ }
410
+
323
411
  // src/capability.ts
324
412
  function profileFor(role) {
325
413
  if (role === "triage") return { level: "read", designTools: false };
@@ -400,6 +488,9 @@ var claudeAdapter = {
400
488
  ...allowedTools(o.role),
401
489
  "--disallowedTools",
402
490
  ...DISALLOWED_TOOLS,
491
+ // oMLX denies LSP itself for prefix-cache stability, but only when the
492
+ // caller passes no --disallowedTools of its own — and crew always does.
493
+ ...isOmlxLauncher(spec) ? ["LSP"] : [],
403
494
  "--model",
404
495
  o.model,
405
496
  "--output-format",
@@ -421,7 +512,7 @@ var claudeAdapter = {
421
512
  })] : [],
422
513
  ...spec.args
423
514
  ];
424
- return spec.launcher ? { bin: spec.launcherBin ?? spec.launcher, args: [spec.bin, ...args] } : { bin: spec.bin, args };
515
+ return wrapInvocation(spec, { model: o.model, args });
425
516
  },
426
517
  runEnv(spec) {
427
518
  return {
@@ -446,12 +537,12 @@ var claudeAdapter = {
446
537
 
447
538
  // src/adapters/codex.ts
448
539
  import { spawnSync as spawnSync2 } from "child_process";
449
- import { homedir as homedir2 } from "os";
450
- import { join as join3 } from "path";
540
+ import { homedir as homedir3 } from "os";
541
+ import { join as join4 } from "path";
451
542
  import { rmSync as rmSync3, writeFileSync as writeFileSync2 } from "fs";
452
543
 
453
544
  // src/pair-files.ts
454
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync, rmSync as rmSync2 } from "fs";
545
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync, rmSync as rmSync2 } from "fs";
455
546
  import { dirname as dirname2 } from "path";
456
547
  var PAIR_BLOCK_START = "<!-- crew:pair start -->";
457
548
  var PAIR_BLOCK_END = "<!-- crew:pair end -->";
@@ -492,7 +583,7 @@ function removePairBlock(text) {
492
583
  return text.replace(new RegExp(`\\n*${BLOCK_RE.source}`), "\n").replace(/^\n+/, "").replace(/\n{3,}/g, "\n\n");
493
584
  }
494
585
  function readIfExists(path) {
495
- return existsSync2(path) ? readFileSync3(path, "utf8") : void 0;
586
+ return existsSync3(path) ? readFileSync4(path, "utf8") : void 0;
496
587
  }
497
588
  function writeFileMkdir(path, text) {
498
589
  mkdirSync2(dirname2(path), { recursive: true });
@@ -639,8 +730,8 @@ function codexTable(url) {
639
730
  return [`[mcp_servers.${SERVER}]`, `url = ${tomlString(url)}`, `bearer_token_env_var = ${tomlString(PAIR_KEY_VAR)}`];
640
731
  }
641
732
  var codexPaths = (home) => ({
642
- instructions: join3(home, ".codex", "AGENTS.md"),
643
- config: join3(home, ".codex", "config.toml")
733
+ instructions: join4(home, ".codex", "AGENTS.md"),
734
+ config: join4(home, ".codex", "config.toml")
644
735
  });
645
736
  function codexMcpState(config, url) {
646
737
  const lines = config.split("\n");
@@ -656,7 +747,7 @@ var codexPair = {
656
747
  harness: "codex",
657
748
  displayName: "Codex",
658
749
  installPair(o) {
659
- const p = codexPaths(o.home ?? homedir2());
750
+ const p = codexPaths(o.home ?? homedir3());
660
751
  const config = readIfExists(p.config) ?? "";
661
752
  const st = codexMcpState(config, o.url);
662
753
  if (st.state === "different" && !o.replace) {
@@ -673,7 +764,7 @@ var codexPair = {
673
764
  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})`;
674
765
  },
675
766
  verifyPair(o) {
676
- const p = codexPaths(o.home ?? homedir2());
767
+ const p = codexPaths(o.home ?? homedir3());
677
768
  const b = blockVerdict(p.instructions, "codex");
678
769
  if (!b.ok) return b;
679
770
  const st = codexMcpState(readIfExists(p.config) ?? "", o.url);
@@ -682,7 +773,7 @@ var codexPair = {
682
773
  return { ok: true, detail: `${p.instructions}, ${p.config}` };
683
774
  },
684
775
  removePair(o) {
685
- const p = codexPaths(o.home ?? homedir2());
776
+ const p = codexPaths(o.home ?? homedir3());
686
777
  const hadBlock = removeBlockFile(p.instructions, o.dryRun);
687
778
  const config = readIfExists(p.config);
688
779
  const st = codexMcpState(config ?? "", o.url);
@@ -758,21 +849,21 @@ var codexAdapter = {
758
849
  // Prompt on stdin.
759
850
  "-"
760
851
  ];
761
- return spec.launcher ? { bin: spec.launcher, args: [spec.bin, ...args] } : { bin: spec.bin, args };
852
+ return wrapInvocation(spec, { model: o.model, args });
762
853
  },
763
854
  runEnv: (spec) => ({ ...spec.env }),
764
- provider: () => "openai",
855
+ provider: (spec) => selectsCustomModelProvider(spec.args) ? "local" : "openai",
765
856
  unsetEnv: CODEX_BILLING_VARS_TO_UNSET,
766
857
  mapStreamLine: mapCodexLine,
767
858
  extractResult: extractCodexResult,
768
859
  extractTurnTokens: extractCodexTurnTokens,
769
860
  inspectMcp(o) {
770
861
  if (binMissing(o.bin)) return { state: "no-binary" };
771
- const st = codexMcpState(readIfExists(codexPaths(o.home ?? homedir2()).config) ?? "", o.url);
862
+ const st = codexMcpState(readIfExists(codexPaths(o.home ?? homedir3()).config) ?? "", o.url);
772
863
  return st.state === "registered" ? { state: "registered", url: o.url } : st;
773
864
  },
774
865
  ensureMcp(o) {
775
- const p = codexPaths(o.home ?? homedir2());
866
+ const p = codexPaths(o.home ?? homedir3());
776
867
  const config = readIfExists(p.config) ?? "";
777
868
  const st = codexMcpState(config, o.url);
778
869
  if (st.state === "registered") return `mcp: ${SERVER} already registered with Codex \u2192 ${o.url}`;
@@ -787,8 +878,8 @@ var codexAdapter = {
787
878
 
788
879
  // src/pair-install.ts
789
880
  var geminiPaths = (home) => ({
790
- instructions: join4(home, ".gemini", "GEMINI.md"),
791
- config: join4(home, ".gemini", "settings.json")
881
+ instructions: join5(home, ".gemini", "GEMINI.md"),
882
+ config: join5(home, ".gemini", "settings.json")
792
883
  });
793
884
  function readGeminiSettings(path) {
794
885
  const text = readIfExists(path);
@@ -809,14 +900,14 @@ function geminiMcpState(s, url) {
809
900
  return { state: "registered" };
810
901
  }
811
902
  function inspectGeminiMcp(o) {
812
- const p = geminiPaths(o.home ?? homedir3());
903
+ const p = geminiPaths(o.home ?? homedir4());
813
904
  const settings = readGeminiSettings(p.config);
814
905
  if (!settings) return { state: "different", detail: `${p.config} is not a JSON object` };
815
906
  const st = geminiMcpState(settings, o.url);
816
907
  return st.state === "registered" ? { state: "registered", url: o.url } : st;
817
908
  }
818
909
  function ensureGeminiMcp(o) {
819
- const p = geminiPaths(o.home ?? homedir3());
910
+ const p = geminiPaths(o.home ?? homedir4());
820
911
  const settings = readGeminiSettings(p.config);
821
912
  if (!settings) return `gemini: ${p.config} is not a JSON object \u2014 fix it by hand; nothing was written`;
822
913
  const st = geminiMcpState(settings, o.url);
@@ -834,7 +925,7 @@ var geminiPair = {
834
925
  harness: "gemini",
835
926
  displayName: "Gemini CLI",
836
927
  installPair(o) {
837
- const p = geminiPaths(o.home ?? homedir3());
928
+ const p = geminiPaths(o.home ?? homedir4());
838
929
  const settings = readGeminiSettings(p.config);
839
930
  if (!settings) return `pair(gemini): ${p.config} is not a JSON object \u2014 fix it by hand; nothing was written`;
840
931
  const st = geminiMcpState(settings, o.url);
@@ -853,7 +944,7 @@ var geminiPair = {
853
944
  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})`;
854
945
  },
855
946
  verifyPair(o) {
856
- const p = geminiPaths(o.home ?? homedir3());
947
+ const p = geminiPaths(o.home ?? homedir4());
857
948
  const b = blockVerdict(p.instructions, "gemini");
858
949
  if (!b.ok) return b;
859
950
  const settings = readGeminiSettings(p.config);
@@ -864,7 +955,7 @@ var geminiPair = {
864
955
  return { ok: true, detail: `${p.instructions}, ${p.config}` };
865
956
  },
866
957
  removePair(o) {
867
- const p = geminiPaths(o.home ?? homedir3());
958
+ const p = geminiPaths(o.home ?? homedir4());
868
959
  const hadBlock = removeBlockFile(p.instructions, o.dryRun);
869
960
  const settings = readGeminiSettings(p.config);
870
961
  const st = settings ? geminiMcpState(settings, o.url) : { state: "missing" };
@@ -885,11 +976,11 @@ var geminiPair = {
885
976
  };
886
977
  var VSCODE_FLAVOURS = ["Code", "Code - Insiders"];
887
978
  function vscodeUserDirs(o) {
888
- const home = o.home ?? homedir3();
979
+ const home = o.home ?? homedir4();
889
980
  const platform2 = o.platform ?? process.platform;
890
- const root = platform2 === "darwin" ? join4(home, "Library", "Application Support") : platform2 === "win32" ? process.env.APPDATA ?? join4(home, "AppData", "Roaming") : join4(home, ".config");
891
- const all = VSCODE_FLAVOURS.map((f) => join4(root, f, "User"));
892
- const present = all.filter((d) => existsSync3(d));
981
+ const root = platform2 === "darwin" ? join5(home, "Library", "Application Support") : platform2 === "win32" ? process.env.APPDATA ?? join5(home, "AppData", "Roaming") : join5(home, ".config");
982
+ const all = VSCODE_FLAVOURS.map((f) => join5(root, f, "User"));
983
+ const present = all.filter((d) => existsSync4(d));
893
984
  return present.length > 0 ? present : [all[0]];
894
985
  }
895
986
  var COPILOT_FRONTMATTER = '---\napplyTo: "**"\n---\n\n';
@@ -936,8 +1027,8 @@ var copilotPair = {
936
1027
  installPair(o) {
937
1028
  const lines = [];
938
1029
  for (const dir of vscodeUserDirs(o)) {
939
- const mcpPath = join4(dir, "mcp.json");
940
- const instrPath = join4(dir, "prompts", "crew-pair.instructions.md");
1030
+ const mcpPath = join5(dir, "mcp.json");
1031
+ const instrPath = join5(dir, "prompts", "crew-pair.instructions.md");
941
1032
  const settings = readVscodeMcp(mcpPath);
942
1033
  if (!settings) {
943
1034
  lines.push(`pair(copilot): ${mcpPath} is not a plain JSON object (comments are not supported) \u2014 fix it by hand; nothing was written there`);
@@ -965,8 +1056,8 @@ var copilotPair = {
965
1056
  },
966
1057
  verifyPair(o) {
967
1058
  const verdicts = vscodeUserDirs(o).map((dir) => {
968
- const instrPath = join4(dir, "prompts", "crew-pair.instructions.md");
969
- const mcpPath = join4(dir, "mcp.json");
1059
+ const instrPath = join5(dir, "prompts", "crew-pair.instructions.md");
1060
+ const mcpPath = join5(dir, "mcp.json");
970
1061
  const b = blockVerdict(instrPath, "copilot");
971
1062
  if (!b.ok) return b;
972
1063
  const settings = readVscodeMcp(mcpPath);
@@ -981,8 +1072,8 @@ var copilotPair = {
981
1072
  removePair(o) {
982
1073
  const lines = [];
983
1074
  for (const dir of vscodeUserDirs(o)) {
984
- const mcpPath = join4(dir, "mcp.json");
985
- const hadBlock = removeCopilotInstructions(join4(dir, "prompts", "crew-pair.instructions.md"), o.dryRun);
1075
+ const mcpPath = join5(dir, "mcp.json");
1076
+ const hadBlock = removeCopilotInstructions(join5(dir, "prompts", "crew-pair.instructions.md"), o.dryRun);
986
1077
  const settings = readVscodeMcp(mcpPath);
987
1078
  const st = settings ? copilotMcpState(settings, o.url) : { state: "missing" };
988
1079
  const hadMcp = st.state === "registered";
@@ -1040,9 +1131,6 @@ function harnessPromptNote(harness) {
1040
1131
  `;
1041
1132
  }
1042
1133
 
1043
- // src/toolchain.ts
1044
- import { basename } from "path";
1045
-
1046
1134
  // src/adapters/gemini.ts
1047
1135
  var BILLING_VARS_TO_UNSET2 = [
1048
1136
  "GEMINI_API_KEY",
@@ -1136,7 +1224,7 @@ var geminiAdapter = {
1136
1224
  ...geminiGrantArgs(o.role),
1137
1225
  ...spec.args
1138
1226
  ];
1139
- return spec.launcher ? { bin: spec.launcherBin ?? spec.launcher, args: [spec.bin, ...args] } : { bin: spec.bin, args };
1227
+ return wrapInvocation(spec, { model: o.model, args });
1140
1228
  },
1141
1229
  runEnv: (spec) => ({ ...spec.env }),
1142
1230
  provider: () => "google",
@@ -1192,13 +1280,13 @@ function resolveToolchain(ship, route, repo) {
1192
1280
  }
1193
1281
  function toolchainVendor(spec) {
1194
1282
  if (isOriLauncher(spec)) return "openrouter";
1283
+ if (isOmlxLauncher(spec)) return "local";
1195
1284
  return adapterFor(spec.harness).vendor;
1196
1285
  }
1197
- function isOriLauncher(spec) {
1198
- return !!spec.launcher && basename(spec.launcher) === "ori";
1199
- }
1200
1286
  function toolchainProvider(spec) {
1201
- return isOriLauncher(spec) ? "openrouter" : adapterFor(spec.harness).provider(spec);
1287
+ if (isOriLauncher(spec)) return "openrouter";
1288
+ if (isOmlxLauncher(spec)) return "local";
1289
+ return adapterFor(spec.harness).provider(spec);
1202
1290
  }
1203
1291
  function resolveSeat(ship, route, role, persona = {}, repo) {
1204
1292
  const warnings = [];
@@ -1226,7 +1314,7 @@ function resolveSeat(ship, route, role, persona = {}, repo) {
1226
1314
  let modelWhy = `toolchain ${chosen.name} model`;
1227
1315
  let reasoning = chosen.tiers.standard?.reasoning;
1228
1316
  const explicit = persona.model;
1229
- const explicitOk = explicit !== void 0 && (vendor === "openrouter" || adapter.ownsModel(explicit));
1317
+ const explicitOk = explicit !== void 0 && (vendor === "openrouter" || vendor === "local" || adapter.ownsModel(explicit));
1230
1318
  if (explicit !== void 0 && !explicitOk) {
1231
1319
  warnings.push(`seat ${role}: persona model "${explicit}" does not belong to vendor "${vendor}" (toolchain ${chosen.name}); ignored`);
1232
1320
  }
@@ -1284,8 +1372,8 @@ function explain(ship, required) {
1284
1372
  }
1285
1373
 
1286
1374
  // src/repo-config.ts
1287
- import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
1288
- import { join as join5, basename as basename2 } from "path";
1375
+ import { readFileSync as readFileSync5, existsSync as existsSync5 } from "fs";
1376
+ import { join as join6, basename as basename2 } from "path";
1289
1377
  import { parse } from "yaml";
1290
1378
  var SPEC_VERSION = 1;
1291
1379
  var REPO_CONFIG_FILENAMES = [".crew.yaml", ".crew.yml", ".crew.json"];
@@ -1358,8 +1446,8 @@ var MODES = ["local", "integrate", "ci_manual", "ci_auto", "external"];
1358
1446
  var PROVIDERS = ["github", "buildkite", "other", "none"];
1359
1447
  function findRepoConfig(dir) {
1360
1448
  for (const name of REPO_CONFIG_FILENAMES) {
1361
- const p = join5(dir, name);
1362
- if (existsSync4(p)) return p;
1449
+ const p = join6(dir, name);
1450
+ if (existsSync5(p)) return p;
1363
1451
  }
1364
1452
  return null;
1365
1453
  }
@@ -1589,7 +1677,7 @@ function parseRepoConfig(text, file) {
1589
1677
  function loadRepoConfig(dir) {
1590
1678
  const file = findRepoConfig(dir);
1591
1679
  if (!file) return null;
1592
- return parseRepoConfig(readFileSync4(file, "utf8"), file);
1680
+ return parseRepoConfig(readFileSync5(file, "utf8"), file);
1593
1681
  }
1594
1682
  function hookLabel(cfg, hook2) {
1595
1683
  return cfg.labels[hook2] ?? cfg.hooks[hook2] ?? hook2;
@@ -2002,16 +2090,16 @@ function mergeRouteRelease(route, override) {
2002
2090
  };
2003
2091
  }
2004
2092
  function expand(p, base) {
2005
- let out = p.startsWith("~") ? p.replace(/^~/, homedir4()) : p;
2006
- out = out.replace(/\$\{?HOME\}?/g, homedir4());
2093
+ let out = p.startsWith("~") ? p.replace(/^~/, homedir5()) : p;
2094
+ out = out.replace(/\$\{?HOME\}?/g, homedir5());
2007
2095
  return isAbsolute(out) ? out : resolve(base, out);
2008
2096
  }
2009
2097
  function resolvePromptsDir(promptSet, crewHome, base) {
2010
- if (!promptSet) return join6(crewHome, "prompts", "default");
2098
+ if (!promptSet) return join7(crewHome, "prompts", "default");
2011
2099
  if (promptSet.includes("/") || promptSet.startsWith(".") || promptSet.startsWith("~")) {
2012
2100
  return expand(promptSet, base);
2013
2101
  }
2014
- return join6(crewHome, "prompts", promptSet);
2102
+ return join7(crewHome, "prompts", promptSet);
2015
2103
  }
2016
2104
  var Missing = class {
2017
2105
  paths = [];
@@ -2035,7 +2123,7 @@ var Missing = class {
2035
2123
  };
2036
2124
  function crewVersion(crewHome) {
2037
2125
  try {
2038
- const pkg = JSON.parse(readFileSync5(join6(crewHome, "package.json"), "utf8"));
2126
+ const pkg = JSON.parse(readFileSync6(join7(crewHome, "package.json"), "utf8"));
2039
2127
  return pkg.version ?? "0.0.0";
2040
2128
  } catch {
2041
2129
  return "0.0.0";
@@ -2045,26 +2133,26 @@ function configSearchPath(crewHome) {
2045
2133
  if (process.env.CREW_CONFIG) return [resolve(process.env.CREW_CONFIG)];
2046
2134
  const xdg = process.env.XDG_CONFIG_HOME;
2047
2135
  return [
2048
- ...xdg ? [join6(xdg, "crew", "crew.yaml")] : [],
2049
- join6(homedir4(), ".config", "crew", "crew.yaml"),
2136
+ ...xdg ? [join7(xdg, "crew", "crew.yaml")] : [],
2137
+ join7(homedir5(), ".config", "crew", "crew.yaml"),
2050
2138
  resolve(crewHome, "crew.yaml")
2051
2139
  ];
2052
2140
  }
2053
2141
  function findConfigFile(crewHome) {
2054
2142
  const candidates = configSearchPath(crewHome);
2055
- return candidates.find((c) => existsSync5(c)) ?? candidates[candidates.length - 1];
2143
+ return candidates.find((c) => existsSync6(c)) ?? candidates[candidates.length - 1];
2056
2144
  }
2057
- var defaultConfigPath = () => join6(process.env.XDG_CONFIG_HOME ?? join6(homedir4(), ".config"), "crew", "crew.yaml");
2145
+ var defaultConfigPath = () => join7(process.env.XDG_CONFIG_HOME ?? join7(homedir5(), ".config"), "crew", "crew.yaml");
2058
2146
  function routeSlug(route) {
2059
2147
  return route.replace(/\//g, "-");
2060
2148
  }
2061
2149
  function resolvedPathFor(stateDir, route) {
2062
2150
  const [workspace, project] = splitRoute(route);
2063
- return join6(stateDir, "resolved", workspace, `${project}.json`);
2151
+ return join7(stateDir, "resolved", workspace, `${project}.json`);
2064
2152
  }
2065
2153
  function apiKeyPathFor(stateDir, route) {
2066
2154
  const [workspace, project] = splitRoute(route);
2067
- return join6(stateDir, "keys", workspace, `${project}.env`);
2155
+ return join7(stateDir, "keys", workspace, `${project}.env`);
2068
2156
  }
2069
2157
  function splitRoute(route) {
2070
2158
  const parts = route.split("/");
@@ -2075,9 +2163,9 @@ function splitRoute(route) {
2075
2163
  }
2076
2164
  function readResolvedFile(stateDir, route) {
2077
2165
  const path = resolvedPathFor(stateDir, route);
2078
- if (!existsSync5(path)) return void 0;
2166
+ if (!existsSync6(path)) return void 0;
2079
2167
  try {
2080
- return JSON.parse(readFileSync5(path, "utf8"));
2168
+ return JSON.parse(readFileSync6(path, "utf8"));
2081
2169
  } catch (e) {
2082
2170
  throw new ConfigError(`${path}: not valid JSON \u2014 ${e.message}`);
2083
2171
  }
@@ -2133,18 +2221,18 @@ function parseResolved(raw, m, where) {
2133
2221
  }
2134
2222
  function loadConfig(crewHome, configFile, opts = {}) {
2135
2223
  const file = configFile ?? findConfigFile(crewHome);
2136
- if (opts.text === void 0 && !existsSync5(file)) {
2224
+ if (opts.text === void 0 && !existsSync6(file)) {
2137
2225
  throw new ConfigError(
2138
2226
  `no config found. Looked in:
2139
2227
  ` + configSearchPath(crewHome).map((c) => ` - ${c}`).join("\n") + `
2140
2228
 
2141
2229
  Create the first of those:
2142
- mkdir -p ${dirname3(defaultConfigPath())} && cp ${join6(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
2230
+ mkdir -p ${dirname3(defaultConfigPath())} && cp ${join7(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
2143
2231
  );
2144
2232
  }
2145
2233
  let raw;
2146
2234
  try {
2147
- raw = parse2(opts.text ?? readFileSync5(file, "utf8"));
2235
+ raw = parse2(opts.text ?? readFileSync6(file, "utf8"));
2148
2236
  } catch (e) {
2149
2237
  throw new ConfigError(`${file}: not valid YAML \u2014 ${e.message}`);
2150
2238
  }
@@ -2222,7 +2310,7 @@ Create the first of those:
2222
2310
  useNvm: shipRaw.useNvm !== false,
2223
2311
  nvmSh: shipRaw.nvmSh,
2224
2312
  stateDir,
2225
- logFile: expand(shipRaw.logFile ?? join6(tmpdir(), "crew.log"), base),
2313
+ logFile: expand(shipRaw.logFile ?? join7(tmpdir(), "crew.log"), base),
2226
2314
  userAgent: shipRaw.userAgent ?? `Mozilla/5.0 CrewAgent/${crewVersion(crewHome)}`,
2227
2315
  maxConcurrentAgents,
2228
2316
  streamRetentionDays,
@@ -2442,8 +2530,8 @@ function repoIdForName(r, name) {
2442
2530
  }
2443
2531
  function resolveApiKey(r) {
2444
2532
  if (r.apiKey) return r.apiKey;
2445
- if (r.apiKeyFile && r.apiKeyVar && existsSync5(r.apiKeyFile)) {
2446
- for (const line of readFileSync5(r.apiKeyFile, "utf8").split("\n")) {
2533
+ if (r.apiKeyFile && r.apiKeyVar && existsSync6(r.apiKeyFile)) {
2534
+ for (const line of readFileSync6(r.apiKeyFile, "utf8").split("\n")) {
2447
2535
  const m = line.match(new RegExp(`^${r.apiKeyVar}=(.*)$`));
2448
2536
  if (m) return (m[1] ?? "").trim().replace(/^["']|["']$/g, "");
2449
2537
  }
@@ -2506,7 +2594,7 @@ function configuredMembers(rt) {
2506
2594
  return out;
2507
2595
  }
2508
2596
  function defaultRepoDir(basePath, workspace, repoName) {
2509
- return join6(basePath, workspace, repoName);
2597
+ return join7(basePath, workspace, repoName);
2510
2598
  }
2511
2599
  function reposOf(r) {
2512
2600
  const named = Object.entries(r.repos);
@@ -2530,7 +2618,7 @@ function passengerRepoTargets(r) {
2530
2618
  return reposOf(r).filter((t) => r.repoOverrides[t.name]?.hostPassengers !== false);
2531
2619
  }
2532
2620
  function passengerCheckoutDir(basePath, workspace, repoName) {
2533
- return join6(basePath, workspace, ".passenger-checkouts", repoName);
2621
+ return join7(basePath, workspace, ".passenger-checkouts", repoName);
2534
2622
  }
2535
2623
  function passengerCheckoutTargets(r) {
2536
2624
  if (!r.hostPassengers) return [];
@@ -2662,8 +2750,8 @@ function rosterMarkdown(roster, meId) {
2662
2750
  }
2663
2751
 
2664
2752
  // src/state.ts
2665
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync3, rmSync as rmSync5, statSync as statSync2 } from "fs";
2666
- import { join as join7 } from "path";
2753
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync7, mkdirSync as mkdirSync3, rmSync as rmSync5, statSync as statSync2 } from "fs";
2754
+ import { join as join8 } from "path";
2667
2755
  var EPOCH = "1970-01-01T00:00:00.000Z";
2668
2756
  var State = class _State {
2669
2757
  dir;
@@ -2672,7 +2760,7 @@ var State = class _State {
2672
2760
  mkdirSync3(dir, { recursive: true });
2673
2761
  }
2674
2762
  path(...parts) {
2675
- return join7(this.dir, ...parts);
2763
+ return join8(this.dir, ...parts);
2676
2764
  }
2677
2765
  /**
2678
2766
  * The timestamp every "new since last poll" check is measured against.
@@ -2683,7 +2771,7 @@ var State = class _State {
2683
2771
  */
2684
2772
  watermark() {
2685
2773
  try {
2686
- const v = readFileSync6(this.path(".poll-watermark"), "utf8").trim();
2774
+ const v = readFileSync7(this.path(".poll-watermark"), "utf8").trim();
2687
2775
  return v || EPOCH;
2688
2776
  } catch {
2689
2777
  return EPOCH;
@@ -2708,10 +2796,10 @@ var State = class _State {
2708
2796
  }
2709
2797
  /** Whole-crew pause, and the per-role sentinels one level down. */
2710
2798
  isPaused() {
2711
- return existsSync6(this.path(".crew-paused"));
2799
+ return existsSync7(this.path(".crew-paused"));
2712
2800
  }
2713
2801
  isRolePaused(role) {
2714
- return existsSync6(this.path(`.role-paused-${role}`));
2802
+ return existsSync7(this.path(`.role-paused-${role}`));
2715
2803
  }
2716
2804
  pausedRoles(roles) {
2717
2805
  return new Set(roles.filter((r) => this.isRolePaused(r)));
@@ -2728,7 +2816,7 @@ var State = class _State {
2728
2816
  * while agents keep working.
2729
2817
  */
2730
2818
  isReleasePaused() {
2731
- return existsSync6(this.path(".release-paused"));
2819
+ return existsSync7(this.path(".release-paused"));
2732
2820
  }
2733
2821
  setReleasePaused(paused) {
2734
2822
  if (paused) writeFileSync4(this.path(".release-paused"), "");
@@ -2769,7 +2857,7 @@ var State = class _State {
2769
2857
  const name = _State.safe(rawName);
2770
2858
  const file = this.path(`.${name}.lock`);
2771
2859
  try {
2772
- const existing = Number.parseInt(readFileSync6(file, "utf8").trim(), 10);
2860
+ const existing = Number.parseInt(readFileSync7(file, "utf8").trim(), 10);
2773
2861
  if (Number.isFinite(existing) && existing > 0) {
2774
2862
  try {
2775
2863
  process.kill(existing, 0);
@@ -2863,7 +2951,7 @@ var State = class _State {
2863
2951
  const heldReported = `.release-held-${suffix}`;
2864
2952
  const read = (f) => {
2865
2953
  try {
2866
- return readFileSync6(this.path(f), "utf8").trim() || null;
2954
+ return readFileSync7(this.path(f), "utf8").trim() || null;
2867
2955
  } catch {
2868
2956
  return null;
2869
2957
  }
@@ -2913,7 +3001,7 @@ var State = class _State {
2913
3001
  /** Whether the board was already told about this head's red gate. */
2914
3002
  heldReported: () => {
2915
3003
  try {
2916
- return readFileSync6(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
3004
+ return readFileSync7(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
2917
3005
  } catch {
2918
3006
  return null;
2919
3007
  }
@@ -2950,7 +3038,7 @@ var State = class _State {
2950
3038
  const ticketStreakFile = `.fairness-ticket-streak-${suffix}`;
2951
3039
  const read = (f) => {
2952
3040
  try {
2953
- return readFileSync6(this.path(f), "utf8").trim() || null;
3041
+ return readFileSync7(this.path(f), "utf8").trim() || null;
2954
3042
  } catch {
2955
3043
  return null;
2956
3044
  }
@@ -3015,7 +3103,7 @@ var State = class _State {
3015
3103
  return {
3016
3104
  previous: () => {
3017
3105
  try {
3018
- const parsed = JSON.parse(readFileSync6(this.path(file), "utf8"));
3106
+ const parsed = JSON.parse(readFileSync7(this.path(file), "utf8"));
3019
3107
  return parsed && typeof parsed === "object" ? parsed : {};
3020
3108
  } catch {
3021
3109
  return {};
@@ -3036,7 +3124,7 @@ var State = class _State {
3036
3124
  return {
3037
3125
  previous: () => {
3038
3126
  try {
3039
- const parsed = JSON.parse(readFileSync6(this.path(file), "utf8"));
3127
+ const parsed = JSON.parse(readFileSync7(this.path(file), "utf8"));
3040
3128
  return Array.isArray(parsed) ? parsed : [];
3041
3129
  } catch {
3042
3130
  return [];
@@ -3069,7 +3157,7 @@ var State = class _State {
3069
3157
  }
3070
3158
  readInt(file) {
3071
3159
  try {
3072
- return Number.parseInt(readFileSync6(this.path(file), "utf8").trim(), 10) || 0;
3160
+ return Number.parseInt(readFileSync7(this.path(file), "utf8").trim(), 10) || 0;
3073
3161
  } catch {
3074
3162
  return 0;
3075
3163
  }
@@ -3103,7 +3191,8 @@ var DEFAULT_CONTRACT = {
3103
3191
  ship: "ship_id",
3104
3192
  heldBy: "held_by_ship_id",
3105
3193
  verifiedSha: "verified_sha",
3106
- prRef: "pr_ref"
3194
+ prRef: "pr_ref",
3195
+ position: "position"
3107
3196
  },
3108
3197
  comments: {
3109
3198
  parent: "ticket_id",
@@ -3373,9 +3462,9 @@ function describeRelease(o, route) {
3373
3462
  }
3374
3463
 
3375
3464
  // src/install.ts
3376
- import { dirname as dirname4, join as join8, extname } from "path";
3377
- import { homedir as homedir5 } from "os";
3378
- import { accessSync, constants, existsSync as existsSync7, mkdirSync as mkdirSync4, readdirSync, readFileSync as readFileSync7, statSync as statSync3, writeFileSync as writeFileSync5, unlinkSync, realpathSync } from "fs";
3465
+ import { dirname as dirname4, join as join9, extname } from "path";
3466
+ import { homedir as homedir6 } from "os";
3467
+ import { accessSync, constants, existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync8, statSync as statSync3, writeFileSync as writeFileSync5, unlinkSync, realpathSync } from "fs";
3379
3468
  import { spawnSync as spawnSync4 } from "child_process";
3380
3469
  import { createHash } from "crypto";
3381
3470
 
@@ -3387,7 +3476,7 @@ function isCompiledBinary(moduleUrl) {
3387
3476
  // src/install.ts
3388
3477
  function crewInvocation(crewHome) {
3389
3478
  if (isCompiledBinary(import.meta.url)) return [process.execPath];
3390
- return [process.execPath, join8(crewHome, "bin", "crew")];
3479
+ return [process.execPath, join9(crewHome, "bin", "crew")];
3391
3480
  }
3392
3481
  var INTERVAL_SECONDS = 120;
3393
3482
  function labelFor(crewHome, job) {
@@ -3425,7 +3514,7 @@ function pathFor(ship) {
3425
3514
  return parts.join(":");
3426
3515
  }
3427
3516
  function findOnPath(bin, searchPath) {
3428
- const candidates = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) => join8(dir, bin));
3517
+ const candidates = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) => join9(dir, bin));
3429
3518
  for (const candidate of candidates) {
3430
3519
  try {
3431
3520
  accessSync(candidate, constants.X_OK);
@@ -3435,7 +3524,7 @@ function findOnPath(bin, searchPath) {
3435
3524
  }
3436
3525
  return void 0;
3437
3526
  }
3438
- var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin", join8(homedir5(), ".docker", "bin")].join(":");
3527
+ var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin", join9(homedir6(), ".docker", "bin")].join(":");
3439
3528
  function dockerPathProblem(ship, shellPath = process.env.PATH ?? "") {
3440
3529
  const schedulerPath = pathFor(ship);
3441
3530
  if (findOnPath("docker", schedulerPath)) return void 0;
@@ -3447,7 +3536,7 @@ function xmlEscape(s) {
3447
3536
  }
3448
3537
  function planLaunchd(ship, crewHome, job) {
3449
3538
  const label = labelFor(crewHome, job);
3450
- const unitPath = join8(homedir5(), "Library", "LaunchAgents", `${label}.plist`);
3539
+ const unitPath = join9(homedir6(), "Library", "LaunchAgents", `${label}.plist`);
3451
3540
  const crewLog = ship.logFile;
3452
3541
  const schedulerLog = schedulerLogFor(crewLog, job);
3453
3542
  const persistent = job === "run";
@@ -3499,8 +3588,8 @@ ${scheduleKeys}
3499
3588
  }
3500
3589
  function planSystemd(ship, crewHome, job) {
3501
3590
  const label = labelFor(crewHome, job);
3502
- const unitDir = join8(homedir5(), ".config", "systemd", "user");
3503
- const servicePath = join8(unitDir, `${label}.service`);
3591
+ const unitDir = join9(homedir6(), ".config", "systemd", "user");
3592
+ const servicePath = join9(unitDir, `${label}.service`);
3504
3593
  const crewLog = ship.logFile;
3505
3594
  const schedulerLog = schedulerLogFor(crewLog, job);
3506
3595
  const description = job === "release" ? "Tablation crew \u2014 release" : job === "passengers" ? "Tablation crew \u2014 Host Passengers sync" : "Tablation crew \u2014 persistent supervisor loop";
@@ -3537,7 +3626,7 @@ WantedBy=default.target
3537
3626
  schedulerLog
3538
3627
  };
3539
3628
  }
3540
- const timerPath = join8(unitDir, `${label}.timer`);
3629
+ const timerPath = join9(unitDir, `${label}.timer`);
3541
3630
  const service = `# Written by \`crew install\` \u2014 re-run it rather than hand-editing this file.
3542
3631
  [Unit]
3543
3632
  Description=${description}
@@ -3604,7 +3693,7 @@ function planInstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
3604
3693
  function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
3605
3694
  const label = labelFor(crewHome, job);
3606
3695
  if (hostShip === "macos") {
3607
- const unitPath = join8(homedir5(), "Library", "LaunchAgents", `${label}.plist`);
3696
+ const unitPath = join9(homedir6(), "Library", "LaunchAgents", `${label}.plist`);
3608
3697
  return {
3609
3698
  mechanism: "launchd",
3610
3699
  unitPaths: [unitPath],
@@ -3614,18 +3703,18 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
3614
3703
  };
3615
3704
  }
3616
3705
  if (hostShip === "linux" && hasSystemd) {
3617
- const unitDir = join8(homedir5(), ".config", "systemd", "user");
3706
+ const unitDir = join9(homedir6(), ".config", "systemd", "user");
3618
3707
  if (job === "run") {
3619
3708
  return {
3620
3709
  mechanism: "systemd",
3621
- unitPaths: [join8(unitDir, `${label}.service`)],
3710
+ unitPaths: [join9(unitDir, `${label}.service`)],
3622
3711
  crontabLine: null,
3623
3712
  unloadCommands: [["systemctl", "--user", "disable", "--now", `${label}.service`]]
3624
3713
  };
3625
3714
  }
3626
3715
  return {
3627
3716
  mechanism: "systemd",
3628
- unitPaths: [join8(unitDir, `${label}.service`), join8(unitDir, `${label}.timer`)],
3717
+ unitPaths: [join9(unitDir, `${label}.service`), join9(unitDir, `${label}.timer`)],
3629
3718
  crontabLine: null,
3630
3719
  unloadCommands: [["systemctl", "--user", "disable", "--now", `${label}.timer`]]
3631
3720
  };
@@ -3643,7 +3732,7 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
3643
3732
  function planDaemonControl(crewHome, hostShip, hasSystemd) {
3644
3733
  const label = labelFor(crewHome, "run");
3645
3734
  if (hostShip === "macos") {
3646
- const unitPath = join8(homedir5(), "Library", "LaunchAgents", `${label}.plist`);
3735
+ const unitPath = join9(homedir6(), "Library", "LaunchAgents", `${label}.plist`);
3647
3736
  return {
3648
3737
  mechanism: "launchd",
3649
3738
  startCommand: ["launchctl", "load", unitPath],
@@ -3738,7 +3827,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
3738
3827
  }
3739
3828
  }
3740
3829
  for (const path of plan.unitPaths) {
3741
- if (!existsSync7(path)) continue;
3830
+ if (!existsSync8(path)) continue;
3742
3831
  if (dryRun) {
3743
3832
  log.emit(`would remove ${path}`);
3744
3833
  continue;
@@ -3757,7 +3846,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
3757
3846
  }
3758
3847
  var CREW_LAUNCHD_PREFIX = "com.tablation.crew";
3759
3848
  function launchAgentsDir() {
3760
- return join8(homedir5(), "Library", "LaunchAgents");
3849
+ return join9(homedir6(), "Library", "LaunchAgents");
3761
3850
  }
3762
3851
  function xmlUnescape(s) {
3763
3852
  return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
@@ -3774,25 +3863,25 @@ function loadedCrewLabels() {
3774
3863
  return [...parseLaunchctlList(list.stdout ?? "").keys()].filter((l) => l.startsWith(CREW_LAUNCHD_PREFIX));
3775
3864
  }
3776
3865
  function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
3777
- const files = existsSync7(dir) ? readdirSync(dir) : [];
3866
+ const files = existsSync8(dir) ? readdirSync2(dir) : [];
3778
3867
  const units = files.filter((f) => f.startsWith(CREW_LAUNCHD_PREFIX) && f.endsWith(".plist")).sort().map((f) => {
3779
- const plistPath = join8(dir, f);
3868
+ const plistPath = join9(dir, f);
3780
3869
  let program = null;
3781
3870
  try {
3782
- program = programFromPlist(readFileSync7(plistPath, "utf8"));
3871
+ program = programFromPlist(readFileSync8(plistPath, "utf8"));
3783
3872
  } catch {
3784
3873
  }
3785
3874
  return {
3786
3875
  label: f.slice(0, -".plist".length),
3787
3876
  plistPath,
3788
3877
  program,
3789
- programExists: program !== null && existsSync7(program)
3878
+ programExists: program !== null && existsSync8(program)
3790
3879
  };
3791
3880
  });
3792
3881
  const seen = new Set(units.map((u) => u.label));
3793
3882
  for (const label of [...loaded].sort()) {
3794
3883
  if (!label.startsWith(CREW_LAUNCHD_PREFIX) || seen.has(label)) continue;
3795
- units.push({ label, plistPath: join8(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
3884
+ units.push({ label, plistPath: join9(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
3796
3885
  }
3797
3886
  return units;
3798
3887
  }
@@ -3805,9 +3894,9 @@ function removeLaunchdUnit(unit, dryRun, log) {
3805
3894
  log.emit(`would unload and remove ${unit.label}`);
3806
3895
  return;
3807
3896
  }
3808
- if (existsSync7(unit.plistPath)) spawnSync4("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
3897
+ if (existsSync8(unit.plistPath)) spawnSync4("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
3809
3898
  spawnSync4("launchctl", ["remove", unit.label], { encoding: "utf8" });
3810
- if (existsSync7(unit.plistPath)) unlinkSync(unit.plistPath);
3899
+ if (existsSync8(unit.plistPath)) unlinkSync(unit.plistPath);
3811
3900
  log.emit(`removed ${unit.label} (${unit.plistPath})`);
3812
3901
  }
3813
3902
  function clearForeignLaunchdUnits(crewHome, replace, dryRun, log, dir, loaded) {
@@ -3890,7 +3979,7 @@ import { statfsSync } from "fs";
3890
3979
  // src/git.ts
3891
3980
  import { execFileSync } from "child_process";
3892
3981
  import {
3893
- existsSync as existsSync8,
3982
+ existsSync as existsSync9,
3894
3983
  mkdirSync as mkdirSync5,
3895
3984
  openSync as openSync2,
3896
3985
  closeSync as closeSync2,
@@ -4262,7 +4351,7 @@ function cloneUrlFor(remote) {
4262
4351
  return `git@github.com:${remote}.git`;
4263
4352
  }
4264
4353
  function ensureRepoCheckout(dir, remote) {
4265
- if (existsSync8(`${dir}/.git`)) return false;
4354
+ if (existsSync9(`${dir}/.git`)) return false;
4266
4355
  if (!remote) {
4267
4356
  throw new GitError(
4268
4357
  `${dir} does not exist and this repo has no discovered remote to clone from \u2014 re-run \`crew connect\`, or check out the repository yourself and point \`repos:\` at it`
@@ -5391,20 +5480,25 @@ function issueNumber(t) {
5391
5480
  function epicRank(t) {
5392
5481
  return t.epicInProgress ? 0 : 1;
5393
5482
  }
5483
+ function positionRank(t) {
5484
+ const p = t.position;
5485
+ return typeof p === "number" && Number.isFinite(p) ? p : Number.POSITIVE_INFINITY;
5486
+ }
5394
5487
  function rank(t, c = DEFAULT_CONTRACT) {
5395
- return [effectivePriority(t, c), severityRank(t, c), epicRank(t), issueNumber(t)];
5488
+ return [effectivePriority(t, c), positionRank(t), severityRank(t, c), epicRank(t), issueNumber(t)];
5396
5489
  }
5397
5490
  function compareRank(a, b, c = DEFAULT_CONTRACT) {
5398
5491
  const ra = rank(a, c);
5399
5492
  const rb = rank(b, c);
5400
5493
  for (let i = 0; i < ra.length; i++) {
5401
- const d = ra[i] - rb[i];
5402
- if (d !== 0) return d;
5494
+ const x = ra[i];
5495
+ const y = rb[i];
5496
+ if (x !== y) return x < y ? -1 : 1;
5403
5497
  }
5404
5498
  return 0;
5405
5499
  }
5406
5500
  function rankScalar(t, c = DEFAULT_CONTRACT) {
5407
- const [p, s, e, n] = rank(t, c);
5501
+ const [p, , s, e, n] = rank(t, c);
5408
5502
  return p * 1e9 + s * 1e7 + e * 1e6 + n;
5409
5503
  }
5410
5504
  var NOTHING_ACTIONABLE = 999999999999;
@@ -5611,7 +5705,7 @@ function claimStatuses(role, c) {
5611
5705
  }
5612
5706
  function rankedCandidates(role, i) {
5613
5707
  const c = i.contract ?? DEFAULT_CONTRACT;
5614
- return [...roleCandidates(role, i)].sort((a, b) => rankScalar(a, c) - rankScalar(b, c));
5708
+ return [...roleCandidates(role, i)].sort((a, b) => compareRank(a, b, c));
5615
5709
  }
5616
5710
  function roleTopRank(role, i) {
5617
5711
  if (role !== "qa" && role !== "triage" && !i.seats[role]) return NOTHING_ACTIONABLE;
@@ -5624,7 +5718,7 @@ function actionableSummary(i, pending) {
5624
5718
  const c = i.contract ?? DEFAULT_CONTRACT;
5625
5719
  const all = pending.flatMap((role) => roleCandidates(role, i));
5626
5720
  if (all.length === 0) return { count: 0 };
5627
- const top = all.reduce((best, t) => rankScalar(t, c) < rankScalar(best, c) ? t : best);
5721
+ const top = all.reduce((best, t) => compareRank(t, best, c) < 0 ? t : best);
5628
5722
  return { count: all.length, top };
5629
5723
  }
5630
5724
  var FIXED_RANK = { qa: -2, triage: -1 };
@@ -5850,6 +5944,7 @@ function blockers(t, i) {
5850
5944
  function modeCell(t, i) {
5851
5945
  return i.workModes && i.workModes.modes.size > 0 ? modeOf(t, i.workModes) : "\u2014";
5852
5946
  }
5947
+ var effCell = (t) => `p${effectivePriority(t)}${positionRank(t) === Number.POSITIVE_INFINITY ? "" : ` #${positionRank(t)}`}`;
5853
5948
  var sorted = (ts) => [...ts].sort(compareRank);
5854
5949
  var stream = (segments) => `${segments.join("\n")}
5855
5950
  `;
@@ -5868,7 +5963,7 @@ function buildingDigest(i) {
5868
5963
  const worktree = i.worktreeFor ? i.worktreeFor(t) : "";
5869
5964
  const dir = i.dirFor ? i.dirFor(t) : null;
5870
5965
  const repo = i.dirFor ? dir ?? "**NO CHECKOUT**" : "";
5871
- return `| ${displayKey2(t)} | ${repo} | ${branch} | ${worktree} | ${modeCell(t, i)} | ${t.status} | ${who(t, i)} | ${filedBy(t, i)} | ${t.severity ?? "\u2014"} | ${t.priority ?? "\u2014"} | p${effectivePriority(t)} | ${t.updated_at.slice(0, 16)}Z | ${lastComment(i, t.id)} | ${n > 0 ? `**${n} new**` : "\u2014"} |`;
5966
+ return `| ${displayKey2(t)} | ${repo} | ${branch} | ${worktree} | ${modeCell(t, i)} | ${t.status} | ${who(t, i)} | ${filedBy(t, i)} | ${t.severity ?? "\u2014"} | ${t.priority ?? "\u2014"} | ${effCell(t)} | ${t.updated_at.slice(0, 16)}Z | ${lastComment(i, t.id)} | ${n > 0 ? `**${n} new**` : "\u2014"} |`;
5872
5967
  };
5873
5968
  const blockedRow = (t) => `| ${displayKey2(t)} | ${t.status} | ${who(t, i)} | p${effectivePriority(t)} | ${blockers(t, i)} |`;
5874
5969
  const blockedTable = (ts) => ts.length === 0 ? "\n_None._\n" : `
@@ -5929,7 +6024,7 @@ function qaDigest(i) {
5929
6024
  const n = newFromOthers(i, t.id);
5930
6025
  const dir = i.dirFor ? i.dirFor(t) : null;
5931
6026
  const repo = i.dirFor ? dir ?? "**NO CHECKOUT**" : "";
5932
- return `| ${displayKey2(t)} | ${repo} | ${modeCell(t, i)} | ${t.status} | ${builtBy(t)} | ${who(t, i)} | ${filedBy(t, i)} | ${t.severity ?? "\u2014"} | ${t.priority ?? "\u2014"} | p${effectivePriority(t)} | ${branchCell(t)} | ${worktreeCell(t)} | ${t.updated_at.slice(0, 16)}Z | ${lastComment(i, t.id)} | ${n > 0 ? `**${n} new**` : "\u2014"} |`;
6027
+ return `| ${displayKey2(t)} | ${repo} | ${modeCell(t, i)} | ${t.status} | ${builtBy(t)} | ${who(t, i)} | ${filedBy(t, i)} | ${t.severity ?? "\u2014"} | ${t.priority ?? "\u2014"} | ${effCell(t)} | ${branchCell(t)} | ${worktreeCell(t)} | ${t.updated_at.slice(0, 16)}Z | ${lastComment(i, t.id)} | ${n > 0 ? `**${n} new**` : "\u2014"} |`;
5933
6028
  };
5934
6029
  const waiting = i.tickets.filter((t) => (t.status === "qa" || t.status === "fixed") && i.blocked.has(t.id));
5935
6030
  const takeable = (status2) => i.tickets.filter((t) => t.status === status2 && !i.blocked.has(t.id));
@@ -6011,7 +6106,7 @@ function materializeBranchForTicket(dir, cfg, t, role) {
6011
6106
 
6012
6107
  // src/poll.ts
6013
6108
  import { writeFileSync as writeFileSync6 } from "fs";
6014
- import { join as join9 } from "path";
6109
+ import { join as join10 } from "path";
6015
6110
 
6016
6111
  // src/stalled.ts
6017
6112
  var STALLED_AFTER_CYCLES = 30;
@@ -6124,6 +6219,7 @@ async function decideCycle(o) {
6124
6219
  data: { tickets: tickets.length, comments: comments.length }
6125
6220
  });
6126
6221
  const epicColumn = tracker.contract.columns.epic;
6222
+ const positionColumn = tracker.contract.columns.position;
6127
6223
  const epicBuilding = tracker.contract.epics.statuses.building;
6128
6224
  const epicStatusColumn = tracker.contract.epics.statusColumn;
6129
6225
  const epicStatusById = new Map(epicRows.map((e) => [e.id, e[epicStatusColumn] ?? null]));
@@ -6143,6 +6239,9 @@ async function decideCycle(o) {
6143
6239
  for (const t of tickets) {
6144
6240
  const epicId = t[epicColumn];
6145
6241
  t.epicInProgress = typeof epicId === "string" && epicStatusById.get(epicId) === epicBuilding;
6242
+ const pos = t[positionColumn];
6243
+ const n = typeof pos === "string" && pos.trim() !== "" ? Number(pos) : pos;
6244
+ t.position = typeof n === "number" && Number.isFinite(n) ? n : null;
6146
6245
  }
6147
6246
  const extra = await tracker.ticketsByIds(missingBlockerIds(tickets));
6148
6247
  const info = blockerInfoMap(tickets, extra);
@@ -6283,7 +6382,7 @@ async function decideCycle(o) {
6283
6382
  }
6284
6383
  return decision;
6285
6384
  }
6286
- var digestPath = (stateDir, route, role) => join9(stateDir, `digest-${route}-${role}.md`);
6385
+ var digestPath = (stateDir, route, role) => join10(stateDir, `digest-${route}-${role}.md`);
6287
6386
  function writeDigest(o, d, role, stateDir) {
6288
6387
  const me = o.route.resolved?.seats[role];
6289
6388
  if (!me) return false;
@@ -6653,9 +6752,9 @@ var alreadyReported = (ticket, g, comments) => comments.some((c) => c.ticket_id
6653
6752
 
6654
6753
  // src/agent.ts
6655
6754
  import { spawn as spawn2 } from "child_process";
6656
- import { readFileSync as readFileSync10, existsSync as existsSync11, statSync as statSync5, mkdirSync as mkdirSync6, writeFileSync as writeFileSync7, createWriteStream } from "fs";
6755
+ import { readFileSync as readFileSync11, existsSync as existsSync12, statSync as statSync5, mkdirSync as mkdirSync6, writeFileSync as writeFileSync7, createWriteStream } from "fs";
6657
6756
  import { finished } from "stream/promises";
6658
- import { join as join12, dirname as dirname6 } from "path";
6757
+ import { join as join13, dirname as dirname6 } from "path";
6659
6758
  import { createHash as createHash2 } from "crypto";
6660
6759
 
6661
6760
  // src/environment.ts
@@ -7044,14 +7143,14 @@ function expandSkillMarkers(text, lookup, opts = {}) {
7044
7143
  }
7045
7144
 
7046
7145
  // src/skills.ts
7047
- import { readFileSync as readFileSync9, readdirSync as readdirSync2, existsSync as existsSync10 } from "fs";
7048
- import { join as join11 } from "path";
7146
+ import { readFileSync as readFileSync10, readdirSync as readdirSync3, existsSync as existsSync11 } from "fs";
7147
+ import { join as join12 } from "path";
7049
7148
  import { parse as parseYaml } from "yaml";
7050
7149
  import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError7, TablationApiError as TablationApiError2 } from "@tablation/client";
7051
7150
 
7052
7151
  // src/agents.ts
7053
- import { readFileSync as readFileSync8, existsSync as existsSync9 } from "fs";
7054
- import { join as join10 } from "path";
7152
+ import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
7153
+ import { join as join11 } from "path";
7055
7154
  import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError6, TablationApiError } from "@tablation/client";
7056
7155
  var PERSONA_NAME = {
7057
7156
  dev: "Developer",
@@ -7065,12 +7164,12 @@ var CREW_AGENT_COLUMN = "agent_id";
7065
7164
  var AgentsSyncError = class extends Error {
7066
7165
  };
7067
7166
  function personaDefaultPrompt(promptsDir, role) {
7068
- const briefPath = join10(promptsDir, "personas", `lane-${role}.md`);
7069
- if (!existsSync9(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
7070
- if (SELF_CONTAINED_ROLES.has(role)) return readFileSync8(briefPath, "utf8");
7071
- const commonPath = join10(promptsDir, "personas", "common.md");
7072
- if (!existsSync9(commonPath)) return readFileSync8(briefPath, "utf8");
7073
- return `${readFileSync8(commonPath, "utf8")}${readFileSync8(briefPath, "utf8")}`;
7167
+ const briefPath = join11(promptsDir, "personas", `lane-${role}.md`);
7168
+ if (!existsSync10(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
7169
+ if (SELF_CONTAINED_ROLES.has(role)) return readFileSync9(briefPath, "utf8");
7170
+ const commonPath = join11(promptsDir, "personas", "common.md");
7171
+ if (!existsSync10(commonPath)) return readFileSync9(briefPath, "utf8");
7172
+ return `${readFileSync9(commonPath, "utf8")}${readFileSync9(briefPath, "utf8")}`;
7074
7173
  }
7075
7174
  async function resolveAgentId(route, role, opts) {
7076
7175
  const cached = route.resolved?.agentPersonas?.[role]?.agentId;
@@ -7394,7 +7493,7 @@ function describeCrewLink(o) {
7394
7493
  var SkillSyncError = class extends Error {
7395
7494
  };
7396
7495
  function parseSkillFile(path) {
7397
- const text = readFileSync9(path, "utf8");
7496
+ const text = readFileSync10(path, "utf8");
7398
7497
  const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text);
7399
7498
  if (!match) throw new SkillSyncError(`${path}: missing YAML frontmatter (expected a leading --- block)`);
7400
7499
  const frontmatter = match[1];
@@ -7407,9 +7506,9 @@ function parseSkillFile(path) {
7407
7506
  return { name: meta.name, description: meta.description, prompt: body, file: path };
7408
7507
  }
7409
7508
  function listLocalSkills(promptsDir) {
7410
- const dir = join11(promptsDir, "skills");
7411
- if (!existsSync10(dir)) return [];
7412
- return readdirSync2(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join11(dir, f)));
7509
+ const dir = join12(promptsDir, "skills");
7510
+ if (!existsSync11(dir)) return [];
7511
+ return readdirSync3(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join12(dir, f)));
7413
7512
  }
7414
7513
  async function syncSkills(route, opts) {
7415
7514
  if (!route.resolved) {
@@ -7601,23 +7700,23 @@ var AgentError = class extends Error {
7601
7700
  var AgentSpawnError = class extends AgentError {
7602
7701
  };
7603
7702
  function planAgentRun(o) {
7604
- const briefPath = join12(o.route.promptsDir, "personas", `lane-${o.role}.md`);
7605
- if (!existsSync11(briefPath)) {
7703
+ const briefPath = join13(o.route.promptsDir, "personas", `lane-${o.role}.md`);
7704
+ if (!existsSync12(briefPath)) {
7606
7705
  throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
7607
7706
  }
7608
- const commonPath = join12(o.route.promptsDir, "personas", "common.md");
7609
- const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync11(commonPath);
7707
+ const commonPath = join13(o.route.promptsDir, "personas", "common.md");
7708
+ const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync12(commonPath);
7610
7709
  const now = o.now ?? (() => Date.now());
7611
7710
  const routeLabel = routeSlug(o.route.route);
7612
- const digestPath2 = join12(o.stateDir, `digest-${routeLabel}-${o.role}.md`);
7711
+ const digestPath2 = join13(o.stateDir, `digest-${routeLabel}-${o.role}.md`);
7613
7712
  let digest;
7614
7713
  let ageSeconds;
7615
- if (existsSync11(digestPath2)) {
7714
+ if (existsSync12(digestPath2)) {
7616
7715
  ageSeconds = Math.floor((now() - statSync5(digestPath2).mtimeMs) / 1e3);
7617
- if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync10(digestPath2, "utf8");
7716
+ if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync11(digestPath2, "utf8");
7618
7717
  }
7619
- const common = usesCommon ? readFileSync10(commonPath, "utf8") : "";
7620
- const brief = readFileSync10(briefPath, "utf8");
7718
+ const common = usesCommon ? readFileSync11(commonPath, "utf8") : "";
7719
+ const brief = readFileSync11(briefPath, "utf8");
7621
7720
  const effectiveCommon = o.divergedPrompt ? "" : common;
7622
7721
  const effectiveBrief = o.divergedPrompt ?? brief;
7623
7722
  const localSkills = new Map(listLocalSkills(o.route.promptsDir).map((s) => [s.name, s.prompt]));
@@ -7639,7 +7738,7 @@ function planAgentRun(o) {
7639
7738
  digest
7640
7739
  });
7641
7740
  const promptSha = createHash2("sha256").update(expanded.text).digest("hex");
7642
- const streamsDir = join12(o.stateDir, "streams");
7741
+ const streamsDir = join13(o.stateDir, "streams");
7643
7742
  const base = `${routeLabel}-${o.role}-${o.cycle}`;
7644
7743
  const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
7645
7744
  const { toolchain, model } = seat;
@@ -7695,8 +7794,8 @@ function planAgentRun(o) {
7695
7794
  digestAttached: digest !== void 0,
7696
7795
  digestAgeSeconds: ageSeconds,
7697
7796
  model,
7698
- streamPath: join12(streamsDir, `${base}.jsonl`),
7699
- eventsPath: join12(streamsDir, `${base}.events.jsonl`),
7797
+ streamPath: join13(streamsDir, `${base}.jsonl`),
7798
+ eventsPath: join13(streamsDir, `${base}.events.jsonl`),
7700
7799
  ticket: o.ticket,
7701
7800
  agentLog,
7702
7801
  promptVersion: o.route.resolved?.agentPersonas?.[o.role]?.historyId,
@@ -7706,7 +7805,7 @@ function planAgentRun(o) {
7706
7805
  function describePlan(p) {
7707
7806
  return [
7708
7807
  `role: ${p.role}`,
7709
- ...p.resolution ? [`resolved: toolchain ${p.toolchain} \u2190 ${p.resolution.toolchain}; model ${p.model} \u2190 ${p.resolution.model}`] : [],
7808
+ ...p.resolution ? [`resolved: toolchain ${p.toolchain} \u2190 ${p.resolution.toolchain}; model ${p.model} \u2190 ${p.resolution.model}${p.provider ? `; provider ${p.provider}` : ""}`] : [],
7710
7809
  ...(p.warnings ?? []).map((w) => `warning: ${w}`),
7711
7810
  // Only for a non-default toolchain, so a plain Claude run's output is unchanged.
7712
7811
  ...p.toolchain && p.toolchain !== "claude" ? [`toolchain: ${p.toolchain} (${p.harness ?? "claude"})`] : [],
@@ -7902,13 +8001,13 @@ function spawnAgent(plan, emit) {
7902
8001
  }
7903
8002
 
7904
8003
  // src/run-worktree.ts
7905
- import { existsSync as existsSync13 } from "fs";
7906
- import { dirname as dirname7, join as join14 } from "path";
8004
+ import { existsSync as existsSync14 } from "fs";
8005
+ import { dirname as dirname7, join as join15 } from "path";
7907
8006
 
7908
8007
  // src/ports.ts
7909
8008
  import { execFileSync as execFileSync2 } from "child_process";
7910
- import { existsSync as existsSync12 } from "fs";
7911
- import { join as join13 } from "path";
8009
+ import { existsSync as existsSync13 } from "fs";
8010
+ import { join as join14 } from "path";
7912
8011
  var run = (cmd, args) => {
7913
8012
  try {
7914
8013
  return execFileSync2(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
@@ -7953,7 +8052,7 @@ function ticketForPort(port, s = DEFAULT_PORTS) {
7953
8052
  }
7954
8053
  function worktreeExists(parent, prefix, n) {
7955
8054
  for (const name of [`${prefix}${n}`, `${prefix}${String(n).padStart(3, "0")}`]) {
7956
- if (existsSync12(join13(parent, name, ".git"))) return true;
8055
+ if (existsSync13(join14(parent, name, ".git"))) return true;
7957
8056
  }
7958
8057
  return false;
7959
8058
  }
@@ -8027,7 +8126,7 @@ function planRunWorktree(dir, cfg, t, role) {
8027
8126
  const existing = worktreeForNumber(dir, num);
8028
8127
  const located = locateBranchForTicket(dir, cfg, t, role);
8029
8128
  const baseRef = hasRemote ? `${remote}/${base}` : base;
8030
- if (existing && !existing.prunable && existsSync13(existing.path)) {
8129
+ if (existing && !existing.prunable && existsSync14(existing.path)) {
8031
8130
  const branch = existing.branch;
8032
8131
  if (status(existing.path).length > 0) return { action: "leave-dirty", path: existing.path, branch };
8033
8132
  if (!branch) return { action: "leave-unpushed", path: existing.path, branch: "(detached HEAD)" };
@@ -8041,7 +8140,7 @@ function planRunWorktree(dir, cfg, t, role) {
8041
8140
  if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
8042
8141
  return { action: "recut", path: existing.path, branch, base: baseRef };
8043
8142
  }
8044
- const path = join14(dirname7(dir), worktreeForTicket(dir, cfg, t, role));
8143
+ const path = join15(dirname7(dir), worktreeForTicket(dir, cfg, t, role));
8045
8144
  if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
8046
8145
  return { action: "cut-remote", path, branch: located.branch };
8047
8146
  }
@@ -8088,7 +8187,7 @@ function worktreeHoldsUnpushed(path, remote) {
8088
8187
  }
8089
8188
  async function finishRunWorktree(dir, cfg, branch) {
8090
8189
  const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
8091
- if (!wt || !existsSync13(wt.path)) return { kind: "none" };
8190
+ if (!wt || !existsSync14(wt.path)) return { kind: "none" };
8092
8191
  if (cfg.hooks.handoff) return { kind: "kept-handoff", path: wt.path };
8093
8192
  if (status(wt.path).length > 0) return { kind: "kept-dirty", path: wt.path };
8094
8193
  if (remoteConfigured(dir, cfg.branch.remote) && worktreeHoldsUnpushed(wt.path, cfg.branch.remote)) {
@@ -8100,7 +8199,7 @@ async function finishRunWorktree(dir, cfg, branch) {
8100
8199
  if (pids.length) await killGently(pids);
8101
8200
  }
8102
8201
  gitOk(dir, ["worktree", "remove", wt.path]);
8103
- return existsSync13(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
8202
+ return existsSync14(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
8104
8203
  }
8105
8204
 
8106
8205
  // src/handoff-guard.ts
@@ -8249,13 +8348,13 @@ async function retireRunWorktree(tracker, dir, cfg, t, role, seatId, shipName2,
8249
8348
  }
8250
8349
 
8251
8350
  // src/connect-wizard.ts
8252
- import { delimiter, dirname as dirname8, join as join15 } from "path";
8253
- import { accessSync as accessSync2, constants as constants2, existsSync as existsSync14, mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
8351
+ import { delimiter, dirname as dirname8, join as join16 } from "path";
8352
+ import { accessSync as accessSync2, constants as constants2, existsSync as existsSync15, mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
8254
8353
  import { stringify } from "yaml";
8255
8354
  var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
8256
8355
  function shouldRunWizard(opts) {
8257
8356
  if (!opts.isTTY) return false;
8258
- const exists = opts.configExists ?? existsSync14;
8357
+ const exists = opts.configExists ?? existsSync15;
8259
8358
  return !configSearchPath(opts.crewHome).some((p) => exists(p));
8260
8359
  }
8261
8360
  function firstRunConfigPath() {
@@ -8272,7 +8371,7 @@ function defaultIsExecutable(p) {
8272
8371
  function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
8273
8372
  for (const dir of pathEnv.split(delimiter)) {
8274
8373
  if (!dir) continue;
8275
- const candidate = join15(dir, name);
8374
+ const candidate = join16(dir, name);
8276
8375
  if (isExecutable(candidate)) return candidate;
8277
8376
  }
8278
8377
  return void 0;
@@ -8367,8 +8466,8 @@ Wrote ${o.configPath}.
8367
8466
  }
8368
8467
 
8369
8468
  // src/release-run.ts
8370
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync9, existsSync as existsSync15, copyFileSync, mkdirSync as mkdirSync8, mkdtempSync, rmSync as rmSync6 } from "fs";
8371
- import { dirname as dirname9, join as join16 } from "path";
8469
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, existsSync as existsSync16, copyFileSync, mkdirSync as mkdirSync8, mkdtempSync, rmSync as rmSync6 } from "fs";
8470
+ import { dirname as dirname9, join as join17 } from "path";
8372
8471
  import { tmpdir as tmpdir2 } from "os";
8373
8472
 
8374
8473
  // src/release.ts
@@ -9026,13 +9125,13 @@ async function bump(o, size) {
9026
9125
  }
9027
9126
  return printed;
9028
9127
  }
9029
- const files = o.repo.release.versionFiles.map((f) => join16(o.cwd, f));
9128
+ const files = o.repo.release.versionFiles.map((f) => join17(o.cwd, f));
9030
9129
  const first = files[0];
9031
- if (!existsSync15(first)) {
9130
+ if (!existsSync16(first)) {
9032
9131
  o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
9033
9132
  return null;
9034
9133
  }
9035
- const current = JSON.parse(readFileSync11(first, "utf8")).version;
9134
+ const current = JSON.parse(readFileSync12(first, "utf8")).version;
9036
9135
  if (!current) {
9037
9136
  o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
9038
9137
  return null;
@@ -9045,7 +9144,7 @@ async function bump(o, size) {
9045
9144
  return version;
9046
9145
  }
9047
9146
  for (const f of files) {
9048
- const src = readFileSync11(f, "utf8");
9147
+ const src = readFileSync12(f, "utf8");
9049
9148
  const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
9050
9149
  if (out === src) {
9051
9150
  o.emit.error(`no version field in ${f}`);
@@ -9058,13 +9157,13 @@ async function bump(o, size) {
9058
9157
  function writeChangelog(o, version, entries) {
9059
9158
  const file = o.repo.release.changelog;
9060
9159
  if (!file || entries.length === 0) return;
9061
- const path = join16(o.cwd, file);
9160
+ const path = join17(o.cwd, file);
9062
9161
  const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
9063
9162
  if (o.dryRun) {
9064
9163
  o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
9065
9164
  return;
9066
9165
  }
9067
- const existing = existsSync15(path) ? readFileSync11(path, "utf8") : "# Changelog\n";
9166
+ const existing = existsSync16(path) ? readFileSync12(path, "utf8") : "# Changelog\n";
9068
9167
  writeFileSync9(path, insertChangelogSection(existing, section));
9069
9168
  }
9070
9169
  async function confirm(o, expected) {
@@ -9146,8 +9245,8 @@ async function confirmLastRelease(o, decision) {
9146
9245
  function openReleaseCheckout(o) {
9147
9246
  const { remote, base } = o.repo.branch;
9148
9247
  const branch = `crew/release-${Date.now()}`;
9149
- const root = mkdtempSync(join16(tmpdir2(), "crew-release-"));
9150
- const dir = join16(root, "checkout");
9248
+ const root = mkdtempSync(join17(tmpdir2(), "crew-release-"));
9249
+ const dir = join17(root, "checkout");
9151
9250
  const close = () => {
9152
9251
  gitOk(o.cwd, ["worktree", "remove", "--force", dir]);
9153
9252
  rmSync6(root, { recursive: true, force: true });
@@ -9161,10 +9260,10 @@ function openReleaseCheckout(o) {
9161
9260
  throw e;
9162
9261
  }
9163
9262
  for (const f of o.repo.worktrees.copy) {
9164
- const src = join16(o.cwd, f);
9165
- if (!existsSync15(src)) continue;
9166
- mkdirSync8(dirname9(join16(dir, f)), { recursive: true });
9167
- copyFileSync(src, join16(dir, f));
9263
+ const src = join17(o.cwd, f);
9264
+ if (!existsSync16(src)) continue;
9265
+ mkdirSync8(dirname9(join17(dir, f)), { recursive: true });
9266
+ copyFileSync(src, join17(dir, f));
9168
9267
  }
9169
9268
  return { dir, branch, close };
9170
9269
  }
@@ -9633,7 +9732,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
9633
9732
  }
9634
9733
 
9635
9734
  // src/watch.ts
9636
- import { watch as watchFile, existsSync as existsSync16, statSync as statSync6, openSync as openSync3, readSync, closeSync as closeSync3 } from "fs";
9735
+ import { watch as watchFile, existsSync as existsSync17, statSync as statSync6, openSync as openSync3, readSync, closeSync as closeSync3 } from "fs";
9637
9736
  var E = "\x1B[";
9638
9737
  var dim = (s) => `${E}2m${s}${E}0m`;
9639
9738
  var bold = (s) => `${E}1m${s}${E}0m`;
@@ -9734,7 +9833,7 @@ function renderHeaders(states, now = Date.now()) {
9734
9833
  return states.map((s) => renderHeader(s, now)).join("");
9735
9834
  }
9736
9835
  function readFrom(file, from) {
9737
- if (!existsSync16(file)) return { events: [], offset: from };
9836
+ if (!existsSync17(file)) return { events: [], offset: from };
9738
9837
  const size = statSync6(file).size;
9739
9838
  if (size < from) return readFrom(file, 0);
9740
9839
  if (size === from) return { events: [], offset: from };
@@ -10445,7 +10544,7 @@ async function showLogEntry(route, entryId, opts = {}) {
10445
10544
  }
10446
10545
 
10447
10546
  // src/repos-cmd.ts
10448
- import { homedir as homedir6 } from "os";
10547
+ import { homedir as homedir7 } from "os";
10449
10548
  import { basename as basename7, resolve as resolve3 } from "path";
10450
10549
  import { isMap, isSeq, parseDocument, stringify as stringify2 } from "yaml";
10451
10550
  var ReposError = class extends Error {
@@ -10492,7 +10591,7 @@ function parseAddArgs(args, routes, isDir) {
10492
10591
  return { route, path: "." };
10493
10592
  }
10494
10593
  function expandDir(p, base) {
10495
- const home = p === "~" ? homedir6() : p.startsWith("~/") ? `${homedir6()}/${p.slice(2)}` : p;
10594
+ const home = p === "~" ? homedir7() : p.startsWith("~/") ? `${homedir7()}/${p.slice(2)}` : p;
10496
10595
  return resolve3(base, home);
10497
10596
  }
10498
10597
  function routeMapOf(doc, route) {
@@ -10627,7 +10726,7 @@ function schedulerInstalled(plan, exists, crontab) {
10627
10726
  }
10628
10727
 
10629
10728
  // src/worktree-sweep.ts
10630
- import { existsSync as existsSync17 } from "fs";
10729
+ import { existsSync as existsSync18 } from "fs";
10631
10730
  import { basename as basename8 } from "path";
10632
10731
  function planWorktreeSweep(target, tickets, contract) {
10633
10732
  const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
@@ -10655,7 +10754,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin")
10655
10754
  let removed = 0;
10656
10755
  let keptBranches = 0;
10657
10756
  for (const a of actions) {
10658
- if (!dryRun && existsSync17(a.path)) {
10757
+ if (!dryRun && existsSync18(a.path)) {
10659
10758
  let dirty = false;
10660
10759
  try {
10661
10760
  dirty = status(a.path).length > 0;
@@ -10669,7 +10768,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin")
10669
10768
  continue;
10670
10769
  }
10671
10770
  }
10672
- if (!dryRun && a.requirePushed && a.branch && existsSync17(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
10771
+ if (!dryRun && a.requirePushed && a.branch && existsSync18(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
10673
10772
  log.warn(
10674
10773
  `not removing worktree ${a.path} for ${a.ticket.issue_id} (${a.ticket.status}): ${a.branch} has commits that are not on ${remote}`,
10675
10774
  { ticket: a.ticket.issue_id, step: "worktree" }
@@ -10750,14 +10849,14 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
10750
10849
  }
10751
10850
 
10752
10851
  // src/stream-sweep.ts
10753
- import { existsSync as existsSync18, readdirSync as readdirSync3, rmSync as rmSync7, statSync as statSync7 } from "fs";
10754
- import { join as join17 } from "path";
10852
+ import { existsSync as existsSync19, readdirSync as readdirSync4, rmSync as rmSync7, statSync as statSync7 } from "fs";
10853
+ import { join as join18 } from "path";
10755
10854
  var DAY_MS2 = 24 * 60 * 60 * 1e3;
10756
10855
  function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
10757
- const dir = join17(stateDir, "streams");
10758
- if (!existsSync18(dir)) return [];
10856
+ const dir = join18(stateDir, "streams");
10857
+ if (!existsSync19(dir)) return [];
10759
10858
  const cutoff = now - retentionDays * DAY_MS2;
10760
- return readdirSync3(dir).map((name) => join17(dir, name)).filter((path) => {
10859
+ return readdirSync4(dir).map((name) => join18(dir, name)).filter((path) => {
10761
10860
  try {
10762
10861
  return statSync7(path).mtimeMs < cutoff;
10763
10862
  } catch {
@@ -10783,9 +10882,9 @@ function applyStreamSweep(paths, dryRun, log) {
10783
10882
  return removed;
10784
10883
  }
10785
10884
  function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
10786
- if (!existsSync18(stateDir)) return [];
10885
+ if (!existsSync19(stateDir)) return [];
10787
10886
  const cutoff = now - retentionDays * DAY_MS2;
10788
- return readdirSync3(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join17(stateDir, name)).filter((path) => {
10887
+ return readdirSync4(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join18(stateDir, name)).filter((path) => {
10789
10888
  try {
10790
10889
  return statSync7(path).mtimeMs < cutoff;
10791
10890
  } catch {
@@ -10795,23 +10894,23 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
10795
10894
  }
10796
10895
 
10797
10896
  // src/cli.ts
10798
- import { readFileSync as readFileSync16, existsSync as existsSync23, mkdirSync as mkdirSync13, writeFileSync as writeFileSync13, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync2, statSync as statSync9 } from "fs";
10897
+ import { readFileSync as readFileSync17, existsSync as existsSync24, mkdirSync as mkdirSync13, writeFileSync as writeFileSync13, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync2, statSync as statSync9 } from "fs";
10799
10898
  import { createInterface } from "readline/promises";
10800
10899
  import { dirname as dirOf, resolve as resolvePath } from "path";
10801
10900
 
10802
10901
  // src/ssh-keys.ts
10803
10902
  import { execFileSync as execFileSync3 } from "child_process";
10804
- import { existsSync as existsSync19, mkdirSync as mkdirSync9, readFileSync as readFileSync12, chmodSync } from "fs";
10805
- import { dirname as dirname11, join as join18 } from "path";
10903
+ import { existsSync as existsSync20, mkdirSync as mkdirSync9, readFileSync as readFileSync13, chmodSync } from "fs";
10904
+ import { dirname as dirname11, join as join19 } from "path";
10806
10905
  var SshKeyError = class extends Error {
10807
10906
  };
10808
10907
  function shipSshKeyPathFor(stateDir) {
10809
- return join18(stateDir, "ssh", "id_ed25519");
10908
+ return join19(stateDir, "ssh", "id_ed25519");
10810
10909
  }
10811
10910
  function ensureShipSshKeypair(stateDir) {
10812
10911
  const privateKeyPath = shipSshKeyPathFor(stateDir);
10813
10912
  const publicKeyPath = `${privateKeyPath}.pub`;
10814
- if (!existsSync19(privateKeyPath)) {
10913
+ if (!existsSync20(privateKeyPath)) {
10815
10914
  mkdirSync9(dirname11(privateKeyPath), { recursive: true });
10816
10915
  try {
10817
10916
  execFileSync3("ssh-keygen", [
@@ -10832,10 +10931,10 @@ function ensureShipSshKeypair(stateDir) {
10832
10931
  }
10833
10932
  chmodSync(privateKeyPath, 384);
10834
10933
  }
10835
- if (!existsSync19(publicKeyPath)) {
10934
+ if (!existsSync20(publicKeyPath)) {
10836
10935
  throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
10837
10936
  }
10838
- const publicKey = readFileSync12(publicKeyPath, "utf8").trim();
10937
+ const publicKey = readFileSync13(publicKeyPath, "utf8").trim();
10839
10938
  return { privateKeyPath, publicKeyPath, publicKey };
10840
10939
  }
10841
10940
  function sshKeygenAvailable() {
@@ -11049,9 +11148,9 @@ function syncAllPassengerCheckouts(cfg) {
11049
11148
 
11050
11149
  // src/tunnel.ts
11051
11150
  import { spawn as spawn3 } from "child_process";
11052
- import { existsSync as existsSync20, mkdirSync as mkdirSync10, readFileSync as readFileSync13, writeFileSync as writeFileSync10, unlinkSync as unlinkSync3, readdirSync as readdirSync4 } from "fs";
11151
+ import { existsSync as existsSync21, mkdirSync as mkdirSync10, readFileSync as readFileSync14, writeFileSync as writeFileSync10, unlinkSync as unlinkSync3, readdirSync as readdirSync5 } from "fs";
11053
11152
  import { randomInt } from "crypto";
11054
- import { join as join19 } from "path";
11153
+ import { join as join20 } from "path";
11055
11154
  var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
11056
11155
  var defaultSpawn = (cmd, args) => spawn3(cmd, args, DEFAULT_SPAWN_OPTIONS);
11057
11156
  var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
@@ -11118,20 +11217,20 @@ function startTunnel(opts) {
11118
11217
  };
11119
11218
  }
11120
11219
  function tunnelStatePath(stateDir, workspaceId) {
11121
- return join19(stateDir, "passengers", `${workspaceId}.json`);
11220
+ return join20(stateDir, "passengers", `${workspaceId}.json`);
11122
11221
  }
11123
11222
  function readPersistedTunnel(stateDir, workspaceId) {
11124
11223
  const path = tunnelStatePath(stateDir, workspaceId);
11125
- if (!existsSync20(path)) return void 0;
11224
+ if (!existsSync21(path)) return void 0;
11126
11225
  try {
11127
- return JSON.parse(readFileSync13(path, "utf8"));
11226
+ return JSON.parse(readFileSync14(path, "utf8"));
11128
11227
  } catch {
11129
11228
  return void 0;
11130
11229
  }
11131
11230
  }
11132
11231
  function writePersistedTunnel(stateDir, state) {
11133
11232
  const path = tunnelStatePath(stateDir, state.workspaceId);
11134
- mkdirSync10(join19(stateDir, "passengers"), { recursive: true });
11233
+ mkdirSync10(join20(stateDir, "passengers"), { recursive: true });
11135
11234
  writeFileSync10(path, `${JSON.stringify(state, null, 2)}
11136
11235
  `);
11137
11236
  }
@@ -11229,9 +11328,9 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
11229
11328
  }
11230
11329
  }
11231
11330
  function readAllPersistedWorkspaceIds(stateDir) {
11232
- const dir = join19(stateDir, "passengers");
11233
- if (!existsSync20(dir)) return [];
11234
- return readdirSync4(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
11331
+ const dir = join20(stateDir, "passengers");
11332
+ if (!existsSync21(dir)) return [];
11333
+ return readdirSync5(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
11235
11334
  }
11236
11335
 
11237
11336
  // src/mcp-endpoint-registry.ts
@@ -11351,8 +11450,8 @@ async function unregisterEndpoint(workspaceId, info, d) {
11351
11450
  import { createServer as createHttpServer } from "http";
11352
11451
  import { spawn as spawn4 } from "child_process";
11353
11452
  import { randomBytes } from "crypto";
11354
- import { existsSync as existsSync21, mkdirSync as mkdirSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync11, unlinkSync as unlinkSync4, readdirSync as readdirSync5 } from "fs";
11355
- import { join as join20 } from "path";
11453
+ import { existsSync as existsSync22, mkdirSync as mkdirSync11, readFileSync as readFileSync15, writeFileSync as writeFileSync11, unlinkSync as unlinkSync4, readdirSync as readdirSync6 } from "fs";
11454
+ import { join as join21 } from "path";
11356
11455
 
11357
11456
  // src/secret-compare.ts
11358
11457
  import { timingSafeEqual } from "crypto";
@@ -11441,20 +11540,20 @@ var defaultSpawn2 = (cmd, args, env) => {
11441
11540
  return child;
11442
11541
  };
11443
11542
  function syncDaemonStatePath(stateDir, workspaceId) {
11444
- return join20(stateDir, "passengers", `${workspaceId}-sync.json`);
11543
+ return join21(stateDir, "passengers", `${workspaceId}-sync.json`);
11445
11544
  }
11446
11545
  function readPersistedSyncDaemon(stateDir, workspaceId) {
11447
11546
  const path = syncDaemonStatePath(stateDir, workspaceId);
11448
- if (!existsSync21(path)) return void 0;
11547
+ if (!existsSync22(path)) return void 0;
11449
11548
  try {
11450
- return JSON.parse(readFileSync14(path, "utf8"));
11549
+ return JSON.parse(readFileSync15(path, "utf8"));
11451
11550
  } catch {
11452
11551
  return void 0;
11453
11552
  }
11454
11553
  }
11455
11554
  function writePersistedSyncDaemon(stateDir, state) {
11456
11555
  const path = syncDaemonStatePath(stateDir, state.workspaceId);
11457
- mkdirSync11(join20(stateDir, "passengers"), { recursive: true });
11556
+ mkdirSync11(join21(stateDir, "passengers"), { recursive: true });
11458
11557
  writeFileSync11(path, `${JSON.stringify(state, null, 2)}
11459
11558
  `);
11460
11559
  }
@@ -11475,10 +11574,10 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
11475
11574
  }
11476
11575
  }
11477
11576
  function readAllPersistedSyncWorkspaceIds(stateDir) {
11478
- const dir = join20(stateDir, "passengers");
11479
- if (!existsSync21(dir)) return [];
11577
+ const dir = join21(stateDir, "passengers");
11578
+ if (!existsSync22(dir)) return [];
11480
11579
  const suffix = "-sync.json";
11481
- return readdirSync5(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
11580
+ return readdirSync6(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
11482
11581
  }
11483
11582
  function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
11484
11583
  const alive = deps.isPidAlive ?? isPidAlive;
@@ -11533,8 +11632,8 @@ function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
11533
11632
  }
11534
11633
 
11535
11634
  // src/daemon.ts
11536
- import { existsSync as existsSync22, mkdirSync as mkdirSync12, readFileSync as readFileSync15, readdirSync as readdirSync6, rmSync as rmSync8, statSync as statSync8, writeFileSync as writeFileSync12 } from "fs";
11537
- import { join as join21 } from "path";
11635
+ import { existsSync as existsSync23, mkdirSync as mkdirSync12, readFileSync as readFileSync16, readdirSync as readdirSync7, rmSync as rmSync8, statSync as statSync8, writeFileSync as writeFileSync12 } from "fs";
11636
+ import { join as join22 } from "path";
11538
11637
  var DEFAULT_FLOOR_MS = 5e3;
11539
11638
  var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
11540
11639
  var defaultSleep = (ms2) => new Promise((resolve5) => setTimeout(resolve5, ms2));
@@ -11572,8 +11671,8 @@ function latestMtime(path) {
11572
11671
  if (stat.isFile()) return stat.mtimeMs;
11573
11672
  if (!stat.isDirectory()) return 0;
11574
11673
  let latest = stat.mtimeMs;
11575
- for (const entry of readdirSync6(path, { withFileTypes: true })) {
11576
- latest = Math.max(latest, latestMtime(join21(path, entry.name)));
11674
+ for (const entry of readdirSync7(path, { withFileTypes: true })) {
11675
+ latest = Math.max(latest, latestMtime(join22(path, entry.name)));
11577
11676
  }
11578
11677
  return latest;
11579
11678
  }
@@ -11582,13 +11681,13 @@ function computeTreeSignature(paths) {
11582
11681
  }
11583
11682
  function makeStaleChecker(paths, opts = {}) {
11584
11683
  const initial = computeTreeSignature(paths);
11585
- return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync22(opts.reloadSentinel);
11684
+ return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync23(opts.reloadSentinel);
11586
11685
  }
11587
11686
  function reloadSentinelFor(stateDir) {
11588
- return join21(stateDir, "reload-requested");
11687
+ return join22(stateDir, "reload-requested");
11589
11688
  }
11590
11689
  function daemonStampFor(stateDir) {
11591
- return join21(stateDir, "daemon.started");
11690
+ return join22(stateDir, "daemon.started");
11592
11691
  }
11593
11692
  function noteDaemonStarted(stateDir, now = Date.now()) {
11594
11693
  mkdirSync12(stateDir, { recursive: true });
@@ -11598,7 +11697,7 @@ function noteDaemonStarted(stateDir, now = Date.now()) {
11598
11697
  }
11599
11698
  function readDaemonStarted(stateDir) {
11600
11699
  try {
11601
- const v = JSON.parse(readFileSync15(daemonStampFor(stateDir), "utf8")).startedAt;
11700
+ const v = JSON.parse(readFileSync16(daemonStampFor(stateDir), "utf8")).startedAt;
11602
11701
  return typeof v === "number" ? v : null;
11603
11702
  } catch {
11604
11703
  return null;
@@ -12188,8 +12287,9 @@ if (command === "passenger-sync-daemon") {
12188
12287
  const FLEET_CAPABLE = /* @__PURE__ */ new Set(["poll", "run", "passengers"]);
12189
12288
  const releaseFleetWide = ["merge", "deploy", "release"].includes(command) && flag("fleet");
12190
12289
  const DAEMON_SUBCOMMANDS = /* @__PURE__ */ new Set(["start", "stop", "status", "restart"]);
12191
- const named = command === "daemon" && DAEMON_SUBCOMMANDS.has(positional[1] ?? "") ? positional[2] : positional[1];
12192
- const fleetWide = command === "inbox" || command === "connect" || command === "disconnect" || command === "agents" || command === "skills" || command === "logbook" || command === "repos" || command === "projects" || releaseFleetWide || command === "pause" || command === "resume" || command === "reload" || FLEET_CAPABLE.has(command) && !named && cfg.routes.length > 1;
12290
+ const worktreeArgs = command === "worktree" ? splitWorktreeArgs(positional, cfg.routes.map((r) => r.route)) : void 0;
12291
+ const named = worktreeArgs ? worktreeArgs.route : command === "daemon" && DAEMON_SUBCOMMANDS.has(positional[1] ?? "") ? positional[2] : positional[1];
12292
+ const fleetWide = command === "inbox" || command === "connect" || command === "disconnect" || command === "agents" || command === "skills" || command === "logbook" || command === "repos" || command === "projects" || releaseFleetWide || command === "pause" || command === "resume" || command === "reload" || FLEET_CAPABLE.has(command) && !named && cfg.routes.length > 1 || worktreeArgs !== void 0 && !named && cfg.routes.length > 1;
12193
12293
  let route;
12194
12294
  try {
12195
12295
  route = fleetWide ? cfg.routes[0] : findRoute(cfg, named);
@@ -12395,8 +12495,8 @@ if (command === "passenger-sync-daemon") {
12395
12495
  }
12396
12496
  if (host === "linux" && hasSystemd) {
12397
12497
  const label = labelFor(CREW_HOME, "run");
12398
- const staleTimer = join22(homedir7(), ".config", "systemd", "user", `${label}.timer`);
12399
- if (existsSync23(staleTimer)) {
12498
+ const staleTimer = join23(homedir8(), ".config", "systemd", "user", `${label}.timer`);
12499
+ if (existsSync24(staleTimer)) {
12400
12500
  process.stdout.write(
12401
12501
  `removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
12402
12502
  `
@@ -12422,7 +12522,7 @@ if (command === "passenger-sync-daemon") {
12422
12522
  const tracker = new Tracker(c, cfg.ship);
12423
12523
  problems.push(...validateContract(tracker.contract).map((p) => `contract: ${p}`));
12424
12524
  for (const r of await resolvedRepos(c)) {
12425
- if (!existsSync23(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
12525
+ if (!existsSync24(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
12426
12526
  if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
12427
12527
  problems.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
12428
12528
  }
@@ -12440,7 +12540,7 @@ if (command === "passenger-sync-daemon") {
12440
12540
  }
12441
12541
  async function doctorFix(o) {
12442
12542
  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))));
12443
- const unitExists = (p) => existsSync23(p);
12543
+ const unitExists = (p) => existsSync24(p);
12444
12544
  const crontab = o.host === "linux" ? spawnSync5("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
12445
12545
  const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run"), unitExists, crontab) : true;
12446
12546
  const plan = planFix({ routes, schedulerAbsent, host: o.host });
@@ -12457,7 +12557,7 @@ if (command === "passenger-sync-daemon") {
12457
12557
  for (const a of plan.actions) {
12458
12558
  if (a.kind === "enable") {
12459
12559
  if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
12460
- writeFileSync13(cfg.configFile, planEnable(readFileSync16(cfg.configFile, "utf8"), a.route));
12560
+ writeFileSync13(cfg.configFile, planEnable(readFileSync17(cfg.configFile, "utf8"), a.route));
12461
12561
  enabledSound.add(a.route);
12462
12562
  changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
12463
12563
  } else {
@@ -13364,7 +13464,7 @@ ${changed2.map((c) => ` - ${c}
13364
13464
  process.exit(1);
13365
13465
  }
13366
13466
  requireArmed("run the daemon loop");
13367
- const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join22(CREW_HOME, "src"), join22(CREW_HOME, "dist"), join22(CREW_HOME, "bin")];
13467
+ const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join23(CREW_HOME, "src"), join23(CREW_HOME, "dist"), join23(CREW_HOME, "bin")];
13368
13468
  const configPaths = daemonConfigPaths(cfg);
13369
13469
  watchPaths.push(...configPaths);
13370
13470
  noteDaemonStarted(cfg.ship.stateDir);
@@ -13523,7 +13623,7 @@ ${changed2.map((c) => ` - ${c}
13523
13623
  `);
13524
13624
  if (!dryRun) {
13525
13625
  const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
13526
- const raw = existsSync23(resolvedPath) ? JSON.parse(readFileSync16(resolvedPath, "utf8")) : {};
13626
+ const raw = existsSync24(resolvedPath) ? JSON.parse(readFileSync17(resolvedPath, "utf8")) : {};
13527
13627
  raw.agentPersonas = personaResult.agentPersonas;
13528
13628
  raw.agentSkills = skillResult.agentSkills;
13529
13629
  mkdirSync13(dirOf(resolvedPath), { recursive: true });
@@ -13574,7 +13674,7 @@ ${changed2.map((c) => ` - ${c}
13574
13674
  `);
13575
13675
  if (!dryRun) {
13576
13676
  const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
13577
- const raw = existsSync23(resolvedPath) ? JSON.parse(readFileSync16(resolvedPath, "utf8")) : {};
13677
+ const raw = existsSync24(resolvedPath) ? JSON.parse(readFileSync17(resolvedPath, "utf8")) : {};
13578
13678
  raw.agentSkills = result.agentSkills;
13579
13679
  mkdirSync13(dirOf(resolvedPath), { recursive: true });
13580
13680
  writeFileSync13(resolvedPath, `${JSON.stringify(raw, null, 2)}
@@ -13590,14 +13690,14 @@ ${changed2.map((c) => ` - ${c}
13590
13690
  process.exit(2);
13591
13691
  };
13592
13692
  if (sub !== "add" && sub !== "list") usage2();
13593
- const configText = readFileSync16(cfg.configFile, "utf8");
13693
+ const configText = readFileSync17(cfg.configFile, "utf8");
13594
13694
  let routeName = positional[2];
13595
13695
  let addPath = ".";
13596
13696
  let addName;
13597
13697
  if (sub === "list" && !routeName) usage2();
13598
13698
  if (sub === "add") {
13599
13699
  try {
13600
- const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync23(resolvePath(p.replace(/^~(?=\/|$)/, homedir7()))));
13700
+ const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync24(resolvePath(p.replace(/^~(?=\/|$)/, homedir8()))));
13601
13701
  routeName = a.route;
13602
13702
  addPath = a.path;
13603
13703
  addName = a.name;
@@ -13612,9 +13712,9 @@ ${changed2.map((c) => ` - ${c}
13612
13712
  }
13613
13713
  const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
13614
13714
  let resolvedRemotes;
13615
- if (existsSync23(resolvedFile)) {
13715
+ if (existsSync24(resolvedFile)) {
13616
13716
  try {
13617
- resolvedRemotes = JSON.parse(readFileSync16(resolvedFile, "utf8"));
13717
+ resolvedRemotes = JSON.parse(readFileSync17(resolvedFile, "utf8"));
13618
13718
  } catch {
13619
13719
  }
13620
13720
  }
@@ -13629,14 +13729,14 @@ ${changed2.map((c) => ` - ${c}
13629
13729
  for (const r of repos) {
13630
13730
  const origin2 = originOf(r.dir);
13631
13731
  const row = matchRepoName(origin2, resolvedRemotes);
13632
- const note = !existsSync23(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
13732
+ const note = !existsSync24(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
13633
13733
  process.stdout.write(`${r.name} ${r.dir} ${note}
13634
13734
  `);
13635
13735
  }
13636
13736
  break;
13637
13737
  }
13638
- const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir7()));
13639
- const top = existsSync23(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
13738
+ const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir8()));
13739
+ const top = existsSync24(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
13640
13740
  if (!top) throw new ReposError(`${target} is not a git checkout`);
13641
13741
  const origin = originOf(top);
13642
13742
  const plan = planRepoAdd({
@@ -13685,8 +13785,8 @@ ${changed2.map((c) => ` - ${c}
13685
13785
  const created = await rowTracker.createRepoRow(rowPayload);
13686
13786
  process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
13687
13787
  `);
13688
- if (existsSync23(resolvedFile)) {
13689
- const raw = JSON.parse(readFileSync16(resolvedFile, "utf8"));
13788
+ if (existsSync24(resolvedFile)) {
13789
+ const raw = JSON.parse(readFileSync17(resolvedFile, "utf8"));
13690
13790
  raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
13691
13791
  raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
13692
13792
  writeFileSync13(resolvedFile, `${JSON.stringify(raw, null, 2)}
@@ -13932,10 +14032,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
13932
14032
  operator = await pickOperator(found.holds);
13933
14033
  }
13934
14034
  let previous;
13935
- const hadPreviousFile = existsSync23(resolvedPath);
14035
+ const hadPreviousFile = existsSync24(resolvedPath);
13936
14036
  if (hadPreviousFile) {
13937
14037
  try {
13938
- previous = JSON.parse(readFileSync16(resolvedPath, "utf8"));
14038
+ previous = JSON.parse(readFileSync17(resolvedPath, "utf8"));
13939
14039
  } catch {
13940
14040
  }
13941
14041
  }
@@ -14023,7 +14123,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
14023
14123
  const keyPersisted = keychainBacked || apiKeyFile !== void 0;
14024
14124
  const wizard = firstRun;
14025
14125
  const writeFirstRun = wizard !== void 0 && !dryRun && keyPersisted;
14026
- const routeDir = value("dir") ?? (writeFirstRun ? join22(DEFAULT_REPOS_BASE_PATH, found.workspaceSlug) : void 0);
14126
+ const routeDir = value("dir") ?? (writeFirstRun ? join23(DEFAULT_REPOS_BASE_PATH, found.workspaceSlug) : void 0);
14027
14127
  const routeBlock = renderConnection(
14028
14128
  found,
14029
14129
  route2,
@@ -14364,21 +14464,36 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
14364
14464
  }
14365
14465
  case "worktree": {
14366
14466
  emit.enter("worktree");
14367
- const rawWt = positional[2] ?? positional[1];
14467
+ const rawWt = worktreeArgs?.ticket;
14368
14468
  const nWt = rawWt ? parseTicketNumber(rawWt) : null;
14369
14469
  if (!nWt) {
14370
14470
  process.stderr.write("worktree: need a ticket number (326, ISSUE-326, or TABL-326)\n");
14371
14471
  process.exit(2);
14372
14472
  }
14373
- const trackerWt = new Tracker(route, cfg.ship);
14374
- const cWt = trackerWt.contract;
14375
- const ticketWt = (await trackerWt.openTickets()).find((t) => t[cWt.columns.key] === `ISSUE-${nWt}`);
14376
- if (!ticketWt) {
14377
- process.stderr.write(`worktree: no open ticket ISSUE-${nWt} on ${route.route}
14473
+ const candidatesWt = !named && cfg.routes.length > 1 ? cfg.routes.filter((r) => r.enabled) : [route];
14474
+ let ticketWt;
14475
+ const lookedWt = [];
14476
+ const hitsWt = /* @__PURE__ */ new Map();
14477
+ for (const cand of candidatesWt) {
14478
+ const tr = new Tracker(cand, cfg.ship);
14479
+ const hit = (await tr.openTickets()).find((t) => t[tr.contract.columns.key] === `ISSUE-${nWt}`);
14480
+ if (hit) hitsWt.set(cand.route, hit);
14481
+ lookedWt.push({
14482
+ route: cand.route,
14483
+ hit: hit ? { repoId: hit.repo_id ?? null } : void 0,
14484
+ servedRepoIds: hit ? (await resolvedRepos(cand)).flatMap((r) => r.id ? [r.id] : []) : []
14485
+ });
14486
+ }
14487
+ const pickedWt = pickWorktreeRoute(Number(nWt), lookedWt);
14488
+ if ("error" in pickedWt) {
14489
+ process.stderr.write(`worktree: ${pickedWt.error}
14378
14490
  `);
14379
14491
  process.exit(2);
14380
14492
  }
14381
- const repoWt = (await resolvedRepos(route)).find((r) => r.id === ticketWt.repo_id) ?? (await resolvedRepos(route))[0];
14493
+ const routeWt = candidatesWt.find((r) => r.route === pickedWt.route) ?? route;
14494
+ ticketWt = hitsWt.get(pickedWt.route);
14495
+ if (!ticketWt) process.exit(2);
14496
+ const repoWt = (await resolvedRepos(routeWt)).find((r) => r.id === ticketWt.repo_id) ?? (await resolvedRepos(routeWt))[0];
14382
14497
  if (!repoWt) {
14383
14498
  process.stderr.write("worktree: this route has no repository\n");
14384
14499
  process.exit(2);
@@ -14588,7 +14703,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
14588
14703
  }
14589
14704
  case "log": {
14590
14705
  try {
14591
- const text = readFileSync16(cfg.ship.logFile, "utf8").split("\n");
14706
+ const text = readFileSync17(cfg.ship.logFile, "utf8").split("\n");
14592
14707
  process.stdout.write(`${text.slice(-(Number(value("tail")) || 40)).join("\n")}
14593
14708
  `);
14594
14709
  } catch {
@@ -14798,7 +14913,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
14798
14913
  );
14799
14914
  try {
14800
14915
  const evFile = eventFileFor(cfg.ship.stateDir);
14801
- const evSize = existsSync23(evFile) ? statSync9(evFile).size : 0;
14916
+ const evSize = existsSync24(evFile) ? statSync9(evFile).size : 0;
14802
14917
  process.stdout.write(
14803
14918
  evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
14804
14919
  ` : "events log: ok\n"
@@ -14932,15 +15047,39 @@ crew manifest: ${seats2.length ? seats2.map((c) => c.name).join(", ") : "non
14932
15047
  let allBinsFound = true;
14933
15048
  for (const tc of shipToolchains(cfg.ship)) {
14934
15049
  const adapter = adapterFor(tc.harness);
14935
- const binPath = findOnPath(tc.launcherBin ?? tc.launcher ?? tc.bin, schedulerPath);
15050
+ const runBin = launcherExecutable(tc) ?? tc.bin;
15051
+ const binPath = findOnPath(runBin, schedulerPath);
14936
15052
  if (!binPath) allBinsFound = false;
14937
15053
  const label = tc.name === defaultToolchain ? "agent binary: " : `toolchain ${tc.name}:`.padEnd(18);
14938
15054
  process.stdout.write(
14939
- `${label} ${tc.launcherBin ?? tc.launcher ?? tc.bin} \u2014 ${binPath ? `OK (${binPath})` : "NOT FOUND on the scheduler PATH"}${tc.name === defaultToolchain ? "" : ` [${adapter.displayName}]`}
15055
+ `${label} ${runBin} \u2014 ${binPath ? `OK (${binPath})` : "NOT FOUND on the scheduler PATH"}${tc.name === defaultToolchain ? "" : ` [${adapter.displayName}]`}
14940
15056
  `
14941
15057
  );
14942
15058
  for (const limit of adapter.limitations) process.stdout.write(` cannot enforce: ${limit}
14943
15059
  `);
15060
+ if (isOmlxLauncher(tc)) {
15061
+ const s = readOmlxSettings();
15062
+ if (!s) {
15063
+ process.stdout.write(` omlx: ~/.omlx/settings.json not found or has no server.port \u2014 cannot check the server
15064
+ `);
15065
+ } else {
15066
+ let status2;
15067
+ try {
15068
+ status2 = (await fetch(`http://${s.host}:${s.port}/v1/models`, { signal: AbortSignal.timeout(3e3) })).status;
15069
+ } catch {
15070
+ status2 = void 0;
15071
+ }
15072
+ const probe = interpretOmlxProbe(status2);
15073
+ process.stdout.write(` omlx server: ${s.host}:${s.port} \u2014 ${probe.detail}${probe.up ? "" : " (start oMLX)"}
15074
+ `);
15075
+ process.stdout.write(
15076
+ ` omlx model: ${tc.model} \u2014 ${omlxModelPresent(tc.model, s.modelDirs) ? "present" : `NOT FOUND in ${s.modelDirs.join(", ") || "any model dir"}`}
15077
+ `
15078
+ );
15079
+ }
15080
+ process.stdout.write(` local model: tool-call reliability and prefill speed vary by model
15081
+ `);
15082
+ }
14944
15083
  if (isOriLauncher(tc)) {
14945
15084
  process.stdout.write(` ori: headless auth under the scheduler is unverified \u2014 run \`ori login\` as the scheduler user, then confirm a run records provider openrouter (see docs/toolchains.md)
14946
15085
  `);