@tablation/crew 0.115.0 → 0.117.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +924 -664
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -125,19 +125,20 @@ function excludeFile(worktree) {
125
125
  return void 0;
126
126
  }
127
127
  }
128
+ function excludeFromGit(worktree, entry) {
129
+ const ex = excludeFile(worktree);
130
+ if (!ex) return;
131
+ mkdirSync(dirname(ex), { recursive: true });
132
+ const have = existsSync2(ex) ? readFileSync(ex, "utf8") : "";
133
+ if (have.split("\n").some((l) => l.trim() === entry)) return;
134
+ appendFileSync(ex, `${have === "" || have.endsWith("\n") ? "" : "\n"}${entry}
135
+ `);
136
+ }
128
137
  function prepareDropFile(worktree) {
129
138
  const file = pointedAtFile(worktree);
130
139
  mkdirSync(dirname(file), { recursive: true });
131
140
  if (!existsSync2(file)) writeFileSync(file, "");
132
- const ex = excludeFile(worktree);
133
- if (ex) {
134
- mkdirSync(dirname(ex), { recursive: true });
135
- const have = existsSync2(ex) ? readFileSync(ex, "utf8") : "";
136
- if (!have.split("\n").some((l) => l.trim() === `${POINTED_AT_DIR}/`)) {
137
- appendFileSync(ex, `${have === "" || have.endsWith("\n") ? "" : "\n"}${POINTED_AT_DIR}/
138
- `);
139
- }
140
- }
141
+ excludeFromGit(worktree, `${POINTED_AT_DIR}/`);
141
142
  return file;
142
143
  }
143
144
  function parseLine(raw) {
@@ -180,15 +181,160 @@ function dropFileMtime(worktree) {
180
181
  }
181
182
  }
182
183
 
184
+ // src/intel-register.ts
185
+ import { execFileSync as execFileSync2 } from "child_process";
186
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
187
+ import { dirname as dirname2, join as join2 } from "path";
188
+
189
+ // src/intel.ts
190
+ var INTEL_MINIMUM_TOOLS = [
191
+ "find symbols by name",
192
+ "references to a symbol",
193
+ "a symbol's definition",
194
+ "a file's symbol outline"
195
+ ];
196
+ var INTEL_SERVER_NAME = "code";
197
+ var lastUrl = (output) => {
198
+ const last = output.split("\n").map((l) => l.trim()).filter(Boolean).pop();
199
+ return last && /^https?:\/\/\S+$/i.test(last) ? last : void 0;
200
+ };
201
+ var oneLine = (s) => s.replace(/\s+/g, " ").trim().slice(0, 200);
202
+ async function rpcResult(res) {
203
+ const text2 = await res.text();
204
+ const body = (res.headers.get("content-type") ?? "").includes("text/event-stream") ? text2.split("\n").find((l) => l.startsWith("data:"))?.slice(5).trim() ?? "" : text2;
205
+ const msg = JSON.parse(body);
206
+ if (msg.error) throw new Error(msg.error.message ?? "MCP error");
207
+ return msg.result;
208
+ }
209
+ async function initializeAndList(url, f, signal) {
210
+ const headers = { "content-type": "application/json", accept: "application/json, text/event-stream" };
211
+ const init = await f(url, {
212
+ method: "POST",
213
+ headers,
214
+ signal,
215
+ body: JSON.stringify({
216
+ jsonrpc: "2.0",
217
+ id: 1,
218
+ method: "initialize",
219
+ params: { protocolVersion: "2025-03-26", capabilities: {}, clientInfo: { name: "crew", version: "0" } }
220
+ })
221
+ });
222
+ if (!init.ok) throw new Error(`initialize answered ${init.status}`);
223
+ await rpcResult(init);
224
+ const session = init.headers.get("mcp-session-id");
225
+ const sessionHeaders = session ? { ...headers, "mcp-session-id": session } : headers;
226
+ await f(url, { method: "POST", headers: sessionHeaders, signal, body: JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }) }).then((r) => r.body?.cancel()).catch(() => {
227
+ });
228
+ const list = await f(url, { method: "POST", headers: sessionHeaders, signal, body: JSON.stringify({ jsonrpc: "2.0", id: 2, method: "tools/list" }) });
229
+ if (!list.ok) throw new Error(`tools/list answered ${list.status}`);
230
+ const result = await rpcResult(list);
231
+ return (result?.tools ?? []).map((t) => String(t.name)).filter(Boolean);
232
+ }
233
+ async function probeIntel(deps, env, startupSeconds) {
234
+ let ran;
235
+ try {
236
+ ran = await deps.runHook(env);
237
+ } catch (e) {
238
+ return { state: "failed", reason: `the intel hook could not run: ${oneLine(e.message)}` };
239
+ }
240
+ if (!ran) return { state: "absent" };
241
+ if (ran.code !== 0) return { state: "failed", reason: `the intel hook exited ${ran.code}${ran.output.trim() ? `: ${oneLine(ran.output.trim().split("\n").pop() ?? "")}` : ""}` };
242
+ const url = lastUrl(ran.output);
243
+ if (!url) return { state: "failed", reason: "the intel hook printed no URL as its last line" };
244
+ const f = deps.fetch ?? fetch;
245
+ const sleep = deps.sleep ?? ((ms3) => new Promise((r) => setTimeout(r, ms3)));
246
+ const now = deps.now ?? Date.now;
247
+ const deadline = now() + startupSeconds * 1e3;
248
+ let last = "no answer";
249
+ for (; ; ) {
250
+ try {
251
+ const tools = await initializeAndList(url, f, AbortSignal.timeout(Math.max(1, deadline - now())));
252
+ return { state: "ready", url, tools };
253
+ } catch (e) {
254
+ last = oneLine(e.message);
255
+ }
256
+ if (now() >= deadline) break;
257
+ await sleep(500);
258
+ }
259
+ return { state: "failed", reason: `${url} did not answer an MCP initialize within ${startupSeconds}s (${last})` };
260
+ }
261
+ function codeToolsParagraph(outcome, server = INTEL_SERVER_NAME) {
262
+ if (outcome.state === "absent") return "No symbol service is available for this repository.";
263
+ if (outcome.state === "failed") return `The symbol service did not start (${outcome.reason}), so none is available for this run.`;
264
+ return [
265
+ `The \`${server}\` MCP server serves this repository's symbols from its language server. It offers at least: ${INTEL_MINIMUM_TOOLS.join("; ")}.`,
266
+ `Tools it advertised: ${outcome.tools.length ? outcome.tools.map((t) => `\`${t}\``).join(", ") : "(none listed)"}.`
267
+ ].join(" ");
268
+ }
269
+
270
+ // src/intel-register.ts
271
+ function mergeJsonServer(existing, entry, server) {
272
+ let doc = {};
273
+ if (existing !== void 0 && existing.trim() !== "") {
274
+ const parsed = JSON.parse(existing);
275
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("is not a JSON object");
276
+ doc = parsed;
277
+ }
278
+ const servers = doc.mcpServers;
279
+ if (servers !== void 0 && (!servers || typeof servers !== "object" || Array.isArray(servers))) throw new Error("has an mcpServers that is not an object");
280
+ doc.mcpServers = { ...servers, [server]: entry };
281
+ return `${JSON.stringify(doc, null, 2)}
282
+ `;
283
+ }
284
+ function mergeTomlServer(existing, url, server) {
285
+ const table2 = `[mcp_servers.${server}]
286
+ url = ${JSON.stringify(url)}
287
+ `;
288
+ const text2 = existing ?? "";
289
+ const lines2 = text2.split("\n");
290
+ const start = lines2.findIndex((l) => l.trim() === `[mcp_servers.${server}]`);
291
+ if (start < 0) return text2 === "" ? table2 : `${text2}${text2.endsWith("\n") ? "" : "\n"}
292
+ ${table2}`;
293
+ let end = lines2.findIndex((l, i) => i > start && /^\s*\[/.test(l));
294
+ if (end < 0) end = lines2.length;
295
+ const keepTail = lines2.slice(end);
296
+ return [...lines2.slice(0, start), ...table2.trimEnd().split("\n"), ...keepTail.length && keepTail[0] !== "" ? [""] : [], ...keepTail].join("\n");
297
+ }
298
+ function tracked(worktree, rel) {
299
+ try {
300
+ execFileSync2("git", ["ls-files", "--error-unmatch", "--", rel], { cwd: worktree, stdio: "ignore" });
301
+ return true;
302
+ } catch {
303
+ return false;
304
+ }
305
+ }
306
+ function registerCodeServer(worktree, url, server = INTEL_SERVER_NAME) {
307
+ const out = { written: [], skipped: [] };
308
+ const plans = [
309
+ [".mcp.json", (old) => mergeJsonServer(old, { type: "http", url }, server)],
310
+ [".codex/config.toml", (old) => mergeTomlServer(old, url, server)],
311
+ [".gemini/settings.json", (old) => mergeJsonServer(old, { httpUrl: url }, server)]
312
+ ];
313
+ for (const [rel, merge] of plans) {
314
+ const file = join2(worktree, rel);
315
+ try {
316
+ const next = merge(existsSync3(file) ? readFileSync2(file, "utf8") : void 0);
317
+ mkdirSync2(dirname2(file), { recursive: true });
318
+ writeFileSync2(file, next);
319
+ if (tracked(worktree, rel)) execFileSync2("git", ["update-index", "--skip-worktree", "--", rel], { cwd: worktree, stdio: "ignore" });
320
+ else excludeFromGit(worktree, rel);
321
+ out.written.push(rel);
322
+ } catch (e) {
323
+ out.skipped.push(`${rel} left alone: ${e.message}`);
324
+ }
325
+ }
326
+ return out;
327
+ }
328
+
183
329
  // src/periscope-state.ts
184
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "fs";
330
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync3 } from "fs";
185
331
  import { connect } from "net";
186
- import { join as join2 } from "path";
187
- var dirOf = (stateDir) => join2(stateDir, "periscopes");
188
- var fileOf = (stateDir, ticketRecordId) => join2(dirOf(stateDir), `${ticketRecordId}.json`);
332
+ import { join as join3 } from "path";
333
+ var dirOf = (stateDir) => join3(stateDir, "periscopes");
334
+ var fileOf = (stateDir, ticketRecordId) => join3(dirOf(stateDir), `${ticketRecordId}.json`);
189
335
  function writePeriscopeSidecar(stateDir, sc) {
190
- mkdirSync2(dirOf(stateDir), { recursive: true });
191
- writeFileSync2(fileOf(stateDir, sc.ticketRecordId), `${JSON.stringify(sc, null, 2)}
336
+ mkdirSync3(dirOf(stateDir), { recursive: true });
337
+ writeFileSync3(fileOf(stateDir, sc.ticketRecordId), `${JSON.stringify(sc, null, 2)}
192
338
  `);
193
339
  }
194
340
  function removePeriscopeSidecar(stateDir, ticketRecordId) {
@@ -196,12 +342,12 @@ function removePeriscopeSidecar(stateDir, ticketRecordId) {
196
342
  }
197
343
  function readPeriscopeSidecars(stateDir) {
198
344
  const dir = dirOf(stateDir);
199
- if (!existsSync3(dir)) return [];
345
+ if (!existsSync4(dir)) return [];
200
346
  const out = [];
201
347
  for (const name of readdirSync(dir)) {
202
348
  if (!name.endsWith(".json")) continue;
203
349
  try {
204
- const sc = JSON.parse(readFileSync2(join2(dir, name), "utf8"));
350
+ const sc = JSON.parse(readFileSync3(join3(dir, name), "utf8"));
205
351
  if (sc && typeof sc.ticketRecordId === "string" && typeof sc.url === "string") out.push(sc);
206
352
  } catch {
207
353
  }
@@ -319,23 +465,23 @@ function shellInitScript(shell) {
319
465
  }
320
466
 
321
467
  // src/config.ts
322
- import { readFileSync as readFileSync12, existsSync as existsSync11 } from "fs";
323
- import { resolve as resolve2, dirname as dirname5, isAbsolute as isAbsolute2, join as join13, basename as basename4 } from "path";
468
+ import { readFileSync as readFileSync13, existsSync as existsSync12 } from "fs";
469
+ import { resolve as resolve2, dirname as dirname6, isAbsolute as isAbsolute2, join as join14, basename as basename4 } from "path";
324
470
  import { homedir as homedir6, tmpdir } from "os";
325
471
  import { parse as parse2 } from "yaml";
326
472
 
327
473
  // src/events.ts
328
474
  import {
329
475
  appendFileSync as appendFileSync2,
330
- mkdirSync as mkdirSync3,
331
- existsSync as existsSync4,
476
+ mkdirSync as mkdirSync4,
477
+ existsSync as existsSync5,
332
478
  statSync as statSync2,
333
479
  renameSync,
334
480
  openSync,
335
481
  closeSync,
336
482
  rmSync as rmSync2
337
483
  } from "fs";
338
- import { join as join3, dirname as dirname2 } from "path";
484
+ import { join as join4, dirname as dirname3 } from "path";
339
485
  function render(e) {
340
486
  const scope = e.role ? `${e.step}[${e.role}]` : e.step;
341
487
  const ticket = e.ticket ? ` ${e.ticket}` : "";
@@ -347,7 +493,7 @@ var ROTATED_EVENTS_RE = /^events\.\d{8}T\d{6}Z(?:-\d+)?\.jsonl$/;
347
493
  var ROTATE_LOCK_STALE_MS = 6e4;
348
494
  function rotateEventsFile(file, maxBytes, now = /* @__PURE__ */ new Date()) {
349
495
  try {
350
- if (!existsSync4(file) || statSync2(file).size <= maxBytes) return void 0;
496
+ if (!existsSync5(file) || statSync2(file).size <= maxBytes) return void 0;
351
497
  } catch {
352
498
  return void 0;
353
499
  }
@@ -367,8 +513,8 @@ function rotateEventsFile(file, maxBytes, now = /* @__PURE__ */ new Date()) {
367
513
  try {
368
514
  if (statSync2(file).size <= maxBytes) return void 0;
369
515
  const stamp = now.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
370
- let dest = join3(dirname2(file), `events.${stamp}.jsonl`);
371
- for (let n = 1; existsSync4(dest); n++) dest = join3(dirname2(file), `events.${stamp}-${n}.jsonl`);
516
+ let dest = join4(dirname3(file), `events.${stamp}.jsonl`);
517
+ for (let n = 1; existsSync5(dest); n++) dest = join4(dirname3(file), `events.${stamp}-${n}.jsonl`);
372
518
  renameSync(file, dest);
373
519
  return dest;
374
520
  } finally {
@@ -391,7 +537,7 @@ var Emitter = class _Emitter {
391
537
  for (const f of [opts.eventFile, opts.logFile]) {
392
538
  if (!f) continue;
393
539
  try {
394
- mkdirSync3(dirname2(f), { recursive: true });
540
+ mkdirSync4(dirname3(f), { recursive: true });
395
541
  } catch {
396
542
  }
397
543
  }
@@ -467,18 +613,18 @@ var Emitter = class _Emitter {
467
613
  }
468
614
  }
469
615
  };
470
- var eventFileFor = (stateDir) => join3(stateDir, "events.jsonl");
616
+ var eventFileFor = (stateDir) => join4(stateDir, "events.jsonl");
471
617
 
472
618
  // src/pair-install.ts
473
- import { existsSync as existsSync7, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "fs";
619
+ import { existsSync as existsSync8, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "fs";
474
620
  import { spawnSync as spawnSync3 } from "child_process";
475
621
  import { homedir as homedir5 } from "os";
476
- import { join as join9 } from "path";
622
+ import { join as join10 } from "path";
477
623
 
478
624
  // src/adapters/claude.ts
479
- import { readFileSync as readFileSync7 } from "fs";
625
+ import { readFileSync as readFileSync8 } from "fs";
480
626
  import { homedir as homedir3 } from "os";
481
- import { join as join7 } from "path";
627
+ import { join as join8 } from "path";
482
628
 
483
629
  // src/stream.ts
484
630
  function toolTarget(name, input) {
@@ -537,19 +683,19 @@ function extractResult(line) {
537
683
 
538
684
  // src/claude-mcp.ts
539
685
  import { spawnSync } from "child_process";
540
- import { readFileSync as readFileSync5 } from "fs";
686
+ import { readFileSync as readFileSync6 } from "fs";
541
687
  import { homedir } from "os";
542
- import { join as join5 } from "path";
688
+ import { join as join6 } from "path";
543
689
 
544
690
  // src/mcp-registrations.ts
545
691
  import { createHash } from "crypto";
546
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
547
- import { join as join4 } from "path";
548
- var fileFor = (stateDir) => join4(stateDir, "mcp-registrations.json");
692
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
693
+ import { join as join5 } from "path";
694
+ var fileFor = (stateDir) => join5(stateDir, "mcp-registrations.json");
549
695
  var keyFingerprint = (key) => createHash("sha256").update(key).digest("hex").slice(0, 8);
550
696
  function readMcpRegistrations(stateDir) {
551
697
  try {
552
- const j = JSON.parse(readFileSync4(fileFor(stateDir), "utf8"));
698
+ const j = JSON.parse(readFileSync5(fileFor(stateDir), "utf8"));
553
699
  return Array.isArray(j) ? j : [];
554
700
  } catch {
555
701
  return [];
@@ -557,8 +703,8 @@ function readMcpRegistrations(stateDir) {
557
703
  }
558
704
  function recordMcpRegistration(stateDir, r, now = /* @__PURE__ */ new Date()) {
559
705
  const rest = readMcpRegistrations(stateDir).filter((x) => !(x.harness === r.harness && x.scope === r.scope && x.name === r.name));
560
- mkdirSync4(stateDir, { recursive: true });
561
- writeFileSync3(fileFor(stateDir), `${JSON.stringify([...rest, { ...r, at: now.toISOString() }], null, 2)}
706
+ mkdirSync5(stateDir, { recursive: true });
707
+ writeFileSync4(fileFor(stateDir), `${JSON.stringify([...rest, { ...r, at: now.toISOString() }], null, 2)}
562
708
  `, { mode: 384 });
563
709
  }
564
710
  function findMcpRegistration(stateDir, r) {
@@ -581,7 +727,7 @@ function runClaude(bin, args) {
581
727
  }
582
728
  function userScopeEntry(home) {
583
729
  try {
584
- const j = JSON.parse(readFileSync5(join5(home, ".claude.json"), "utf8"));
730
+ const j = JSON.parse(readFileSync6(join6(home, ".claude.json"), "utf8"));
585
731
  return j.mcpServers?.[MCP_SERVER_NAME];
586
732
  } catch {
587
733
  return void 0;
@@ -665,8 +811,8 @@ function removeClaudeMcp(o) {
665
811
  }
666
812
 
667
813
  // src/launcher.ts
668
- import { basename, join as join6 } from "path";
669
- import { existsSync as existsSync5, readFileSync as readFileSync6, readdirSync as readdirSync2 } from "fs";
814
+ import { basename, join as join7 } from "path";
815
+ import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync2 } from "fs";
670
816
  import { homedir as homedir2 } from "os";
671
817
  function isOriLauncher(spec) {
672
818
  return !!spec.launcher && basename(spec.launcher) === "ori";
@@ -675,7 +821,7 @@ function isOmlxLauncher(spec) {
675
821
  return !!spec.launcher && basename(spec.launcher) === "omlx";
676
822
  }
677
823
  function defaultOmlxBin(home = homedir2()) {
678
- return join6(home, ".omlx", "bin", "omlx");
824
+ return join7(home, ".omlx", "bin", "omlx");
679
825
  }
680
826
  function launcherExecutable(spec, home) {
681
827
  if (!spec.launcher) return void 0;
@@ -728,8 +874,8 @@ function omlxModelPresent(model, dirs) {
728
874
  });
729
875
  }
730
876
  function readOmlxSettings(home = homedir2()) {
731
- const p = join6(home, ".omlx", "settings.json");
732
- return existsSync5(p) ? parseOmlxSettings(readFileSync6(p, "utf8")) : void 0;
877
+ const p = join7(home, ".omlx", "settings.json");
878
+ return existsSync6(p) ? parseOmlxSettings(readFileSync7(p, "utf8")) : void 0;
733
879
  }
734
880
 
735
881
  // src/capability.ts
@@ -793,7 +939,7 @@ function describeClaudeAuth(spec, env = process.env, home = homedir3()) {
793
939
  if (spec.auth === "workspace") return `workspace${spec.inference ? ` (${new URL(spec.inference.baseUrl).host})` : " (endpoint resolved per route at run time)"}`;
794
940
  let fromSettings;
795
941
  try {
796
- const parsed = JSON.parse(readFileSync7(join7(home, ".claude", "settings.json"), "utf8"));
942
+ const parsed = JSON.parse(readFileSync8(join8(home, ".claude", "settings.json"), "utf8"));
797
943
  const e = parsed.env ?? {};
798
944
  const on = (k) => e[k] !== void 0 && e[k] !== "" && e[k] !== "0";
799
945
  if (on("CLAUDE_CODE_USE_VERTEX")) fromSettings = "vertex from ~/.claude/settings.json";
@@ -897,12 +1043,12 @@ var claudeAdapter = {
897
1043
  // src/adapters/codex.ts
898
1044
  import { spawnSync as spawnSync2 } from "child_process";
899
1045
  import { homedir as homedir4 } from "os";
900
- import { join as join8 } from "path";
901
- import { rmSync as rmSync4, writeFileSync as writeFileSync5 } from "fs";
1046
+ import { join as join9 } from "path";
1047
+ import { rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
902
1048
 
903
1049
  // src/pair-files.ts
904
- import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync4, rmSync as rmSync3 } from "fs";
905
- import { dirname as dirname3 } from "path";
1050
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync5, rmSync as rmSync3 } from "fs";
1051
+ import { dirname as dirname4 } from "path";
906
1052
  var PAIR_BLOCK_START = "<!-- crew:pair start -->";
907
1053
  var PAIR_BLOCK_END = "<!-- crew:pair end -->";
908
1054
  var PAIR_KEY_VAR = "CREW_API_KEY";
@@ -942,11 +1088,11 @@ function removePairBlock(text2) {
942
1088
  return text2.replace(new RegExp(`\\n*${BLOCK_RE.source}`), "\n").replace(/^\n+/, "").replace(/\n{3,}/g, "\n\n");
943
1089
  }
944
1090
  function readIfExists(path) {
945
- return existsSync6(path) ? readFileSync8(path, "utf8") : void 0;
1091
+ return existsSync7(path) ? readFileSync9(path, "utf8") : void 0;
946
1092
  }
947
1093
  function writeFileMkdir(path, text2) {
948
- mkdirSync5(dirname3(path), { recursive: true });
949
- writeFileSync4(path, text2);
1094
+ mkdirSync6(dirname4(path), { recursive: true });
1095
+ writeFileSync5(path, text2);
950
1096
  }
951
1097
  function installBlockFile(path, harness, dryRun) {
952
1098
  const before = readIfExists(path) ?? "";
@@ -962,7 +1108,7 @@ function removeBlockFile(path, dryRun) {
962
1108
  if (dryRun) return true;
963
1109
  const after = removePairBlock(before);
964
1110
  if (after.trim() === "") rmSync3(path);
965
- else writeFileSync4(path, after);
1111
+ else writeFileSync5(path, after);
966
1112
  return true;
967
1113
  }
968
1114
  function blockVerdict(path, harness) {
@@ -1089,8 +1235,8 @@ function codexTable(url) {
1089
1235
  return [`[mcp_servers.${SERVER}]`, `url = ${tomlString(url)}`, `bearer_token_env_var = ${tomlString(PAIR_KEY_VAR)}`];
1090
1236
  }
1091
1237
  var codexPaths = (home) => ({
1092
- instructions: join8(home, ".codex", "AGENTS.md"),
1093
- config: join8(home, ".codex", "config.toml")
1238
+ instructions: join9(home, ".codex", "AGENTS.md"),
1239
+ config: join9(home, ".codex", "config.toml")
1094
1240
  });
1095
1241
  function codexMcpState(config, url) {
1096
1242
  const lines2 = config.split("\n");
@@ -1141,7 +1287,7 @@ var codexPair = {
1141
1287
  if (hadMcp && !o.dryRun) {
1142
1288
  const after = removeTomlTable(config, `mcp_servers.${SERVER}`);
1143
1289
  if (after === "") rmSync4(p.config);
1144
- else writeFileSync5(p.config, after);
1290
+ else writeFileSync6(p.config, after);
1145
1291
  }
1146
1292
  const what = hadBlock && hadMcp ? `the Pair block and ${SERVER} server` : hadBlock ? "the Pair block" : `the ${SERVER} server`;
1147
1293
  return hadBlock || hadMcp ? `pair(codex): ${o.dryRun ? "(dry run) would remove" : "removed"} ${what}${kept}` : `pair(codex): nothing to remove${kept}`;
@@ -1237,8 +1383,8 @@ var codexAdapter = {
1237
1383
 
1238
1384
  // src/pair-install.ts
1239
1385
  var geminiPaths = (home) => ({
1240
- instructions: join9(home, ".gemini", "GEMINI.md"),
1241
- config: join9(home, ".gemini", "settings.json")
1386
+ instructions: join10(home, ".gemini", "GEMINI.md"),
1387
+ config: join10(home, ".gemini", "settings.json")
1242
1388
  });
1243
1389
  function readGeminiSettings(path) {
1244
1390
  const text2 = readIfExists(path);
@@ -1326,7 +1472,7 @@ var geminiPair = {
1326
1472
  if (Object.keys(rest).length > 0) next.mcpServers = rest;
1327
1473
  else delete next.mcpServers;
1328
1474
  if (Object.keys(next).length === 0) rmSync5(p.config);
1329
- else writeFileSync6(p.config, `${JSON.stringify(next, null, 2)}
1475
+ else writeFileSync7(p.config, `${JSON.stringify(next, null, 2)}
1330
1476
  `);
1331
1477
  }
1332
1478
  const what = hadBlock && hadMcp ? `the Pair block and ${SERVER} server` : hadBlock ? "the Pair block" : `the ${SERVER} server`;
@@ -1337,9 +1483,9 @@ var VSCODE_FLAVOURS = ["Code", "Code - Insiders"];
1337
1483
  function vscodeUserDirs(o) {
1338
1484
  const home = o.home ?? homedir5();
1339
1485
  const platform2 = o.platform ?? process.platform;
1340
- const root = platform2 === "darwin" ? join9(home, "Library", "Application Support") : platform2 === "win32" ? process.env.APPDATA ?? join9(home, "AppData", "Roaming") : join9(home, ".config");
1341
- const all = VSCODE_FLAVOURS.map((f) => join9(root, f, "User"));
1342
- const present = all.filter((d) => existsSync7(d));
1486
+ const root = platform2 === "darwin" ? join10(home, "Library", "Application Support") : platform2 === "win32" ? process.env.APPDATA ?? join10(home, "AppData", "Roaming") : join10(home, ".config");
1487
+ const all = VSCODE_FLAVOURS.map((f) => join10(root, f, "User"));
1488
+ const present = all.filter((d) => existsSync8(d));
1343
1489
  return present.length > 0 ? present : [all[0]];
1344
1490
  }
1345
1491
  var COPILOT_FRONTMATTER = '---\napplyTo: "**"\n---\n\n';
@@ -1377,7 +1523,7 @@ function removeCopilotInstructions(path, dryRun) {
1377
1523
  if (dryRun) return true;
1378
1524
  const after = removePairBlock(before);
1379
1525
  if (after.trim() === "" || after.trim() === COPILOT_FRONTMATTER.trim()) rmSync5(path);
1380
- else writeFileSync6(path, after);
1526
+ else writeFileSync7(path, after);
1381
1527
  return true;
1382
1528
  }
1383
1529
  var copilotPair = {
@@ -1386,8 +1532,8 @@ var copilotPair = {
1386
1532
  installPair(o) {
1387
1533
  const lines2 = [];
1388
1534
  for (const dir of vscodeUserDirs(o)) {
1389
- const mcpPath = join9(dir, "mcp.json");
1390
- const instrPath = join9(dir, "prompts", "crew-pair.instructions.md");
1535
+ const mcpPath = join10(dir, "mcp.json");
1536
+ const instrPath = join10(dir, "prompts", "crew-pair.instructions.md");
1391
1537
  const settings = readVscodeMcp(mcpPath);
1392
1538
  if (!settings) {
1393
1539
  lines2.push(`pair(copilot): ${mcpPath} is not a plain JSON object (comments are not supported) \u2014 fix it by hand; nothing was written there`);
@@ -1415,8 +1561,8 @@ var copilotPair = {
1415
1561
  },
1416
1562
  verifyPair(o) {
1417
1563
  const verdicts = vscodeUserDirs(o).map((dir) => {
1418
- const instrPath = join9(dir, "prompts", "crew-pair.instructions.md");
1419
- const mcpPath = join9(dir, "mcp.json");
1564
+ const instrPath = join10(dir, "prompts", "crew-pair.instructions.md");
1565
+ const mcpPath = join10(dir, "mcp.json");
1420
1566
  const b = blockVerdict(instrPath, "copilot");
1421
1567
  if (!b.ok) return b;
1422
1568
  const settings = readVscodeMcp(mcpPath);
@@ -1431,8 +1577,8 @@ var copilotPair = {
1431
1577
  removePair(o) {
1432
1578
  const lines2 = [];
1433
1579
  for (const dir of vscodeUserDirs(o)) {
1434
- const mcpPath = join9(dir, "mcp.json");
1435
- const hadBlock = removeCopilotInstructions(join9(dir, "prompts", "crew-pair.instructions.md"), o.dryRun);
1580
+ const mcpPath = join10(dir, "mcp.json");
1581
+ const hadBlock = removeCopilotInstructions(join10(dir, "prompts", "crew-pair.instructions.md"), o.dryRun);
1436
1582
  const settings = readVscodeMcp(mcpPath);
1437
1583
  const st = settings ? copilotMcpState(settings, o.url) : { state: "missing" };
1438
1584
  const hadMcp = st.state === "registered";
@@ -1443,7 +1589,7 @@ var copilotPair = {
1443
1589
  if (Object.keys(rest).length > 0) next.servers = rest;
1444
1590
  else delete next.servers;
1445
1591
  if (Object.keys(next).length === 0) rmSync5(mcpPath);
1446
- else writeFileSync6(mcpPath, `${JSON.stringify(next, null, 2)}
1592
+ else writeFileSync7(mcpPath, `${JSON.stringify(next, null, 2)}
1447
1593
  `);
1448
1594
  }
1449
1595
  const what = hadBlock && hadMcp ? `the Pair instructions and ${SERVER} server` : hadBlock ? "the Pair instructions" : `the ${SERVER} server`;
@@ -1692,9 +1838,9 @@ function resolveSeat(ship, route2, role, persona = {}, repo) {
1692
1838
  }
1693
1839
 
1694
1840
  // src/forges.ts
1695
- import { execFileSync as execFileSync2 } from "child_process";
1696
- import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync9, rmSync as rmSync6, statSync as statSync3, writeFileSync as writeFileSync7 } from "fs";
1697
- import { dirname as dirname4, join as join10 } from "path";
1841
+ import { execFileSync as execFileSync3 } from "child_process";
1842
+ import { chmodSync, existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync10, rmSync as rmSync6, statSync as statSync3, writeFileSync as writeFileSync8 } from "fs";
1843
+ import { dirname as dirname5, join as join11 } from "path";
1698
1844
  var ForgeError = class extends Error {
1699
1845
  };
1700
1846
  var DEFAULT_FORGE_USERNAME = "x-access-token";
@@ -1722,7 +1868,7 @@ function forgeDescriptorFor(host) {
1722
1868
  }
1723
1869
  var DEFAULT_KEYCHAIN_SERVICE = "crew-forge";
1724
1870
  function defaultTokenFilePath(stateDir, host) {
1725
- return join10(stateDir, "forges", `${host.replace(/[^a-z0-9.-]/gi, "_")}.token`);
1871
+ return join11(stateDir, "forges", `${host.replace(/[^a-z0-9.-]/gi, "_")}.token`);
1726
1872
  }
1727
1873
  var envSource = {
1728
1874
  read: (s, c) => c.env[s.var]?.trim() || null,
@@ -1731,19 +1877,19 @@ var envSource = {
1731
1877
  var fileSource = {
1732
1878
  read(s, c) {
1733
1879
  const path = s.path ?? defaultTokenFilePath(c.stateDir, c.host);
1734
- if (!existsSync8(path)) return null;
1880
+ if (!existsSync9(path)) return null;
1735
1881
  const mode = statSync3(path).mode & 511;
1736
1882
  if (mode & 63) {
1737
1883
  throw new ForgeError(
1738
1884
  `${path} is mode ${mode.toString(8).padStart(3, "0")}; a forge token file must be owner-only (chmod 600 ${path})`
1739
1885
  );
1740
1886
  }
1741
- return readFileSync9(path, "utf8").trim() || null;
1887
+ return readFileSync10(path, "utf8").trim() || null;
1742
1888
  },
1743
1889
  write(s, token, c) {
1744
1890
  const path = s.path ?? defaultTokenFilePath(c.stateDir, c.host);
1745
- mkdirSync6(dirname4(path), { recursive: true, mode: 448 });
1746
- writeFileSync7(path, `${token}
1891
+ mkdirSync7(dirname5(path), { recursive: true, mode: 448 });
1892
+ writeFileSync8(path, `${token}
1747
1893
  `, { mode: 384 });
1748
1894
  chmodSync(path, 384);
1749
1895
  },
@@ -1757,13 +1903,13 @@ var keychainSource = {
1757
1903
  read(s, c) {
1758
1904
  try {
1759
1905
  if (process.platform === "darwin") {
1760
- return execFileSync2(
1906
+ return execFileSync3(
1761
1907
  "security",
1762
1908
  ["find-generic-password", "-s", keychainService(s), "-a", c.host, "-w"],
1763
1909
  { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
1764
1910
  ).trim() || null;
1765
1911
  }
1766
- return execFileSync2(
1912
+ return execFileSync3(
1767
1913
  "secret-tool",
1768
1914
  ["lookup", "service", keychainService(s), "account", c.host],
1769
1915
  { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
@@ -1776,13 +1922,13 @@ var keychainSource = {
1776
1922
  try {
1777
1923
  if (process.platform === "darwin") {
1778
1924
  const q2 = (v) => `"${v.replace(/(["\\])/g, "\\$1")}"`;
1779
- execFileSync2("security", ["-i"], {
1925
+ execFileSync3("security", ["-i"], {
1780
1926
  input: `add-generic-password -U -s ${q2(keychainService(s))} -a ${q2(c.host)} -w ${q2(token)}
1781
1927
  `,
1782
1928
  stdio: ["pipe", "ignore", "pipe"]
1783
1929
  });
1784
1930
  } else {
1785
- execFileSync2(
1931
+ execFileSync3(
1786
1932
  "secret-tool",
1787
1933
  ["store", "--label", `crew ${c.host}`, "service", keychainService(s), "account", c.host],
1788
1934
  { input: token, stdio: ["pipe", "ignore", "pipe"] }
@@ -1795,9 +1941,9 @@ var keychainSource = {
1795
1941
  erase(s, c) {
1796
1942
  try {
1797
1943
  if (process.platform === "darwin") {
1798
- execFileSync2("security", ["delete-generic-password", "-s", keychainService(s), "-a", c.host], { stdio: "ignore" });
1944
+ execFileSync3("security", ["delete-generic-password", "-s", keychainService(s), "-a", c.host], { stdio: "ignore" });
1799
1945
  } else {
1800
- execFileSync2("secret-tool", ["clear", "service", keychainService(s), "account", c.host], { stdio: "ignore" });
1946
+ execFileSync3("secret-tool", ["clear", "service", keychainService(s), "account", c.host], { stdio: "ignore" });
1801
1947
  }
1802
1948
  } catch {
1803
1949
  }
@@ -1877,10 +2023,10 @@ function answerCredential(action, input, forges, stateDir, env = process.env) {
1877
2023
  password=${token}
1878
2024
  `;
1879
2025
  }
1880
- var missesPath = (stateDir) => join10(stateDir, "forge-misses.json");
2026
+ var missesPath = (stateDir) => join11(stateDir, "forge-misses.json");
1881
2027
  function readForgeMisses(stateDir) {
1882
2028
  try {
1883
- const raw = JSON.parse(readFileSync9(missesPath(stateDir), "utf8"));
2029
+ const raw = JSON.parse(readFileSync10(missesPath(stateDir), "utf8"));
1884
2030
  return Object.entries(raw).filter(([, t]) => typeof t === "string").map(([host, lastMissAt]) => ({ host, lastMissAt })).sort((a, b) => a.host.localeCompare(b.host));
1885
2031
  } catch {
1886
2032
  return [];
@@ -1895,8 +2041,8 @@ function noteForgeMiss(stateDir, host, at) {
1895
2041
  if (!(bare in cur)) return;
1896
2042
  delete cur[bare];
1897
2043
  } else cur[bare] = at;
1898
- mkdirSync6(stateDir, { recursive: true });
1899
- writeFileSync7(missesPath(stateDir), JSON.stringify(cur), { mode: 384 });
2044
+ mkdirSync7(stateDir, { recursive: true });
2045
+ writeFileSync8(missesPath(stateDir), JSON.stringify(cur), { mode: 384 });
1900
2046
  } catch {
1901
2047
  }
1902
2048
  }
@@ -1998,8 +2144,8 @@ import { hostFromUrl } from "@tablation/client";
1998
2144
 
1999
2145
  // src/secret-store.ts
2000
2146
  import { createHash as createHash2 } from "crypto";
2001
- import { accessSync, constants, existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync10, renameSync as renameSync2, rmSync as rmSync7, statSync as statSync4, writeFileSync as writeFileSync8, chmodSync as chmodSync2 } from "fs";
2002
- import { join as join11 } from "path";
2147
+ import { accessSync, constants, existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync11, renameSync as renameSync2, rmSync as rmSync7, statSync as statSync4, writeFileSync as writeFileSync9, chmodSync as chmodSync2 } from "fs";
2148
+ import { join as join12 } from "path";
2003
2149
  import { getSessionStore as getOsSessionStore } from "@tablation/client";
2004
2150
  var SECRET_STORE_MODES = ["auto", "os", "file"];
2005
2151
  function isSecretStoreMode(v) {
@@ -2010,7 +2156,7 @@ function configureSecretStore(c) {
2010
2156
  configured = c;
2011
2157
  }
2012
2158
  function keysDir(stateDir) {
2013
- return join11(stateDir, "keys");
2159
+ return join12(stateDir, "keys");
2014
2160
  }
2015
2161
  function entryName(host, slug2) {
2016
2162
  return `${createHash2("sha256").update(`${host}\0${slug2}`).digest("hex")}.json`;
@@ -2021,34 +2167,34 @@ var FileSessionStore = class {
2021
2167
  this.dir = dir;
2022
2168
  }
2023
2169
  ensureDir() {
2024
- mkdirSync7(this.dir, { recursive: true, mode: 448 });
2170
+ mkdirSync8(this.dir, { recursive: true, mode: 448 });
2025
2171
  if ((statSync4(this.dir).mode & 63) !== 0) chmodSync2(this.dir, 448);
2026
2172
  }
2027
2173
  async get(host, slug2) {
2028
- const file = join11(this.dir, entryName(host, slug2));
2029
- if (!existsSync9(this.dir)) return void 0;
2174
+ const file = join12(this.dir, entryName(host, slug2));
2175
+ if (!existsSync10(this.dir)) return void 0;
2030
2176
  accessSync(this.dir, constants.R_OK | constants.X_OK);
2031
- if (!existsSync9(file)) return void 0;
2032
- return JSON.parse(readFileSync10(file, "utf8"));
2177
+ if (!existsSync10(file)) return void 0;
2178
+ return JSON.parse(readFileSync11(file, "utf8"));
2033
2179
  }
2034
2180
  async set(host, slug2, session) {
2035
2181
  this.ensureDir();
2036
- const file = join11(this.dir, entryName(host, slug2));
2182
+ const file = join12(this.dir, entryName(host, slug2));
2037
2183
  const tmp = `${file}.${process.pid}.tmp`;
2038
- writeFileSync8(tmp, JSON.stringify(session), { mode: 384 });
2184
+ writeFileSync9(tmp, JSON.stringify(session), { mode: 384 });
2039
2185
  renameSync2(tmp, file);
2040
2186
  }
2041
2187
  async clear(host, slug2) {
2042
- if (!existsSync9(this.dir)) return;
2043
- rmSync7(join11(this.dir, entryName(host, slug2)), { force: true });
2188
+ if (!existsSync10(this.dir)) return;
2189
+ rmSync7(join12(this.dir, entryName(host, slug2)), { force: true });
2044
2190
  }
2045
2191
  /** Whether a key could be written here right now, without leaving anything behind. */
2046
2192
  writable() {
2047
2193
  try {
2048
2194
  this.ensureDir();
2049
2195
  accessSync(this.dir, constants.R_OK | constants.W_OK | constants.X_OK);
2050
- const probe = join11(this.dir, `.probe.${process.pid}`);
2051
- writeFileSync8(probe, "", { mode: 384 });
2196
+ const probe = join12(this.dir, `.probe.${process.pid}`);
2197
+ writeFileSync9(probe, "", { mode: 384 });
2052
2198
  rmSync7(probe, { force: true });
2053
2199
  return { ok: true };
2054
2200
  } catch (e) {
@@ -2099,11 +2245,11 @@ function osStoreReachable(env = process.env, platform2 = process.platform, hasBi
2099
2245
  if (platform2 !== "linux") return true;
2100
2246
  if (!hasBinary("secret-tool")) return false;
2101
2247
  if (env.DBUS_SESSION_BUS_ADDRESS) return true;
2102
- return !!env.XDG_RUNTIME_DIR && existsSync9(join11(env.XDG_RUNTIME_DIR, "bus"));
2248
+ return !!env.XDG_RUNTIME_DIR && existsSync10(join12(env.XDG_RUNTIME_DIR, "bus"));
2103
2249
  }
2104
2250
  function onPath(name) {
2105
2251
  for (const dir of (process.env.PATH ?? "").split(":")) {
2106
- if (dir && existsSync9(join11(dir, name))) return true;
2252
+ if (dir && existsSync10(join12(dir, name))) return true;
2107
2253
  }
2108
2254
  return false;
2109
2255
  }
@@ -2170,8 +2316,8 @@ function explain(ship, required) {
2170
2316
  }
2171
2317
 
2172
2318
  // src/repo-config.ts
2173
- import { readFileSync as readFileSync11, existsSync as existsSync10 } from "fs";
2174
- import { join as join12, basename as basename3 } from "path";
2319
+ import { readFileSync as readFileSync12, existsSync as existsSync11 } from "fs";
2320
+ import { join as join13, basename as basename3 } from "path";
2175
2321
  import { parse } from "yaml";
2176
2322
 
2177
2323
  // src/sandbox-policy.ts
@@ -2382,6 +2528,8 @@ var HOOK_NAMES = [
2382
2528
  "isolate",
2383
2529
  "handoff",
2384
2530
  "preview",
2531
+ "intel",
2532
+ "map",
2385
2533
  "ci_status",
2386
2534
  "version",
2387
2535
  "bump",
@@ -2452,8 +2600,8 @@ var MODES = ["local", "integrate", "ci_manual", "ci_auto", "external"];
2452
2600
  var PROVIDERS = ["github", "buildkite", "other", "none"];
2453
2601
  function findRepoConfig(dir) {
2454
2602
  for (const name of REPO_CONFIG_FILENAMES) {
2455
- const p = join12(dir, name);
2456
- if (existsSync10(p)) return p;
2603
+ const p = join13(dir, name);
2604
+ if (existsSync11(p)) return p;
2457
2605
  }
2458
2606
  return null;
2459
2607
  }
@@ -2733,7 +2881,7 @@ function parseRepoConfig(text2, file) {
2733
2881
  function loadRepoConfig(dir) {
2734
2882
  const file = findRepoConfig(dir);
2735
2883
  if (!file) return null;
2736
- return parseRepoConfig(readFileSync11(file, "utf8"), file);
2884
+ return parseRepoConfig(readFileSync12(file, "utf8"), file);
2737
2885
  }
2738
2886
  function hookLabel(cfg, hook2) {
2739
2887
  return cfg.labels[hook2] ?? cfg.hooks[hook2] ?? hook2;
@@ -3014,6 +3162,8 @@ var ConfigError = class extends Error {
3014
3162
  };
3015
3163
  var DEFAULT_MAX_PERISCOPES = 3;
3016
3164
  var DEFAULT_PERISCOPE_IDLE_MINUTES = 120;
3165
+ var DEFAULT_INTEL_STARTUP_SECONDS = 20;
3166
+ var DEFAULT_MAP_TOKEN_BUDGET = 1e3;
3017
3167
  function parseCloneScheme(v) {
3018
3168
  if (v === void 0 || v === null || v === "") return void 0;
3019
3169
  if (v === "ssh" || v === "https") return v;
@@ -3106,6 +3256,8 @@ var SHIP_KEYS = /* @__PURE__ */ new Set([
3106
3256
  "previewHost",
3107
3257
  "maxPeriscopes",
3108
3258
  "periscopeIdleMinutes",
3259
+ "intelStartupSeconds",
3260
+ "mapTokenBudget",
3109
3261
  "secretStore",
3110
3262
  "env",
3111
3263
  "runner",
@@ -3258,11 +3410,11 @@ function expand(p, base) {
3258
3410
  return isAbsolute2(out) ? out : resolve2(base, out);
3259
3411
  }
3260
3412
  function resolvePromptsDir(promptSet, crewHome, base) {
3261
- if (!promptSet) return join13(crewHome, "prompts", "default");
3413
+ if (!promptSet) return join14(crewHome, "prompts", "default");
3262
3414
  if (promptSet.includes("/") || promptSet.startsWith(".") || promptSet.startsWith("~")) {
3263
3415
  return expand(promptSet, base);
3264
3416
  }
3265
- return join13(crewHome, "prompts", promptSet);
3417
+ return join14(crewHome, "prompts", promptSet);
3266
3418
  }
3267
3419
  var Missing = class {
3268
3420
  paths = [];
@@ -3286,7 +3438,7 @@ var Missing = class {
3286
3438
  };
3287
3439
  function crewVersion(crewHome) {
3288
3440
  try {
3289
- const pkg = JSON.parse(readFileSync12(join13(crewHome, "package.json"), "utf8"));
3441
+ const pkg = JSON.parse(readFileSync13(join14(crewHome, "package.json"), "utf8"));
3290
3442
  return pkg.version ?? "0.0.0";
3291
3443
  } catch {
3292
3444
  return "0.0.0";
@@ -3296,26 +3448,26 @@ function configSearchPath(crewHome) {
3296
3448
  if (process.env.CREW_CONFIG) return [resolve2(process.env.CREW_CONFIG)];
3297
3449
  const xdg = process.env.XDG_CONFIG_HOME;
3298
3450
  return [
3299
- ...xdg ? [join13(xdg, "crew", "crew.yaml")] : [],
3300
- join13(homedir6(), ".config", "crew", "crew.yaml"),
3451
+ ...xdg ? [join14(xdg, "crew", "crew.yaml")] : [],
3452
+ join14(homedir6(), ".config", "crew", "crew.yaml"),
3301
3453
  resolve2(crewHome, "crew.yaml")
3302
3454
  ];
3303
3455
  }
3304
3456
  function findConfigFile(crewHome) {
3305
3457
  const candidates2 = configSearchPath(crewHome);
3306
- return candidates2.find((c) => existsSync11(c)) ?? candidates2[candidates2.length - 1];
3458
+ return candidates2.find((c) => existsSync12(c)) ?? candidates2[candidates2.length - 1];
3307
3459
  }
3308
- var defaultConfigPath = () => join13(process.env.XDG_CONFIG_HOME ?? join13(homedir6(), ".config"), "crew", "crew.yaml");
3460
+ var defaultConfigPath = () => join14(process.env.XDG_CONFIG_HOME ?? join14(homedir6(), ".config"), "crew", "crew.yaml");
3309
3461
  function routeSlug(route2) {
3310
3462
  return route2.replace(/\//g, "-");
3311
3463
  }
3312
3464
  function resolvedPathFor(stateDir, route2) {
3313
3465
  const [workspace, project] = splitRoute(route2);
3314
- return join13(stateDir, "resolved", workspace, `${project}.json`);
3466
+ return join14(stateDir, "resolved", workspace, `${project}.json`);
3315
3467
  }
3316
3468
  function apiKeyPathFor(stateDir, route2) {
3317
3469
  const [workspace, project] = splitRoute(route2);
3318
- return join13(stateDir, "keys", workspace, `${project}.env`);
3470
+ return join14(stateDir, "keys", workspace, `${project}.env`);
3319
3471
  }
3320
3472
  function splitRoute(route2) {
3321
3473
  const parts = route2.split("/");
@@ -3326,9 +3478,9 @@ function splitRoute(route2) {
3326
3478
  }
3327
3479
  function readResolvedFile(stateDir, route2) {
3328
3480
  const path = resolvedPathFor(stateDir, route2);
3329
- if (!existsSync11(path)) return void 0;
3481
+ if (!existsSync12(path)) return void 0;
3330
3482
  try {
3331
- return JSON.parse(readFileSync12(path, "utf8"));
3483
+ return JSON.parse(readFileSync13(path, "utf8"));
3332
3484
  } catch (e) {
3333
3485
  throw new ConfigError(`${path}: not valid JSON \u2014 ${e.message}`);
3334
3486
  }
@@ -3389,23 +3541,23 @@ function parseResolved(raw, m, where) {
3389
3541
  }
3390
3542
  function loadConfig(crewHome, configFile, opts = {}) {
3391
3543
  const file = configFile ?? findConfigFile(crewHome);
3392
- if (opts.text === void 0 && !existsSync11(file)) {
3544
+ if (opts.text === void 0 && !existsSync12(file)) {
3393
3545
  throw new ConfigError(
3394
3546
  `no config found. Looked in:
3395
3547
  ` + configSearchPath(crewHome).map((c) => ` - ${c}`).join("\n") + `
3396
3548
 
3397
3549
  Create the first of those:
3398
- mkdir -p ${dirname5(defaultConfigPath())} && cp ${join13(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
3550
+ mkdir -p ${dirname6(defaultConfigPath())} && cp ${join14(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
3399
3551
  );
3400
3552
  }
3401
3553
  let raw;
3402
3554
  try {
3403
- raw = parse2(opts.text ?? readFileSync12(file, "utf8"));
3555
+ raw = parse2(opts.text ?? readFileSync13(file, "utf8"));
3404
3556
  } catch (e) {
3405
3557
  throw new ConfigError(`${file}: not valid YAML \u2014 ${e.message}`);
3406
3558
  }
3407
3559
  if (!raw || typeof raw !== "object") throw new ConfigError(`${file} is empty or not a mapping`);
3408
- const base = dirname5(file);
3560
+ const base = dirname6(file);
3409
3561
  const missing = new Missing();
3410
3562
  rejectUnknownKeys(raw, /* @__PURE__ */ new Set(["ship", "routes"]), "top-level", file);
3411
3563
  const shipRaw = raw.ship ?? {};
@@ -3454,6 +3606,14 @@ Create the first of those:
3454
3606
  if (!Number.isInteger(periscopeIdleMinutes) || periscopeIdleMinutes < 1) {
3455
3607
  missing.add(`ship.periscopeIdleMinutes must be a positive integer (got "${shipRaw.periscopeIdleMinutes}")`);
3456
3608
  }
3609
+ const intelStartupSeconds = shipRaw.intelStartupSeconds === void 0 ? DEFAULT_INTEL_STARTUP_SECONDS : Number(shipRaw.intelStartupSeconds);
3610
+ if (!Number.isInteger(intelStartupSeconds) || intelStartupSeconds < 1) {
3611
+ missing.add(`ship.intelStartupSeconds must be a positive integer (got "${shipRaw.intelStartupSeconds}")`);
3612
+ }
3613
+ const mapTokenBudget = shipRaw.mapTokenBudget === void 0 ? DEFAULT_MAP_TOKEN_BUDGET : Number(shipRaw.mapTokenBudget);
3614
+ if (!Number.isInteger(mapTokenBudget) || mapTokenBudget < 1) {
3615
+ missing.add(`ship.mapTokenBudget must be a positive integer (got "${shipRaw.mapTokenBudget}")`);
3616
+ }
3457
3617
  const secretStoreRaw = shipRaw.secretStore === void 0 ? "auto" : shipRaw.secretStore;
3458
3618
  if (!isSecretStoreMode(secretStoreRaw)) {
3459
3619
  missing.add(`ship.secretStore must be one of ${SECRET_STORE_MODES.join(" | ")} (got "${String(shipRaw.secretStore)}")`);
@@ -3519,7 +3679,7 @@ Create the first of those:
3519
3679
  useNvm: shipRaw.useNvm !== false,
3520
3680
  nvmSh: shipRaw.nvmSh,
3521
3681
  stateDir,
3522
- logFile: expand(shipRaw.logFile ?? join13(tmpdir(), "crew.log"), base),
3682
+ logFile: expand(shipRaw.logFile ?? join14(tmpdir(), "crew.log"), base),
3523
3683
  userAgent: shipRaw.userAgent ?? `Mozilla/5.0 CrewAgent/${crewVersion(crewHome)}`,
3524
3684
  maxConcurrentAgents,
3525
3685
  maxConcurrentAgentsSet,
@@ -3537,6 +3697,8 @@ Create the first of those:
3537
3697
  previewHost: shipRaw.previewHost ? String(shipRaw.previewHost).trim() || void 0 : void 0,
3538
3698
  maxPeriscopes,
3539
3699
  periscopeIdleMinutes,
3700
+ intelStartupSeconds,
3701
+ mapTokenBudget,
3540
3702
  secretStore,
3541
3703
  env: parseShipEnv(shipRaw.env)
3542
3704
  },
@@ -3699,7 +3861,7 @@ function parseOneRoute(c, where, file, base, stateDir, raw, missing, crewHome, t
3699
3861
  const hasRepos = Object.keys(repoDirs).length > 0;
3700
3862
  const reposBasePath = expand(String(c?.reposBasePath ?? raw.ship?.reposBasePath ?? DEFAULT_REPOS_BASE_PATH), base);
3701
3863
  const dirIsCloneBase = !c?.dir && !hasRepos && !Array.isArray(c?.repos);
3702
- const dir = c?.dir ? expand(String(c.dir), base) : hasRepos ? Object.values(repoDirs)[0] : dirIsCloneBase ? join13(reposBasePath, splitRoute(route2)[0]) : "";
3864
+ const dir = c?.dir ? expand(String(c.dir), base) : hasRepos ? Object.values(repoDirs)[0] : dirIsCloneBase ? join14(reposBasePath, splitRoute(route2)[0]) : "";
3703
3865
  const weight = c?.weight === void 0 ? void 0 : Number(c.weight);
3704
3866
  if (weight !== void 0 && !(Number.isFinite(weight) && weight > 0)) {
3705
3867
  missing.add(`${where}.weight must be a positive number (got "${c?.weight}")`);
@@ -3762,7 +3924,7 @@ function locateDir(cfg, dir, commonDirOf) {
3762
3924
  const direct = find(target);
3763
3925
  if (direct) return direct;
3764
3926
  const common = commonDirOf?.(target);
3765
- return common ? find(dirname5(resolve2(target, common))) : void 0;
3927
+ return common ? find(dirname6(resolve2(target, common))) : void 0;
3766
3928
  }
3767
3929
  function findRoute(cfg, route2) {
3768
3930
  if (!route2) {
@@ -3797,8 +3959,8 @@ function repoIdForName(r, name) {
3797
3959
  }
3798
3960
  function resolveApiKey(r) {
3799
3961
  if (r.apiKey) return r.apiKey;
3800
- if (r.apiKeyFile && r.apiKeyVar && existsSync11(r.apiKeyFile)) {
3801
- for (const line of readFileSync12(r.apiKeyFile, "utf8").split("\n")) {
3962
+ if (r.apiKeyFile && r.apiKeyVar && existsSync12(r.apiKeyFile)) {
3963
+ for (const line of readFileSync13(r.apiKeyFile, "utf8").split("\n")) {
3802
3964
  const m = line.match(new RegExp(`^${r.apiKeyVar}=(.*)$`));
3803
3965
  if (m) return (m[1] ?? "").trim().replace(/^["']|["']$/g, "");
3804
3966
  }
@@ -3811,9 +3973,9 @@ function resolveApiKey(r) {
3811
3973
  }
3812
3974
  function credentialSource(r) {
3813
3975
  if (r.apiKey) return KEYCHAIN_ELIGIBLE_ROUTES.has(r) ? "keychain" : "inline";
3814
- if (r.apiKeyFile && r.apiKeyVar && existsSync11(r.apiKeyFile)) {
3976
+ if (r.apiKeyFile && r.apiKeyVar && existsSync12(r.apiKeyFile)) {
3815
3977
  const re = new RegExp(`^${r.apiKeyVar}=`);
3816
- if (readFileSync12(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
3978
+ if (readFileSync13(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
3817
3979
  }
3818
3980
  return process.env.CREW_API_KEY?.trim() ? "CREW_API_KEY" : "none";
3819
3981
  }
@@ -3869,7 +4031,7 @@ function configuredMembers(rt) {
3869
4031
  return out;
3870
4032
  }
3871
4033
  function defaultRepoDir(basePath, workspace, repoName) {
3872
- return join13(basePath, workspace, repoName);
4034
+ return join14(basePath, workspace, repoName);
3873
4035
  }
3874
4036
  function reposOf(r) {
3875
4037
  const named = Object.entries(r.repos);
@@ -3908,10 +4070,10 @@ function passengerRepoTargets(r) {
3908
4070
  return reposOf(r).filter((t) => r.repoOverrides[t.name]?.hostPassengers !== false);
3909
4071
  }
3910
4072
  function passengerCheckoutDir(basePath, workspace, repoName) {
3911
- return join13(basePath, workspace, ".passenger-checkouts", repoName);
4073
+ return join14(basePath, workspace, ".passenger-checkouts", repoName);
3912
4074
  }
3913
4075
  function passengerMirrorArea(ship) {
3914
- return ship.runner === "sandbox" ? join13(ship.stateDir, "passenger-sources") : void 0;
4076
+ return ship.runner === "sandbox" ? join14(ship.stateDir, "passenger-sources") : void 0;
3915
4077
  }
3916
4078
  function passengerCheckoutTargets(r, mirrorArea) {
3917
4079
  if (!r.hostPassengers) return [];
@@ -3922,7 +4084,7 @@ function passengerCheckoutTargets(r, mirrorArea) {
3922
4084
  const forgeHost = t.forgeHost ?? (id ? r.resolved?.repoForgeHosts?.[id] : void 0);
3923
4085
  return {
3924
4086
  name: t.name,
3925
- dir: mirrorArea ? join13(mirrorArea, workspace, t.name) : passengerCheckoutDir(r.reposBasePath, workspace, t.name),
4087
+ dir: mirrorArea ? join14(mirrorArea, workspace, t.name) : passengerCheckoutDir(r.reposBasePath, workspace, t.name),
3926
4088
  remote: t.remote ?? (id ? remotes[id] : void 0),
3927
4089
  ...forgeHost ? { forgeHost } : {}
3928
4090
  };
@@ -4039,6 +4201,7 @@ function runScript(script, opts) {
4039
4201
  stdio: ["ignore", "pipe", "pipe"]
4040
4202
  });
4041
4203
  let output = "";
4204
+ let stdout = "";
4042
4205
  let pending = "";
4043
4206
  const absorb = (buf) => {
4044
4207
  const text2 = buf.toString();
@@ -4049,18 +4212,21 @@ function runScript(script, opts) {
4049
4212
  pending = lines2.pop() ?? "";
4050
4213
  for (const l of lines2) opts.onLine(l);
4051
4214
  };
4052
- child.stdout.on("data", absorb);
4215
+ child.stdout.on("data", (buf) => {
4216
+ stdout += buf.toString();
4217
+ absorb(buf);
4218
+ });
4053
4219
  child.stderr.on("data", absorb);
4054
4220
  const timer = opts.timeoutMs ? setTimeout(() => child.kill("SIGKILL"), opts.timeoutMs) : void 0;
4055
4221
  child.on("error", (err3) => {
4056
4222
  if (timer) clearTimeout(timer);
4057
4223
  resolvePromise({ code: 127, output: `${output}
4058
- crew: cannot run ${shell.bin}: ${err3.message}` });
4224
+ crew: cannot run ${shell.bin}: ${err3.message}`, stdout });
4059
4225
  });
4060
4226
  child.on("close", (code) => {
4061
4227
  if (timer) clearTimeout(timer);
4062
4228
  if (pending && opts.onLine) opts.onLine(pending);
4063
- resolvePromise({ code: code ?? 1, output });
4229
+ resolvePromise({ code: code ?? 1, output, stdout });
4064
4230
  });
4065
4231
  });
4066
4232
  }
@@ -4200,9 +4366,9 @@ function recoveredFailureHeadlines(o, route2) {
4200
4366
  }
4201
4367
 
4202
4368
  // src/install.ts
4203
- import { dirname as dirname6, join as join14, extname } from "path";
4369
+ import { dirname as dirname7, join as join15, extname } from "path";
4204
4370
  import { homedir as homedir7, userInfo } from "os";
4205
- import { accessSync as accessSync2, constants as constants2, existsSync as existsSync12, mkdirSync as mkdirSync8, readdirSync as readdirSync3, readFileSync as readFileSync13, statSync as statSync5, writeFileSync as writeFileSync9, unlinkSync, realpathSync } from "fs";
4371
+ import { accessSync as accessSync2, constants as constants2, existsSync as existsSync13, mkdirSync as mkdirSync9, readdirSync as readdirSync3, readFileSync as readFileSync14, statSync as statSync5, writeFileSync as writeFileSync10, unlinkSync, realpathSync } from "fs";
4206
4372
  import { spawnSync as spawnSync4 } from "child_process";
4207
4373
  import { createHash as createHash3 } from "crypto";
4208
4374
 
@@ -4214,7 +4380,7 @@ function isCompiledBinary(moduleUrl) {
4214
4380
  // src/install.ts
4215
4381
  function crewInvocation(crewHome) {
4216
4382
  if (isCompiledBinary(import.meta.url)) return [process.execPath];
4217
- return [process.execPath, join14(crewHome, "bin", "crew")];
4383
+ return [process.execPath, join15(crewHome, "bin", "crew")];
4218
4384
  }
4219
4385
  var INTERVAL_SECONDS = 120;
4220
4386
  function labelFor(crewHome, job) {
@@ -4245,14 +4411,14 @@ function schedulerLogFor(crewLog, job) {
4245
4411
  return `${stem}.${suffix}${ext || ".log"}`;
4246
4412
  }
4247
4413
  function pathFor(ship) {
4248
- const nodeBinDir = dirname6(process.execPath);
4414
+ const nodeBinDir = dirname7(process.execPath);
4249
4415
  const parts = [nodeBinDir];
4250
4416
  if (ship.extraPath) parts.push(ship.extraPath);
4251
4417
  parts.push("/usr/bin", "/bin", "/usr/sbin", "/sbin");
4252
4418
  return parts.join(":");
4253
4419
  }
4254
4420
  function findOnPath(bin, searchPath) {
4255
- const candidates2 = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) => join14(dir, bin));
4421
+ const candidates2 = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) => join15(dir, bin));
4256
4422
  for (const candidate of candidates2) {
4257
4423
  try {
4258
4424
  accessSync2(candidate, constants2.X_OK);
@@ -4262,12 +4428,12 @@ function findOnPath(bin, searchPath) {
4262
4428
  }
4263
4429
  return void 0;
4264
4430
  }
4265
- var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin", join14(homedir7(), ".docker", "bin")].join(":");
4431
+ var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin", join15(homedir7(), ".docker", "bin")].join(":");
4266
4432
  function dockerPathProblem(ship, shellPath = process.env.PATH ?? "") {
4267
4433
  const schedulerPath = pathFor(ship);
4268
4434
  if (findOnPath("docker", schedulerPath)) return void 0;
4269
4435
  const inShell = findOnPath("docker", shellPath);
4270
- return inShell ? `\`docker\` is not on the scheduler PATH (${schedulerPath}) but is at ${inShell} in your shell; add ${dirname6(inShell)} to ship.extraPath in crew.yaml and run \`crew install\`` : `\`docker\` is not on the scheduler PATH (${schedulerPath}) or your shell PATH; install Docker, then add its directory to ship.extraPath in crew.yaml and run \`crew install\``;
4436
+ return inShell ? `\`docker\` is not on the scheduler PATH (${schedulerPath}) but is at ${inShell} in your shell; add ${dirname7(inShell)} to ship.extraPath in crew.yaml and run \`crew install\`` : `\`docker\` is not on the scheduler PATH (${schedulerPath}) or your shell PATH; install Docker, then add its directory to ship.extraPath in crew.yaml and run \`crew install\``;
4271
4437
  }
4272
4438
  var USER_INSTALL = { root: false, user: "" };
4273
4439
  function installContext(env = process.env) {
@@ -4277,7 +4443,7 @@ function installContext(env = process.env) {
4277
4443
  }
4278
4444
  var SYSTEM_UNIT_DIR = "/etc/systemd/system";
4279
4445
  function systemUnitPathFor(label, suffix = "service") {
4280
- return join14(SYSTEM_UNIT_DIR, `${label}.${suffix}`);
4446
+ return join15(SYSTEM_UNIT_DIR, `${label}.${suffix}`);
4281
4447
  }
4282
4448
  function unitEnvironment(ship) {
4283
4449
  return [["PATH", pathFor(ship)], ...Object.entries(ship.env ?? {})];
@@ -4298,7 +4464,7 @@ function xmlEscape(s) {
4298
4464
  }
4299
4465
  function planLaunchd(ship, crewHome, job) {
4300
4466
  const label = labelFor(crewHome, job);
4301
- const unitPath = join14(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
4467
+ const unitPath = join15(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
4302
4468
  const crewLog = ship.logFile;
4303
4469
  const schedulerLog = schedulerLogFor(crewLog, job);
4304
4470
  const persistent = job === "run";
@@ -4352,7 +4518,7 @@ ${scheduleKeys}
4352
4518
  }
4353
4519
  function planSystemd(ship, crewHome, job, ctx) {
4354
4520
  const label = labelFor(crewHome, job);
4355
- const unitDir = ctx.root ? SYSTEM_UNIT_DIR : join14(homedir7(), ".config", "systemd", "user");
4521
+ const unitDir = ctx.root ? SYSTEM_UNIT_DIR : join15(homedir7(), ".config", "systemd", "user");
4356
4522
  const ctl = ctx.root ? ["systemctl"] : ["systemctl", "--user"];
4357
4523
  const userLine = ctx.root && ctx.user && ctx.user !== "root" ? `User=${ctx.user}
4358
4524
  ` : "";
@@ -4361,7 +4527,7 @@ function planSystemd(ship, crewHome, job, ctx) {
4361
4527
  lingerCommand: ["loginctl", "enable-linger", ctx.user],
4362
4528
  lingerHint: `the daemon will stop when ${ctx.user} logs out; run \`sudo loginctl enable-linger ${ctx.user}\` once, or run \`crew install\` as root for a system unit`
4363
4529
  };
4364
- const servicePath = join14(unitDir, `${label}.service`);
4530
+ const servicePath = join15(unitDir, `${label}.service`);
4365
4531
  const crewLog = ship.logFile;
4366
4532
  const schedulerLog = schedulerLogFor(crewLog, job);
4367
4533
  const description = job === "release" ? "Tablation crew \u2014 release" : job === "passengers" ? "Tablation crew \u2014 Host Passengers sync" : "Tablation crew \u2014 persistent supervisor loop";
@@ -4402,7 +4568,7 @@ WantedBy=${wantedBy}
4402
4568
  ...linger
4403
4569
  };
4404
4570
  }
4405
- const timerPath = join14(unitDir, `${label}.timer`);
4571
+ const timerPath = join15(unitDir, `${label}.timer`);
4406
4572
  const service = `# Written by \`crew install\` \u2014 re-run it rather than hand-editing this file.
4407
4573
  [Unit]
4408
4574
  Description=${description}
@@ -4477,7 +4643,7 @@ function planInstall(ship, crewHome, hostShip, hasSystemd, job = "run", ctx = US
4477
4643
  function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
4478
4644
  const label = labelFor(crewHome, job);
4479
4645
  if (hostShip === "macos") {
4480
- const unitPath = join14(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
4646
+ const unitPath = join15(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
4481
4647
  return {
4482
4648
  mechanism: "launchd",
4483
4649
  unitPaths: [unitPath],
@@ -4487,20 +4653,20 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
4487
4653
  };
4488
4654
  }
4489
4655
  if (hostShip === "linux" && hasSystemd) {
4490
- const system = existsSync12(systemUnitPathFor(label));
4491
- const unitDir = system ? SYSTEM_UNIT_DIR : join14(homedir7(), ".config", "systemd", "user");
4656
+ const system = existsSync13(systemUnitPathFor(label));
4657
+ const unitDir = system ? SYSTEM_UNIT_DIR : join15(homedir7(), ".config", "systemd", "user");
4492
4658
  const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
4493
4659
  if (job === "run") {
4494
4660
  return {
4495
4661
  mechanism: "systemd",
4496
- unitPaths: [join14(unitDir, `${label}.service`)],
4662
+ unitPaths: [join15(unitDir, `${label}.service`)],
4497
4663
  crontabLine: null,
4498
4664
  unloadCommands: [[...ctl, "disable", "--now", `${label}.service`]]
4499
4665
  };
4500
4666
  }
4501
4667
  return {
4502
4668
  mechanism: "systemd",
4503
- unitPaths: [join14(unitDir, `${label}.service`), join14(unitDir, `${label}.timer`)],
4669
+ unitPaths: [join15(unitDir, `${label}.service`), join15(unitDir, `${label}.timer`)],
4504
4670
  crontabLine: null,
4505
4671
  unloadCommands: [[...ctl, "disable", "--now", `${label}.timer`]]
4506
4672
  };
@@ -4518,7 +4684,7 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
4518
4684
  function planDaemonControl(crewHome, hostShip, hasSystemd) {
4519
4685
  const label = labelFor(crewHome, "run");
4520
4686
  if (hostShip === "macos") {
4521
- const unitPath = join14(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
4687
+ const unitPath = join15(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
4522
4688
  return {
4523
4689
  mechanism: "launchd",
4524
4690
  startCommand: ["launchctl", "load", unitPath],
@@ -4528,7 +4694,7 @@ function planDaemonControl(crewHome, hostShip, hasSystemd) {
4528
4694
  }
4529
4695
  if (hostShip === "linux" && hasSystemd) {
4530
4696
  const unit = `${label}.service`;
4531
- const ctl = existsSync12(systemUnitPathFor(label)) ? ["systemctl"] : ["systemctl", "--user"];
4697
+ const ctl = existsSync13(systemUnitPathFor(label)) ? ["systemctl"] : ["systemctl", "--user"];
4532
4698
  return {
4533
4699
  mechanism: "systemd",
4534
4700
  startCommand: [...ctl, "start", unit],
@@ -4560,10 +4726,10 @@ async function applyInstall(plan, crewHome, dryRun, log) {
4560
4726
  log.emit(`would write ${path}`);
4561
4727
  continue;
4562
4728
  }
4563
- const before = existsSync12(path) ? readFileSync13(path, "utf8") : null;
4729
+ const before = existsSync13(path) ? readFileSync14(path, "utf8") : null;
4564
4730
  if (before !== plan.unitContent[path]) changed2 = true;
4565
- mkdirSync8(dirname6(path), { recursive: true });
4566
- writeFileSync9(path, plan.unitContent[path]);
4731
+ mkdirSync9(dirname7(path), { recursive: true });
4732
+ writeFileSync10(path, plan.unitContent[path]);
4567
4733
  log.emit(`wrote ${path}`);
4568
4734
  }
4569
4735
  if (plan.crontabLine) {
@@ -4627,7 +4793,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
4627
4793
  }
4628
4794
  }
4629
4795
  for (const path of plan.unitPaths) {
4630
- if (!existsSync12(path)) continue;
4796
+ if (!existsSync13(path)) continue;
4631
4797
  if (dryRun) {
4632
4798
  log.emit(`would remove ${path}`);
4633
4799
  continue;
@@ -4646,7 +4812,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
4646
4812
  }
4647
4813
  var CREW_LAUNCHD_PREFIX = "com.tablation.crew";
4648
4814
  function launchAgentsDir() {
4649
- return join14(homedir7(), "Library", "LaunchAgents");
4815
+ return join15(homedir7(), "Library", "LaunchAgents");
4650
4816
  }
4651
4817
  function xmlUnescape(s) {
4652
4818
  return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
@@ -4663,25 +4829,25 @@ function loadedCrewLabels() {
4663
4829
  return [...parseLaunchctlList(list.stdout ?? "").keys()].filter((l) => l.startsWith(CREW_LAUNCHD_PREFIX));
4664
4830
  }
4665
4831
  function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
4666
- const files = existsSync12(dir) ? readdirSync3(dir) : [];
4832
+ const files = existsSync13(dir) ? readdirSync3(dir) : [];
4667
4833
  const units = files.filter((f) => f.startsWith(CREW_LAUNCHD_PREFIX) && f.endsWith(".plist")).sort().map((f) => {
4668
- const plistPath = join14(dir, f);
4834
+ const plistPath = join15(dir, f);
4669
4835
  let program = null;
4670
4836
  try {
4671
- program = programFromPlist(readFileSync13(plistPath, "utf8"));
4837
+ program = programFromPlist(readFileSync14(plistPath, "utf8"));
4672
4838
  } catch {
4673
4839
  }
4674
4840
  return {
4675
4841
  label: f.slice(0, -".plist".length),
4676
4842
  plistPath,
4677
4843
  program,
4678
- programExists: program !== null && existsSync12(program)
4844
+ programExists: program !== null && existsSync13(program)
4679
4845
  };
4680
4846
  });
4681
4847
  const seen = new Set(units.map((u) => u.label));
4682
4848
  for (const label of [...loaded].sort()) {
4683
4849
  if (!label.startsWith(CREW_LAUNCHD_PREFIX) || seen.has(label)) continue;
4684
- units.push({ label, plistPath: join14(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
4850
+ units.push({ label, plistPath: join15(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
4685
4851
  }
4686
4852
  return units;
4687
4853
  }
@@ -4694,9 +4860,9 @@ function removeLaunchdUnit(unit, dryRun, log) {
4694
4860
  log.emit(`would unload and remove ${unit.label}`);
4695
4861
  return;
4696
4862
  }
4697
- if (existsSync12(unit.plistPath)) spawnSync4("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
4863
+ if (existsSync13(unit.plistPath)) spawnSync4("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
4698
4864
  spawnSync4("launchctl", ["remove", unit.label], { encoding: "utf8" });
4699
- if (existsSync12(unit.plistPath)) unlinkSync(unit.plistPath);
4865
+ if (existsSync13(unit.plistPath)) unlinkSync(unit.plistPath);
4700
4866
  log.emit(`removed ${unit.label} (${unit.plistPath})`);
4701
4867
  }
4702
4868
  function clearForeignLaunchdUnits(crewHome, replace, dryRun, log, dir, loaded) {
@@ -4774,24 +4940,24 @@ function detectSystemd() {
4774
4940
  }
4775
4941
 
4776
4942
  // src/ship-attention.ts
4777
- import { existsSync as existsSync14, statfsSync } from "fs";
4943
+ import { existsSync as existsSync15, statfsSync } from "fs";
4778
4944
 
4779
4945
  // src/git.ts
4780
- import { execFileSync as execFileSync3 } from "child_process";
4946
+ import { execFileSync as execFileSync4 } from "child_process";
4781
4947
  import {
4782
- existsSync as existsSync13,
4783
- mkdirSync as mkdirSync9,
4948
+ existsSync as existsSync14,
4949
+ mkdirSync as mkdirSync10,
4784
4950
  openSync as openSync2,
4785
4951
  closeSync as closeSync2,
4786
4952
  writeSync,
4787
4953
  statSync as statSync6,
4788
4954
  unlinkSync as unlinkSync2,
4789
- readFileSync as readFileSync14,
4790
- writeFileSync as writeFileSync10,
4955
+ readFileSync as readFileSync15,
4956
+ writeFileSync as writeFileSync11,
4791
4957
  readdirSync as readdirSync4,
4792
4958
  rmSync as rmSync8
4793
4959
  } from "fs";
4794
- import { dirname as dirname7, basename as basename5, join as join15 } from "path";
4960
+ import { dirname as dirname8, basename as basename5, join as join16 } from "path";
4795
4961
  var GitError = class extends Error {
4796
4962
  };
4797
4963
  function streamText(x) {
@@ -4799,7 +4965,7 @@ function streamText(x) {
4799
4965
  }
4800
4966
  function gitRaw(cwd, args) {
4801
4967
  try {
4802
- return execFileSync3("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
4968
+ return execFileSync4("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
4803
4969
  } catch (e) {
4804
4970
  const err3 = e;
4805
4971
  const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
@@ -4808,7 +4974,7 @@ function gitRaw(cwd, args) {
4808
4974
  }
4809
4975
  function git(cwd, args) {
4810
4976
  try {
4811
- return execFileSync3("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
4977
+ return execFileSync4("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
4812
4978
  } catch (e) {
4813
4979
  const err3 = e;
4814
4980
  const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
@@ -4979,9 +5145,9 @@ function keyCommitsInRange(cwd, key, range) {
4979
5145
  }
4980
5146
  function patchIdOf(cwd, from, to) {
4981
5147
  try {
4982
- const diff = execFileSync3("git", ["diff", from, to], { cwd, encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
5148
+ const diff = execFileSync4("git", ["diff", from, to], { cwd, encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
4983
5149
  if (!diff) return null;
4984
- const out = execFileSync3("git", ["patch-id", "--stable"], { cwd, encoding: "utf8", input: diff });
5150
+ const out = execFileSync4("git", ["patch-id", "--stable"], { cwd, encoding: "utf8", input: diff });
4985
5151
  return out.split(" ")[0]?.trim() || null;
4986
5152
  } catch {
4987
5153
  return null;
@@ -5171,7 +5337,7 @@ function recoverStrandedReleaseCheckouts(cwd) {
5171
5337
  if (line.startsWith("worktree ")) path = line.slice("worktree ".length);
5172
5338
  else if (line.startsWith("branch refs/heads/crew/release-") && path) {
5173
5339
  gitOk(cwd, ["worktree", "remove", "--force", path]);
5174
- const root = dirname7(path);
5340
+ const root = dirname8(path);
5175
5341
  if (basename5(root).startsWith("crew-release-")) rmSync8(root, { recursive: true, force: true });
5176
5342
  }
5177
5343
  }
@@ -5189,11 +5355,11 @@ function sweepStaleReleaseRoots(tmp, now = Date.now(), maxAgeMs = 60 * 60 * 1e3)
5189
5355
  }
5190
5356
  for (const name of names) {
5191
5357
  if (!name.startsWith("crew-release-")) continue;
5192
- const root = join15(tmp, name);
5358
+ const root = join16(tmp, name);
5193
5359
  try {
5194
5360
  const st = statSync6(root);
5195
5361
  if (!st.isDirectory() || now - st.mtimeMs < maxAgeMs) continue;
5196
- if (existsSync13(join15(root, "checkout"))) continue;
5362
+ if (existsSync14(join16(root, "checkout"))) continue;
5197
5363
  rmSync8(root, { recursive: true, force: true });
5198
5364
  removed.push(name);
5199
5365
  } catch {
@@ -5245,16 +5411,16 @@ function originUrlOf(dir, remoteName) {
5245
5411
  }
5246
5412
  }
5247
5413
  function ensureRepoCheckout(dir, remote, opts = {}) {
5248
- if (existsSync13(`${dir}/.git`)) return false;
5414
+ if (existsSync14(`${dir}/.git`)) return false;
5249
5415
  if (!remote) {
5250
5416
  throw new GitError(
5251
5417
  `${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`
5252
5418
  );
5253
5419
  }
5254
- mkdirSync9(dirname7(dir), { recursive: true });
5420
+ mkdirSync10(dirname8(dir), { recursive: true });
5255
5421
  const url = cloneUrlFor(remote, opts);
5256
5422
  try {
5257
- execFileSync3("git", ["clone", url, dir], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
5423
+ execFileSync4("git", ["clone", url, dir], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
5258
5424
  } catch (e) {
5259
5425
  const err3 = e;
5260
5426
  const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
@@ -5276,7 +5442,7 @@ function sleepSync(ms3) {
5276
5442
  }
5277
5443
  function acquireCheckoutLock(dir, timeoutMs = 3e4) {
5278
5444
  const lockPath = checkoutLockPath(dir);
5279
- mkdirSync9(dirname7(lockPath), { recursive: true });
5445
+ mkdirSync10(dirname8(lockPath), { recursive: true });
5280
5446
  const deadline = Date.now() + timeoutMs;
5281
5447
  for (; ; ) {
5282
5448
  try {
@@ -5313,17 +5479,17 @@ function acquireCheckoutLock(dir, timeoutMs = 3e4) {
5313
5479
  function syncPassengerCheckout(dir, githubRemote, gitRemoteName, base, clone = {}) {
5314
5480
  const release = acquireCheckoutLock(dir);
5315
5481
  try {
5316
- if (!existsSync13(`${dir}/.git`) && githubRemote) {
5482
+ if (!existsSync14(`${dir}/.git`) && githubRemote) {
5317
5483
  const url = cloneUrlFor(githubRemote, clone);
5318
5484
  const marker = cloneFailureMarker(dir);
5319
- if (existsSync13(marker) && readFileSync14(marker, "utf8").split("\n")[0] === url) {
5320
- return { action: "clone-failed", detail: readFileSync14(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
5485
+ if (existsSync14(marker) && readFileSync15(marker, "utf8").split("\n")[0] === url) {
5486
+ return { action: "clone-failed", detail: readFileSync15(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
5321
5487
  }
5322
5488
  try {
5323
5489
  ensureRepoCheckout(dir, githubRemote, clone);
5324
5490
  } catch (e) {
5325
5491
  if (!(e instanceof GitError)) throw e;
5326
- writeFileSync10(marker, `${url}
5492
+ writeFileSync11(marker, `${url}
5327
5493
  ${e.message}`);
5328
5494
  return { action: "clone-failed", detail: e.message, repeat: false };
5329
5495
  }
@@ -5549,7 +5715,7 @@ function noteHeldWorktree(state, h, nowMs = Date.now()) {
5549
5715
  function reconcileHeldWorktrees(state, repo, log, stillRefused) {
5550
5716
  for (const i of state.shipAttention().previous()) {
5551
5717
  if (i.kind !== "worktree_held" || !i.held || i.held.repo !== repo) continue;
5552
- const gone = !existsSync14(i.held.path);
5718
+ const gone = !existsSync15(i.held.path);
5553
5719
  if (!gone && !(stillRefused && !stillRefused.has(i.held.path))) continue;
5554
5720
  clearShipAttention(state, i.key);
5555
5721
  log.emit(
@@ -5667,6 +5833,18 @@ function repoFindings(o) {
5667
5833
  remedy: "add the directory holding it to ship.extraPath in crew.yaml"
5668
5834
  });
5669
5835
  }
5836
+ if (o.intelSyntax) {
5837
+ if (o.intelSyntax.status === "error") {
5838
+ problems.push({
5839
+ severity: "attention",
5840
+ kind: "intel_hook_syntax",
5841
+ ...at,
5842
+ hook: "intel",
5843
+ message: `${o.route}/${o.repo}: the intel hook fails its syntax check, so no symbol service will start: ${(o.intelSyntax.message ?? "").split("\n")[0]}`,
5844
+ remedy: "fix hooks.intel in the repository's .crew.yaml (`crew repos check` prints the full error)"
5845
+ });
5846
+ } else passed++;
5847
+ }
5670
5848
  return { problems, passed };
5671
5849
  }
5672
5850
  function attentionProblems(items, already) {
@@ -5677,13 +5855,13 @@ function attentionProblems(items, already) {
5677
5855
  // src/sandbox-image.ts
5678
5856
  import { createHash as createHash6 } from "crypto";
5679
5857
  import { spawn as spawn4 } from "child_process";
5680
- import { execFileSync as execFileSync7 } from "child_process";
5681
- import { mkdirSync as mkdirSync15, readFileSync as readFileSync22, renameSync as renameSync3, writeFileSync as writeFileSync16, rmSync as rmSync13 } from "fs";
5682
- import { dirname as dirname11, join as join27 } from "path";
5858
+ import { execFileSync as execFileSync8 } from "child_process";
5859
+ import { mkdirSync as mkdirSync16, readFileSync as readFileSync23, renameSync as renameSync3, writeFileSync as writeFileSync17, rmSync as rmSync13 } from "fs";
5860
+ import { dirname as dirname12, join as join28 } from "path";
5683
5861
  import { tmpdir as tmpdir3 } from "os";
5684
5862
 
5685
5863
  // src/passenger-containers.ts
5686
- import { execFileSync as execFileSync6 } from "child_process";
5864
+ import { execFileSync as execFileSync7 } from "child_process";
5687
5865
  import { createHash as createHash5 } from "crypto";
5688
5866
 
5689
5867
  // src/sandbox-cache.ts
@@ -5692,32 +5870,32 @@ import { promisify as promisify2 } from "util";
5692
5870
 
5693
5871
  // src/deprovision-host.ts
5694
5872
  import { spawnSync as spawnSync6 } from "child_process";
5695
- import { existsSync as existsSync25, readdirSync as readdirSync11 } from "fs";
5873
+ import { existsSync as existsSync26, readdirSync as readdirSync11 } from "fs";
5696
5874
 
5697
5875
  // src/interrupted.ts
5698
- import { existsSync as existsSync23, readdirSync as readdirSync9, readFileSync as readFileSync21 } from "fs";
5699
- import { join as join25 } from "path";
5876
+ import { existsSync as existsSync24, readdirSync as readdirSync9, readFileSync as readFileSync22 } from "fs";
5877
+ import { join as join26 } from "path";
5700
5878
 
5701
5879
  // src/agent.ts
5702
5880
  import { spawnSync as spawnSync5 } from "child_process";
5703
- import { readFileSync as readFileSync20, existsSync as existsSync22, statSync as statSync8, mkdirSync as mkdirSync14, writeFileSync as writeFileSync15, createWriteStream, openSync as openSync3, closeSync as closeSync3, readSync } from "fs";
5881
+ import { readFileSync as readFileSync21, existsSync as existsSync23, statSync as statSync8, mkdirSync as mkdirSync15, writeFileSync as writeFileSync16, createWriteStream, openSync as openSync3, closeSync as closeSync3, readSync } from "fs";
5704
5882
  import { StringDecoder } from "string_decoder";
5705
5883
  import { finished } from "stream/promises";
5706
- import { join as join24, dirname as dirname10, basename as basename9 } from "path";
5884
+ import { join as join25, dirname as dirname11, basename as basename9 } from "path";
5707
5885
  import { createHash as createHash4 } from "crypto";
5708
5886
 
5709
5887
  // src/state.ts
5710
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync11, existsSync as existsSync15, mkdirSync as mkdirSync10, rmSync as rmSync9, statSync as statSync7, readdirSync as readdirSync5 } from "fs";
5711
- import { join as join16 } from "path";
5888
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync12, existsSync as existsSync16, mkdirSync as mkdirSync11, rmSync as rmSync9, statSync as statSync7, readdirSync as readdirSync5 } from "fs";
5889
+ import { join as join17 } from "path";
5712
5890
  var EPOCH = "1970-01-01T00:00:00.000Z";
5713
5891
  var State = class _State {
5714
5892
  dir;
5715
5893
  constructor(dir) {
5716
5894
  this.dir = dir;
5717
- mkdirSync10(dir, { recursive: true });
5895
+ mkdirSync11(dir, { recursive: true });
5718
5896
  }
5719
5897
  path(...parts) {
5720
- return join16(this.dir, ...parts);
5898
+ return join17(this.dir, ...parts);
5721
5899
  }
5722
5900
  /**
5723
5901
  * The timestamp every "new since last poll" check is measured against.
@@ -5728,14 +5906,14 @@ var State = class _State {
5728
5906
  */
5729
5907
  watermark() {
5730
5908
  try {
5731
- const v = readFileSync15(this.path(".poll-watermark"), "utf8").trim();
5909
+ const v = readFileSync16(this.path(".poll-watermark"), "utf8").trim();
5732
5910
  return v || EPOCH;
5733
5911
  } catch {
5734
5912
  return EPOCH;
5735
5913
  }
5736
5914
  }
5737
5915
  setWatermark(at) {
5738
- writeFileSync11(this.path(".poll-watermark"), `${at}
5916
+ writeFileSync12(this.path(".poll-watermark"), `${at}
5739
5917
  `);
5740
5918
  }
5741
5919
  /**
@@ -5753,16 +5931,16 @@ var State = class _State {
5753
5931
  }
5754
5932
  /** Whole-crew pause, and the per-role sentinels one level down. */
5755
5933
  isPaused() {
5756
- return existsSync15(this.path(".crew-paused"));
5934
+ return existsSync16(this.path(".crew-paused"));
5757
5935
  }
5758
5936
  isRolePaused(role) {
5759
- return existsSync15(this.path(`.role-paused-${role}`));
5937
+ return existsSync16(this.path(`.role-paused-${role}`));
5760
5938
  }
5761
5939
  pausedRoles(roles) {
5762
5940
  return new Set(roles.filter((r) => this.isRolePaused(r)));
5763
5941
  }
5764
5942
  pause(role) {
5765
- writeFileSync11(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), "");
5943
+ writeFileSync12(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), "");
5766
5944
  }
5767
5945
  resume(role) {
5768
5946
  rmSync9(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), { force: true });
@@ -5773,10 +5951,10 @@ var State = class _State {
5773
5951
  * while agents keep working.
5774
5952
  */
5775
5953
  isReleasePaused() {
5776
- return existsSync15(this.path(".release-paused"));
5954
+ return existsSync16(this.path(".release-paused"));
5777
5955
  }
5778
5956
  setReleasePaused(paused) {
5779
- if (paused) writeFileSync11(this.path(".release-paused"), "");
5957
+ if (paused) writeFileSync12(this.path(".release-paused"), "");
5780
5958
  else rmSync9(this.path(".release-paused"), { force: true });
5781
5959
  }
5782
5960
  /**
@@ -5814,7 +5992,7 @@ var State = class _State {
5814
5992
  const name = _State.safe(rawName);
5815
5993
  const file = this.path(`.${name}.lock`);
5816
5994
  try {
5817
- const existing = Number.parseInt(readFileSync15(file, "utf8").trim(), 10);
5995
+ const existing = Number.parseInt(readFileSync16(file, "utf8").trim(), 10);
5818
5996
  if (Number.isFinite(existing) && existing > 0) {
5819
5997
  try {
5820
5998
  process.kill(existing, 0);
@@ -5824,7 +6002,7 @@ var State = class _State {
5824
6002
  }
5825
6003
  } catch {
5826
6004
  }
5827
- writeFileSync11(file, `${process.pid}
6005
+ writeFileSync12(file, `${process.pid}
5828
6006
  `);
5829
6007
  return {
5830
6008
  ok: true,
@@ -5852,7 +6030,7 @@ var State = class _State {
5852
6030
  if (!/^\.release-.+\.lock$/.test(n)) continue;
5853
6031
  let pid = 0;
5854
6032
  try {
5855
- pid = Number.parseInt(readFileSync15(this.path(n), "utf8").trim(), 10);
6033
+ pid = Number.parseInt(readFileSync16(this.path(n), "utf8").trim(), 10);
5856
6034
  } catch {
5857
6035
  continue;
5858
6036
  }
@@ -5938,7 +6116,7 @@ var State = class _State {
5938
6116
  ticketInFlight(recordId) {
5939
6117
  const file = this.path(`.${_State.safe(`crew-ticket-${recordId}`)}.lock`);
5940
6118
  try {
5941
- const pid = Number.parseInt(readFileSync15(file, "utf8").trim(), 10);
6119
+ const pid = Number.parseInt(readFileSync16(file, "utf8").trim(), 10);
5942
6120
  if (!Number.isFinite(pid) || pid <= 0) return false;
5943
6121
  process.kill(pid, 0);
5944
6122
  return true;
@@ -5972,7 +6150,7 @@ var State = class _State {
5972
6150
  const heldReported = `.release-held-${suffix}`;
5973
6151
  const read = (f) => {
5974
6152
  try {
5975
- return readFileSync15(this.path(f), "utf8").trim() || null;
6153
+ return readFileSync16(this.path(f), "utf8").trim() || null;
5976
6154
  } catch {
5977
6155
  return null;
5978
6156
  }
@@ -5981,7 +6159,7 @@ var State = class _State {
5981
6159
  blockedCount: () => Number.parseInt(read(blocked) ?? "", 10) || 0,
5982
6160
  noteBlocked: () => {
5983
6161
  const n = (Number.parseInt(read(blocked) ?? "", 10) || 0) + 1;
5984
- writeFileSync11(this.path(blocked), `${n}
6162
+ writeFileSync12(this.path(blocked), `${n}
5985
6163
  `);
5986
6164
  return n;
5987
6165
  },
@@ -5998,7 +6176,7 @@ var State = class _State {
5998
6176
  */
5999
6177
  deployFailedSha: () => read(failed),
6000
6178
  noteDeployFailed: (sha) => {
6001
- writeFileSync11(this.path(failed), `${sha}
6179
+ writeFileSync12(this.path(failed), `${sha}
6002
6180
  `);
6003
6181
  },
6004
6182
  clearDeployFailed: () => {
@@ -6012,7 +6190,7 @@ var State = class _State {
6012
6190
  noteTestGateFailed: (sha) => {
6013
6191
  const [prev, n] = (read(gateFailed) ?? "").split(" ");
6014
6192
  const next = prev === sha ? (Number.parseInt(n ?? "", 10) || 0) + 1 : 1;
6015
- writeFileSync11(this.path(gateFailed), `${sha} ${next}
6193
+ writeFileSync12(this.path(gateFailed), `${sha} ${next}
6016
6194
  `);
6017
6195
  return next;
6018
6196
  },
@@ -6022,13 +6200,13 @@ var State = class _State {
6022
6200
  /** Whether the board was already told about this head's red gate. */
6023
6201
  heldReported: () => {
6024
6202
  try {
6025
- return readFileSync15(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
6203
+ return readFileSync16(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
6026
6204
  } catch {
6027
6205
  return null;
6028
6206
  }
6029
6207
  },
6030
6208
  noteHeldReported: (signature) => {
6031
- writeFileSync11(this.path(heldReported), `${signature}
6209
+ writeFileSync12(this.path(heldReported), `${signature}
6032
6210
  `);
6033
6211
  },
6034
6212
  clearHeldReported: () => {
@@ -6036,7 +6214,7 @@ var State = class _State {
6036
6214
  },
6037
6215
  testGateReported: (sha) => read(gateReported) === sha,
6038
6216
  noteTestGateReported: (sha) => {
6039
- writeFileSync11(this.path(gateReported), `${sha}
6217
+ writeFileSync12(this.path(gateReported), `${sha}
6040
6218
  `);
6041
6219
  }
6042
6220
  };
@@ -6059,7 +6237,7 @@ var State = class _State {
6059
6237
  const ticketStreakFile = `.fairness-ticket-streak-${suffix}`;
6060
6238
  const read = (f) => {
6061
6239
  try {
6062
- return readFileSync15(this.path(f), "utf8").trim() || null;
6240
+ return readFileSync16(this.path(f), "utf8").trim() || null;
6063
6241
  } catch {
6064
6242
  return null;
6065
6243
  }
@@ -6092,18 +6270,18 @@ var State = class _State {
6092
6270
  clear2();
6093
6271
  return;
6094
6272
  }
6095
- writeFileSync11(this.path(streakFile), `${this.readInt(streakFile) + 1}
6273
+ writeFileSync12(this.path(streakFile), `${this.readInt(streakFile) + 1}
6096
6274
  `);
6097
6275
  const prior = read(ticketFile);
6098
6276
  if (topTicket && topTicket === prior) {
6099
- writeFileSync11(this.path(ticketStreakFile), `${this.readInt(ticketStreakFile) + 1}
6277
+ writeFileSync12(this.path(ticketStreakFile), `${this.readInt(ticketStreakFile) + 1}
6100
6278
  `);
6101
6279
  } else if (topTicket) {
6102
- writeFileSync11(this.path(ticketFile), `${topTicket}
6280
+ writeFileSync12(this.path(ticketFile), `${topTicket}
6103
6281
  `);
6104
- writeFileSync11(this.path(sinceFile), `${(/* @__PURE__ */ new Date()).toISOString()}
6282
+ writeFileSync12(this.path(sinceFile), `${(/* @__PURE__ */ new Date()).toISOString()}
6105
6283
  `);
6106
- writeFileSync11(this.path(ticketStreakFile), "1\n");
6284
+ writeFileSync12(this.path(ticketStreakFile), "1\n");
6107
6285
  }
6108
6286
  }
6109
6287
  };
@@ -6124,14 +6302,14 @@ var State = class _State {
6124
6302
  return {
6125
6303
  previous: () => {
6126
6304
  try {
6127
- const parsed = JSON.parse(readFileSync15(this.path(file), "utf8"));
6305
+ const parsed = JSON.parse(readFileSync16(this.path(file), "utf8"));
6128
6306
  return parsed && typeof parsed === "object" ? parsed : {};
6129
6307
  } catch {
6130
6308
  return {};
6131
6309
  }
6132
6310
  },
6133
6311
  persist: (next) => {
6134
- writeFileSync11(this.path(file), JSON.stringify(next));
6312
+ writeFileSync12(this.path(file), JSON.stringify(next));
6135
6313
  }
6136
6314
  };
6137
6315
  }
@@ -6147,14 +6325,14 @@ var State = class _State {
6147
6325
  return {
6148
6326
  previous: () => {
6149
6327
  try {
6150
- const parsed = JSON.parse(readFileSync15(this.path(file), "utf8"));
6328
+ const parsed = JSON.parse(readFileSync16(this.path(file), "utf8"));
6151
6329
  return Array.isArray(parsed) ? parsed.filter((x) => typeof x === "string") : [];
6152
6330
  } catch {
6153
6331
  return [];
6154
6332
  }
6155
6333
  },
6156
6334
  persist: (next) => {
6157
- writeFileSync11(this.path(file), JSON.stringify(next));
6335
+ writeFileSync12(this.path(file), JSON.stringify(next));
6158
6336
  }
6159
6337
  };
6160
6338
  }
@@ -6168,14 +6346,14 @@ var State = class _State {
6168
6346
  return {
6169
6347
  previous: () => {
6170
6348
  try {
6171
- const parsed = JSON.parse(readFileSync15(this.path(file), "utf8"));
6349
+ const parsed = JSON.parse(readFileSync16(this.path(file), "utf8"));
6172
6350
  return Array.isArray(parsed) ? parsed : [];
6173
6351
  } catch {
6174
6352
  return [];
6175
6353
  }
6176
6354
  },
6177
6355
  persist: (next) => {
6178
- writeFileSync11(this.path(file), JSON.stringify(next));
6356
+ writeFileSync12(this.path(file), JSON.stringify(next));
6179
6357
  }
6180
6358
  };
6181
6359
  }
@@ -6188,7 +6366,7 @@ var State = class _State {
6188
6366
  const file = ".release-last-run";
6189
6367
  return {
6190
6368
  touch: () => {
6191
- writeFileSync11(this.path(file), (/* @__PURE__ */ new Date()).toISOString());
6369
+ writeFileSync12(this.path(file), (/* @__PURE__ */ new Date()).toISOString());
6192
6370
  },
6193
6371
  at: () => {
6194
6372
  try {
@@ -6201,7 +6379,7 @@ var State = class _State {
6201
6379
  }
6202
6380
  readInt(file) {
6203
6381
  try {
6204
- return Number.parseInt(readFileSync15(this.path(file), "utf8").trim(), 10) || 0;
6382
+ return Number.parseInt(readFileSync16(this.path(file), "utf8").trim(), 10) || 0;
6205
6383
  } catch {
6206
6384
  return 0;
6207
6385
  }
@@ -6280,6 +6458,8 @@ var HOOK_PURPOSE = [
6280
6458
  ["ports", "this worktree's port assignments; prints KEY=value overrides to export"],
6281
6459
  ["handoff", "leave the operator able to open what you built; put its output in your progress comment"],
6282
6460
  ["preview", "start this worktree's dev server for a viewer, bound to CREW_PREVIEW_HOST, detached; its last stdout line is the URL"],
6461
+ ["intel", "start this worktree's symbol service, loopback only, detached; its last stdout line is the URL of an MCP endpoint"],
6462
+ ["map", 'prints an outline of the repository; the crew places it under "Repository map" below'],
6283
6463
  ["test", "run the suite"],
6284
6464
  ["build", "build it"],
6285
6465
  ["ci_status", "after you push and before you hand off: exit 0 means CI is green for your branch; on failure fix and push again, and say in your progress comment if it is still pending"]
@@ -6376,6 +6556,13 @@ function repoSection(r, key, heading) {
6376
6556
  }
6377
6557
  return lines2;
6378
6558
  }
6559
+ function mapSection(r, heading) {
6560
+ if (r.map) return ["", heading, "", r.map, ""];
6561
+ if (r.mapSkipped) {
6562
+ return ["", `Note: ${r.mapSkipped}. Say so in one line of your progress comment; it does not block the work.`, ""];
6563
+ }
6564
+ return [];
6565
+ }
6379
6566
  function renderEnvironment(i) {
6380
6567
  const { route: route2, contract } = i;
6381
6568
  const repos = i.repos;
@@ -6567,12 +6754,16 @@ function renderEnvironment(i) {
6567
6754
  for (const r of repos) {
6568
6755
  lines2.push(...i.sandbox ? ["", `### ${r.name}`] : repoSection(r, key, `### ${r.name} \u2014 \`${r.dir}\``));
6569
6756
  lines2.push("", `Hooks for ${r.name} \u2014 the only commands you should run here:`, "", hookTable(r.config));
6757
+ lines2.push(...mapSection(r, `#### Repository map \u2014 ${r.name}`));
6570
6758
  }
6571
6759
  }
6572
6760
  if (i.repoDir) {
6573
6761
  lines2.push("", `This ticket's work happens in \`${i.repoDir}\`.`);
6574
6762
  }
6575
- if (only) lines2.push("", "## Hooks \u2014 the only commands you should run", "", hookTable(only.config));
6763
+ if (only) {
6764
+ lines2.push("", "## Hooks \u2014 the only commands you should run", "", hookTable(only.config));
6765
+ lines2.push(...mapSection(only, "## Repository map"));
6766
+ }
6576
6767
  return lines2.join("\n");
6577
6768
  }
6578
6769
 
@@ -6742,8 +6933,8 @@ var PERISCOPE_COLUMNS = [
6742
6933
  ];
6743
6934
 
6744
6935
  // src/agent-log.ts
6745
- import { existsSync as existsSync16, mkdirSync as mkdirSync11, readdirSync as readdirSync6, readFileSync as readFileSync16, rmSync as rmSync10, writeFileSync as writeFileSync12 } from "fs";
6746
- import { join as join17 } from "path";
6936
+ import { existsSync as existsSync17, mkdirSync as mkdirSync12, readdirSync as readdirSync6, readFileSync as readFileSync17, rmSync as rmSync10, writeFileSync as writeFileSync13 } from "fs";
6937
+ import { join as join18 } from "path";
6747
6938
 
6748
6939
  // src/pending-backoff.ts
6749
6940
  var sleepBackoff = (attempts) => Math.min(6e4 * 2 ** Math.max(0, attempts - 1), 30 * 6e4);
@@ -6915,20 +7106,20 @@ async function addRunToTicket(target, t, recordId, run3) {
6915
7106
  };
6916
7107
  }
6917
7108
  }
6918
- var queueDir = (stateDir) => join17(stateDir, "pending-agent-logs");
7109
+ var queueDir = (stateDir) => join18(stateDir, "pending-agent-logs");
6919
7110
  function queueAgentRun(stateDir, name, q2) {
6920
- mkdirSync11(queueDir(stateDir), { recursive: true });
6921
- writeFileSync12(join17(queueDir(stateDir), `${name}.json`), JSON.stringify(q2));
7111
+ mkdirSync12(queueDir(stateDir), { recursive: true });
7112
+ writeFileSync13(join18(queueDir(stateDir), `${name}.json`), JSON.stringify(q2));
6922
7113
  }
6923
7114
  async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
6924
7115
  const dir = queueDir(stateDir);
6925
- if (!existsSync16(dir)) return 0;
7116
+ if (!existsSync17(dir)) return 0;
6926
7117
  let sent = 0;
6927
7118
  for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
6928
- const path = join17(dir, f);
7119
+ const path = join18(dir, f);
6929
7120
  let q2;
6930
7121
  try {
6931
- q2 = JSON.parse(readFileSync16(path, "utf8"));
7122
+ q2 = JSON.parse(readFileSync17(path, "utf8"));
6932
7123
  } catch {
6933
7124
  continue;
6934
7125
  }
@@ -6957,7 +7148,7 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
6957
7148
  continue;
6958
7149
  }
6959
7150
  const attempts = q2.attempts + 1;
6960
- writeFileSync12(path, JSON.stringify({
7151
+ writeFileSync13(path, JSON.stringify({
6961
7152
  ...q2,
6962
7153
  attempts,
6963
7154
  warned: true,
@@ -6968,20 +7159,20 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
6968
7159
  }
6969
7160
  return sent;
6970
7161
  }
6971
- var countersDir = (stateDir) => join17(stateDir, "pending-ticket-counters");
7162
+ var countersDir = (stateDir) => join18(stateDir, "pending-ticket-counters");
6972
7163
  function queueTicketCounters(stateDir, name, q2) {
6973
- mkdirSync11(countersDir(stateDir), { recursive: true });
6974
- writeFileSync12(join17(countersDir(stateDir), `${name}.json`), JSON.stringify(q2));
7164
+ mkdirSync12(countersDir(stateDir), { recursive: true });
7165
+ writeFileSync13(join18(countersDir(stateDir), `${name}.json`), JSON.stringify(q2));
6975
7166
  }
6976
7167
  async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
6977
7168
  const dir = countersDir(stateDir);
6978
- if (!existsSync16(dir)) return 0;
7169
+ if (!existsSync17(dir)) return 0;
6979
7170
  let sent = 0;
6980
7171
  for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
6981
- const path = join17(dir, f);
7172
+ const path = join18(dir, f);
6982
7173
  let q2;
6983
7174
  try {
6984
- q2 = JSON.parse(readFileSync16(path, "utf8"));
7175
+ q2 = JSON.parse(readFileSync17(path, "utf8"));
6985
7176
  } catch {
6986
7177
  continue;
6987
7178
  }
@@ -7000,7 +7191,7 @@ async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /
7000
7191
  continue;
7001
7192
  }
7002
7193
  const attempts = q2.attempts + 1;
7003
- writeFileSync12(path, JSON.stringify({
7194
+ writeFileSync13(path, JSON.stringify({
7004
7195
  ...q2,
7005
7196
  attempts,
7006
7197
  warned: true,
@@ -7203,8 +7394,8 @@ var HandoffTranscriptTracker = class {
7203
7394
  };
7204
7395
 
7205
7396
  // src/pending-handoff.ts
7206
- import { existsSync as existsSync19, mkdirSync as mkdirSync12, readdirSync as readdirSync7, readFileSync as readFileSync17, rmSync as rmSync11, writeFileSync as writeFileSync13 } from "fs";
7207
- import { join as join20 } from "path";
7397
+ import { existsSync as existsSync20, mkdirSync as mkdirSync13, readdirSync as readdirSync7, readFileSync as readFileSync18, rmSync as rmSync11, writeFileSync as writeFileSync14 } from "fs";
7398
+ import { join as join21 } from "path";
7208
7399
 
7209
7400
  // src/ticket-branch.ts
7210
7401
  import { basename as basename7 } from "path";
@@ -8370,16 +8561,16 @@ function holdWarnings(tickets, ships, contract, humanHolds, now = Date.now()) {
8370
8561
  }
8371
8562
 
8372
8563
  // src/run-worktree.ts
8373
- import { existsSync as existsSync18 } from "fs";
8374
- import { dirname as dirname8, join as join19 } from "path";
8564
+ import { existsSync as existsSync19 } from "fs";
8565
+ import { dirname as dirname9, join as join20 } from "path";
8375
8566
 
8376
8567
  // src/ports.ts
8377
- import { execFileSync as execFileSync4 } from "child_process";
8378
- import { existsSync as existsSync17 } from "fs";
8379
- import { join as join18 } from "path";
8568
+ import { execFileSync as execFileSync5 } from "child_process";
8569
+ import { existsSync as existsSync18 } from "fs";
8570
+ import { join as join19 } from "path";
8380
8571
  var run = (cmd, args) => {
8381
8572
  try {
8382
- return execFileSync4(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
8573
+ return execFileSync5(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
8383
8574
  } catch {
8384
8575
  return "";
8385
8576
  }
@@ -8421,7 +8612,7 @@ function ticketForPort(port, s = DEFAULT_PORTS) {
8421
8612
  }
8422
8613
  function worktreeExists(parent, prefix, n) {
8423
8614
  for (const name of [`${prefix}${n}`, `${prefix}${String(n).padStart(3, "0")}`]) {
8424
- if (existsSync17(join18(parent, name, ".git"))) return true;
8615
+ if (existsSync18(join19(parent, name, ".git"))) return true;
8425
8616
  }
8426
8617
  return false;
8427
8618
  }
@@ -8504,7 +8695,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
8504
8695
  const existing = worktreeForNumber(dir, num);
8505
8696
  const located = locateBranchForTicket(dir, cfg, t, role);
8506
8697
  const baseRef = hasRemote ? `${remote}/${base}` : base;
8507
- if (existing && !existing.prunable && existsSync18(existing.path)) {
8698
+ if (existing && !existing.prunable && existsSync19(existing.path)) {
8508
8699
  const branch = existing.branch;
8509
8700
  if (status(existing.path).length > 0) {
8510
8701
  return { action: opts.resumeInterrupted ? "resume-dirty" : "leave-dirty", path: existing.path, branch };
@@ -8520,7 +8711,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
8520
8711
  if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
8521
8712
  return { action: "recut", path: existing.path, branch, base: baseRef };
8522
8713
  }
8523
- const path = join19(dirname8(dir), worktreeForTicket(dir, cfg, t, role));
8714
+ const path = join20(dirname9(dir), worktreeForTicket(dir, cfg, t, role));
8524
8715
  if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
8525
8716
  return { action: "cut-remote", path, branch: located.branch };
8526
8717
  }
@@ -8574,7 +8765,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
8574
8765
  if (remoteConfigured(dir, remote) && remoteBranchExists(dir, remote, branch)) return false;
8575
8766
  if (countCommits(dir, `${baseRef}..${branch}`) !== 0) return false;
8576
8767
  const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
8577
- if (wt && existsSync18(wt.path)) {
8768
+ if (wt && existsSync19(wt.path)) {
8578
8769
  if (status(wt.path).length > 0) return false;
8579
8770
  if (gitOk(dir, ["worktree", "remove", wt.path]) === null) return false;
8580
8771
  }
@@ -8582,7 +8773,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
8582
8773
  }
8583
8774
  async function finishRunWorktree(dir, cfg, branch) {
8584
8775
  const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
8585
- if (!wt || !existsSync18(wt.path)) return { kind: "none" };
8776
+ if (!wt || !existsSync19(wt.path)) return { kind: "none" };
8586
8777
  if (cfg.hooks.handoff) return { kind: "kept-handoff", path: wt.path };
8587
8778
  if (status(wt.path).length > 0) return { kind: "kept-dirty", path: wt.path };
8588
8779
  if (remoteConfigured(dir, cfg.branch.remote) && worktreeHoldsUnpushed(wt.path, cfg.branch.remote)) {
@@ -8594,7 +8785,7 @@ async function finishRunWorktree(dir, cfg, branch) {
8594
8785
  if (pids.length) await killGently(pids);
8595
8786
  }
8596
8787
  gitOk(dir, ["worktree", "remove", wt.path]);
8597
- return existsSync18(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
8788
+ return existsSync19(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
8598
8789
  }
8599
8790
 
8600
8791
  // src/handoff-guard.ts
@@ -8977,20 +9168,20 @@ async function guardRunStrays(route2, ship, tracker, role, workingId, since2, lo
8977
9168
 
8978
9169
  // src/pending-handoff.ts
8979
9170
  var PENDING_MAX_AGE_MS = 48 * 36e5;
8980
- var pendingDirFor = (stateDir) => join20(stateDir, "pending-handoffs");
8981
- var stateDirOfStream = (streamPath) => join20(streamPath, "..", "..");
9171
+ var pendingDirFor = (stateDir) => join21(stateDir, "pending-handoffs");
9172
+ var stateDirOfStream = (streamPath) => join21(streamPath, "..", "..");
8982
9173
  function writePendingHandoff(stateDir, rec) {
8983
9174
  const dir = pendingDirFor(stateDir);
8984
- mkdirSync12(dir, { recursive: true });
8985
- writeFileSync13(join20(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
9175
+ mkdirSync13(dir, { recursive: true });
9176
+ writeFileSync14(join21(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
8986
9177
  }
8987
9178
  function listPendingHandoffs(stateDir, route2) {
8988
9179
  const dir = pendingDirFor(stateDir);
8989
- if (!existsSync19(dir)) return [];
9180
+ if (!existsSync20(dir)) return [];
8990
9181
  const out = [];
8991
9182
  for (const f of readdirSync7(dir).filter((n) => n.endsWith(".json")).sort()) {
8992
9183
  try {
8993
- const rec = JSON.parse(readFileSync17(join20(dir, f), "utf8"));
9184
+ const rec = JSON.parse(readFileSync18(join21(dir, f), "utf8"));
8994
9185
  if (rec.version === 1 && rec.ticketRecordId && (!route2 || rec.route === route2)) out.push(rec);
8995
9186
  } catch {
8996
9187
  }
@@ -8998,7 +9189,7 @@ function listPendingHandoffs(stateDir, route2) {
8998
9189
  return out;
8999
9190
  }
9000
9191
  function removePendingHandoff(stateDir, ticketRecordId) {
9001
- rmSync11(join20(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
9192
+ rmSync11(join21(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
9002
9193
  }
9003
9194
  function pendingHandoffFor(stateDir, ticketRecordId) {
9004
9195
  return listPendingHandoffs(stateDir).find((r) => r.ticketRecordId === ticketRecordId);
@@ -9108,8 +9299,8 @@ function expandSkillMarkers(text2, lookup2, opts = {}) {
9108
9299
  }
9109
9300
 
9110
9301
  // src/skills.ts
9111
- import { readFileSync as readFileSync19, readdirSync as readdirSync8, existsSync as existsSync21 } from "fs";
9112
- import { join as join22 } from "path";
9302
+ import { readFileSync as readFileSync20, readdirSync as readdirSync8, existsSync as existsSync22 } from "fs";
9303
+ import { join as join23 } from "path";
9113
9304
  import { parse as parseYaml } from "yaml";
9114
9305
  import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError3, TablationApiError as TablationApiError2 } from "@tablation/client";
9115
9306
 
@@ -9125,8 +9316,8 @@ ${additions}`;
9125
9316
  }
9126
9317
 
9127
9318
  // src/agents.ts
9128
- import { readFileSync as readFileSync18, existsSync as existsSync20 } from "fs";
9129
- import { join as join21 } from "path";
9319
+ import { readFileSync as readFileSync19, existsSync as existsSync21 } from "fs";
9320
+ import { join as join22 } from "path";
9130
9321
  import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError2, TablationApiError } from "@tablation/client";
9131
9322
  var PERSONA_NAME = {
9132
9323
  dev: "Developer",
@@ -9140,12 +9331,12 @@ var CREW_AGENT_COLUMN = "agent_id";
9140
9331
  var AgentsSyncError = class extends Error {
9141
9332
  };
9142
9333
  function personaDefaultPrompt(promptsDir, role) {
9143
- const briefPath = join21(promptsDir, "personas", `lane-${role}.md`);
9144
- if (!existsSync20(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
9145
- if (SELF_CONTAINED_ROLES.has(role)) return readFileSync18(briefPath, "utf8");
9146
- const commonPath = join21(promptsDir, "personas", "common.md");
9147
- if (!existsSync20(commonPath)) return readFileSync18(briefPath, "utf8");
9148
- return `${readFileSync18(commonPath, "utf8")}${readFileSync18(briefPath, "utf8")}`;
9334
+ const briefPath = join22(promptsDir, "personas", `lane-${role}.md`);
9335
+ if (!existsSync21(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
9336
+ if (SELF_CONTAINED_ROLES.has(role)) return readFileSync19(briefPath, "utf8");
9337
+ const commonPath = join22(promptsDir, "personas", "common.md");
9338
+ if (!existsSync21(commonPath)) return readFileSync19(briefPath, "utf8");
9339
+ return `${readFileSync19(commonPath, "utf8")}${readFileSync19(briefPath, "utf8")}`;
9149
9340
  }
9150
9341
  async function resolveAgentId(route2, role, opts) {
9151
9342
  const cached = route2.resolved?.agentPersonas?.[role]?.agentId;
@@ -9592,7 +9783,7 @@ function describeCrewLink(o) {
9592
9783
  var SkillSyncError = class extends Error {
9593
9784
  };
9594
9785
  function parseSkillFile(path) {
9595
- const text2 = readFileSync19(path, "utf8");
9786
+ const text2 = readFileSync20(path, "utf8");
9596
9787
  const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text2);
9597
9788
  if (!match) throw new SkillSyncError(`${path}: missing YAML frontmatter (expected a leading --- block)`);
9598
9789
  const frontmatter = match[1];
@@ -9605,9 +9796,9 @@ function parseSkillFile(path) {
9605
9796
  return { name: meta.name, description: meta.description, prompt: body, file: path };
9606
9797
  }
9607
9798
  function listLocalSkills(promptsDir) {
9608
- const dir = join22(promptsDir, "skills");
9609
- if (!existsSync21(dir)) return [];
9610
- return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join22(dir, f)));
9799
+ const dir = join23(promptsDir, "skills");
9800
+ if (!existsSync22(dir)) return [];
9801
+ return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join23(dir, f)));
9611
9802
  }
9612
9803
  async function syncSkills(route2, opts) {
9613
9804
  if (!route2.resolved) {
@@ -9791,10 +9982,10 @@ function skillMarkerWarnings(promptsDir, personaPrompts) {
9791
9982
  }
9792
9983
 
9793
9984
  // src/runner.ts
9794
- import { spawn as spawn2, execFile, execFileSync as execFileSync5 } from "child_process";
9795
- import { mkdtempSync, mkdirSync as mkdirSync13, writeFileSync as writeFileSync14, rmSync as rmSync12 } from "fs";
9985
+ import { spawn as spawn2, execFile, execFileSync as execFileSync6 } from "child_process";
9986
+ import { mkdtempSync, mkdirSync as mkdirSync14, writeFileSync as writeFileSync15, rmSync as rmSync12 } from "fs";
9796
9987
  import { tmpdir as tmpdir2 } from "os";
9797
- import { basename as basename8, dirname as dirname9, join as join23 } from "path";
9988
+ import { basename as basename8, dirname as dirname10, join as join24 } from "path";
9798
9989
  import { promisify } from "util";
9799
9990
 
9800
9991
  // src/run-limit.ts
@@ -9923,8 +10114,8 @@ var SandboxRunner = class {
9923
10114
  throw new Error(`cannot generate the sandbox policy: ${e.message}`);
9924
10115
  }
9925
10116
  policyFile = policyPathFor(plan.streamPath);
9926
- mkdirSync13(dirname9(policyFile), { recursive: true });
9927
- writeFileSync14(policyFile, generated.yaml, { mode: 384 });
10117
+ mkdirSync14(dirname10(policyFile), { recursive: true });
10118
+ writeFileSync15(policyFile, generated.yaml, { mode: 384 });
9928
10119
  }
9929
10120
  const create2 = ["sandbox", "create", "--name", name, "--detach"];
9930
10121
  if (policyFile) create2.push("--policy", policyFile);
@@ -9967,10 +10158,10 @@ var SandboxRunner = class {
9967
10158
  const harness = cfg.agentBin ?? basename8(plan.bin);
9968
10159
  const viaUpload = Buffer.byteLength(plan.prompt) > SANDBOX_STDIN_LIMIT;
9969
10160
  if (viaUpload) {
9970
- const tmp = mkdtempSync(join23(tmpdir2(), "crew-prompt-"));
10161
+ const tmp = mkdtempSync(join24(tmpdir2(), "crew-prompt-"));
9971
10162
  try {
9972
- const file = join23(tmp, "prompt.txt");
9973
- writeFileSync14(file, plan.prompt, { mode: 384 });
10163
+ const file = join24(tmp, "prompt.txt");
10164
+ writeFileSync15(file, plan.prompt, { mode: 384 });
9974
10165
  await this.os(["sandbox", "upload", name, file, SANDBOX_PROMPT_PATH]);
9975
10166
  } finally {
9976
10167
  rmSync12(tmp, { recursive: true, force: true });
@@ -10036,7 +10227,7 @@ function describeExecError(e) {
10036
10227
  }
10037
10228
  function gitOrigin(cwd) {
10038
10229
  try {
10039
- return execFileSync5("git", ["-C", cwd, "remote", "get-url", "origin"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
10230
+ return execFileSync6("git", ["-C", cwd, "remote", "get-url", "origin"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
10040
10231
  } catch {
10041
10232
  return null;
10042
10233
  }
@@ -10047,7 +10238,7 @@ function runnerFor(kind, sandbox) {
10047
10238
  function runnerOfSidecar(sc) {
10048
10239
  return sc.runner === "sandbox" ? new SandboxRunner(sc.sandboxBin ? { bin: sc.sandboxBin } : {}) : new HostRunner();
10049
10240
  }
10050
- function checkSandboxRunner(cfg = {}, run3 = (b, a) => execFileSync5(b, a, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 15e3 })) {
10241
+ function checkSandboxRunner(cfg = {}, run3 = (b, a) => execFileSync6(b, a, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 15e3 })) {
10051
10242
  const bin = cfg.bin ?? DEFAULT_SANDBOX_CONFIG.bin;
10052
10243
  let version;
10053
10244
  try {
@@ -10114,7 +10305,7 @@ var ALLOWANCE_REFUSAL_MARKER = "AI_ALLOWANCE_EXCEEDED";
10114
10305
  function streamShowsAllowanceRefusal(streamPath) {
10115
10306
  let raw;
10116
10307
  try {
10117
- raw = readFileSync20(streamPath, "utf8");
10308
+ raw = readFileSync21(streamPath, "utf8");
10118
10309
  } catch {
10119
10310
  return false;
10120
10311
  }
@@ -10137,26 +10328,26 @@ function streamShowsAllowanceRefusal(streamPath) {
10137
10328
  }
10138
10329
  return false;
10139
10330
  }
10140
- var digestPathFor = (stateDir, routeLabel, role, runId) => join24(stateDir, `digest-${routeLabel}-${role}${runId ? `-${runId}` : ""}.md`);
10331
+ var digestPathFor = (stateDir, routeLabel, role, runId) => join25(stateDir, `digest-${routeLabel}-${role}${runId ? `-${runId}` : ""}.md`);
10141
10332
  function planAgentRun(o) {
10142
- const briefPath = join24(o.route.promptsDir, "personas", `lane-${o.role}.md`);
10143
- if (!existsSync22(briefPath)) {
10333
+ const briefPath = join25(o.route.promptsDir, "personas", `lane-${o.role}.md`);
10334
+ if (!existsSync23(briefPath)) {
10144
10335
  throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
10145
10336
  }
10146
- const commonPath = join24(o.route.promptsDir, "personas", "common.md");
10147
- const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync22(commonPath);
10337
+ const commonPath = join25(o.route.promptsDir, "personas", "common.md");
10338
+ const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync23(commonPath);
10148
10339
  const now = o.now ?? (() => Date.now());
10149
10340
  const routeLabel = routeSlug(o.route.route);
10150
10341
  const ownDigest = o.runId ? digestPathFor(o.stateDir, routeLabel, o.role, o.runId) : void 0;
10151
- const digestPath2 = ownDigest && existsSync22(ownDigest) ? ownDigest : digestPathFor(o.stateDir, routeLabel, o.role);
10342
+ const digestPath2 = ownDigest && existsSync23(ownDigest) ? ownDigest : digestPathFor(o.stateDir, routeLabel, o.role);
10152
10343
  let digest;
10153
10344
  let ageSeconds;
10154
- if (existsSync22(digestPath2)) {
10345
+ if (existsSync23(digestPath2)) {
10155
10346
  ageSeconds = Math.floor((now() - statSync8(digestPath2).mtimeMs) / 1e3);
10156
- if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync20(digestPath2, "utf8");
10347
+ if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync21(digestPath2, "utf8");
10157
10348
  }
10158
- const common = usesCommon ? readFileSync20(commonPath, "utf8") : "";
10159
- const brief = readFileSync20(briefPath, "utf8");
10349
+ const common = usesCommon ? readFileSync21(commonPath, "utf8") : "";
10350
+ const brief = readFileSync21(briefPath, "utf8");
10160
10351
  const effectiveCommon = o.divergedPrompt ? "" : common;
10161
10352
  const effectiveBrief = appendAdditions(o.divergedPrompt ?? brief, o.personaAdditions);
10162
10353
  const localSkills = new Map(listLocalSkills(o.route.promptsDir).map((s) => [s.name, s.prompt]));
@@ -10179,7 +10370,7 @@ function planAgentRun(o) {
10179
10370
  digest
10180
10371
  });
10181
10372
  const promptSha = createHash4("sha256").update(expanded.text).digest("hex");
10182
- const streamsDir = join24(o.stateDir, "streams");
10373
+ const streamsDir = join25(o.stateDir, "streams");
10183
10374
  const base = `${routeLabel}-${o.role}-${o.cycle}${o.runId ? `-${o.runId}` : ""}`;
10184
10375
  const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
10185
10376
  const { model } = seat;
@@ -10269,8 +10460,8 @@ function planAgentRun(o) {
10269
10460
  digestAttached: digest !== void 0,
10270
10461
  digestAgeSeconds: ageSeconds,
10271
10462
  model,
10272
- streamPath: join24(streamsDir, `${base}.jsonl`),
10273
- eventsPath: join24(streamsDir, `${base}.events.jsonl`),
10463
+ streamPath: join25(streamsDir, `${base}.jsonl`),
10464
+ eventsPath: join25(streamsDir, `${base}.events.jsonl`),
10274
10465
  ticket: o.ticket,
10275
10466
  ticketRecordId: o.ticketRecordId ?? null,
10276
10467
  repo: o.repo ?? null,
@@ -10301,7 +10492,7 @@ function describePlan(p) {
10301
10492
  var pidPathFor = (streamPath) => `${streamPath}.pid`;
10302
10493
  function readRunSidecar(streamPath) {
10303
10494
  try {
10304
- const sc = JSON.parse(readFileSync20(pidPathFor(streamPath), "utf8"));
10495
+ const sc = JSON.parse(readFileSync21(pidPathFor(streamPath), "utf8"));
10305
10496
  return typeof sc.pid === "number" && typeof sc.spawnedAt === "string" && typeof sc.role === "string" ? sc : null;
10306
10497
  } catch {
10307
10498
  return null;
@@ -10348,7 +10539,7 @@ async function killForDeadline(kill, pid, emit, role, ticket, spawnedAt, limitMi
10348
10539
  var withoutKey = ({ apiKey: _apiKey, ...rest }) => rest;
10349
10540
  function openSink(path) {
10350
10541
  try {
10351
- mkdirSync14(dirname10(path), { recursive: true });
10542
+ mkdirSync15(dirname11(path), { recursive: true });
10352
10543
  return createWriteStream(path, { flags: "a" });
10353
10544
  } catch {
10354
10545
  return void 0;
@@ -10530,7 +10721,7 @@ var RunObserver = class {
10530
10721
  };
10531
10722
  function writeRunMeta(id, startedAt, liveTicket, extra) {
10532
10723
  try {
10533
- writeFileSync15(`${id.streamPath}.meta.json`, JSON.stringify({
10724
+ writeFileSync16(`${id.streamPath}.meta.json`, JSON.stringify({
10534
10725
  route: id.route,
10535
10726
  role: id.role,
10536
10727
  hint: id.ticket,
@@ -10690,7 +10881,7 @@ function spawnAgent(plan, emit, hintMoved) {
10690
10881
  const id = identityOf(plan, adapter.harness);
10691
10882
  let streamFd;
10692
10883
  try {
10693
- mkdirSync14(dirname10(plan.streamPath), { recursive: true });
10884
+ mkdirSync15(dirname11(plan.streamPath), { recursive: true });
10694
10885
  streamFd = openSync3(plan.streamPath, "a");
10695
10886
  } catch (e) {
10696
10887
  void endSink(eventsSink);
@@ -10748,7 +10939,7 @@ function spawnAgent(plan, emit, hintMoved) {
10748
10939
  promptSha: plan.promptSha
10749
10940
  };
10750
10941
  try {
10751
- writeFileSync15(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
10942
+ writeFileSync16(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
10752
10943
  } catch {
10753
10944
  }
10754
10945
  }
@@ -10862,21 +11053,21 @@ var pidAlive = (pid) => {
10862
11053
  };
10863
11054
  function runFinished(streamPath) {
10864
11055
  const path = `${streamPath}.meta.json`;
10865
- if (!existsSync23(path)) return false;
11056
+ if (!existsSync24(path)) return false;
10866
11057
  try {
10867
- return JSON.parse(readFileSync21(path, "utf8")).running !== true;
11058
+ return JSON.parse(readFileSync22(path, "utf8")).running !== true;
10868
11059
  } catch {
10869
11060
  return true;
10870
11061
  }
10871
11062
  }
10872
11063
  function orphanedStreams(o) {
10873
- const dir = join25(o.stateDir, "streams");
10874
- if (!existsSync23(dir)) return [];
11064
+ const dir = join26(o.stateDir, "streams");
11065
+ if (!existsSync24(dir)) return [];
10875
11066
  const ownerAlive = o.isOwnerAlive ?? pidAlive;
10876
11067
  const out = [];
10877
11068
  for (const name of readdirSync9(dir)) {
10878
11069
  if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
10879
- const streamPath = join25(dir, name);
11070
+ const streamPath = join26(dir, name);
10880
11071
  if (runFinished(streamPath)) continue;
10881
11072
  const sidecar = readRunSidecar(streamPath);
10882
11073
  if (!sidecar || sidecar.route !== o.routeLabel) continue;
@@ -10929,15 +11120,15 @@ async function reconcileRuns(o) {
10929
11120
  }
10930
11121
 
10931
11122
  // src/live-runs.ts
10932
- import { existsSync as existsSync24, readdirSync as readdirSync10, statSync as statSync9 } from "fs";
10933
- import { join as join26 } from "path";
11123
+ import { existsSync as existsSync25, readdirSync as readdirSync10, statSync as statSync9 } from "fs";
11124
+ import { join as join27 } from "path";
10934
11125
  function liveRuns(stateDir, isAlive = runIsAlive) {
10935
- const dir = join26(stateDir, "streams");
10936
- if (!existsSync24(dir)) return [];
11126
+ const dir = join27(stateDir, "streams");
11127
+ if (!existsSync25(dir)) return [];
10937
11128
  const out = [];
10938
11129
  for (const name of readdirSync10(dir)) {
10939
11130
  if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
10940
- const streamPath = join26(dir, name);
11131
+ const streamPath = join27(dir, name);
10941
11132
  if (runFinished(streamPath)) continue;
10942
11133
  const sidecar = readRunSidecar(streamPath);
10943
11134
  if (!sidecar || !isAlive(sidecar)) continue;
@@ -10947,12 +11138,12 @@ function liveRuns(stateDir, isAlive = runIsAlive) {
10947
11138
  }
10948
11139
  function runActivity(stateDir, isAlive = runIsAlive) {
10949
11140
  const activeRuns = liveRuns(stateDir, isAlive).length;
10950
- const dir = join26(stateDir, "streams");
11141
+ const dir = join27(stateDir, "streams");
10951
11142
  let newest = 0;
10952
- if (existsSync24(dir)) {
11143
+ if (existsSync25(dir)) {
10953
11144
  for (const name of readdirSync10(dir)) {
10954
11145
  if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
10955
- const streamPath = join26(dir, name);
11146
+ const streamPath = join27(dir, name);
10956
11147
  if (!runFinished(streamPath)) continue;
10957
11148
  try {
10958
11149
  newest = Math.max(newest, statSync9(`${streamPath}.meta.json`).mtimeMs);
@@ -11323,7 +11514,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
11323
11514
  if (host === "windows") return [];
11324
11515
  const ctx = installContext();
11325
11516
  const systemd = detectSystemd();
11326
- return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) => existsSync25(p));
11517
+ return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) => existsSync26(p));
11327
11518
  };
11328
11519
  return {
11329
11520
  liveRuns: () => liveRuns(stateDir).map((r) => ({ role: r.role, route: r.route, pid: r.pid, ticketRecordId: r.ticketRecordId ?? void 0, streamPath: r.streamPath })),
@@ -11384,7 +11575,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
11384
11575
  },
11385
11576
  remaining() {
11386
11577
  const left = [];
11387
- if (existsSync25(cfg.configFile)) left.push(`config ${cfg.configFile}`);
11578
+ if (existsSync26(cfg.configFile)) left.push(`config ${cfg.configFile}`);
11388
11579
  for (const h of forgeHosts()) if (hasForgeToken(cfg.ship.forges, h, stateDir)) left.push(`forge token for ${h}`);
11389
11580
  try {
11390
11581
  for (const k of readdirSync11(keysDir(stateDir))) left.push(`seat key ${k}`);
@@ -11659,7 +11850,7 @@ function makeCachePass(o) {
11659
11850
  // src/passenger-containers.ts
11660
11851
  var realExec2 = (cmd, args, opts) => {
11661
11852
  try {
11662
- return execFileSync6(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: opts?.timeoutMs });
11853
+ return execFileSync7(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: opts?.timeoutMs });
11663
11854
  } catch (e) {
11664
11855
  const err3 = e;
11665
11856
  const why = [err3.stderr, err3.stdout].map((x) => (x ?? "").trim()).filter(Boolean).join(" / ");
@@ -11939,7 +12130,7 @@ var WARM_MAX_BYTES = 64 * 1024 * 1024;
11939
12130
  var globToRegExp = (glob) => new RegExp(`^${glob.split("*").map((p) => p.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*")}$`);
11940
12131
  function readWarmFiles(dir, ref, patterns) {
11941
12132
  if (patterns.length === 0) return {};
11942
- const git2 = (args) => execFileSync7("git", ["-C", dir, ...args], { encoding: "utf8", maxBuffer: WARM_MAX_BYTES, stdio: ["ignore", "pipe", "pipe"] });
12133
+ const git2 = (args) => execFileSync8("git", ["-C", dir, ...args], { encoding: "utf8", maxBuffer: WARM_MAX_BYTES, stdio: ["ignore", "pipe", "pipe"] });
11943
12134
  const all = git2(["ls-tree", "-r", "--name-only", ref]).split("\n").filter(Boolean);
11944
12135
  const res = patterns.map(globToRegExp);
11945
12136
  const out = {};
@@ -11992,19 +12183,19 @@ function planSandbox(repo, decl, warm = {}) {
11992
12183
  const layer = repoLayer(repo, decl, crew, warm);
11993
12184
  return { repo, tag: layer.tag, claude: SANDBOX_CLAUDE_VERSION, layers: [base, crew, layer] };
11994
12185
  }
11995
- var sandboxStatePath = (stateDir) => join27(stateDir, "sandbox-images.json");
12186
+ var sandboxStatePath = (stateDir) => join28(stateDir, "sandbox-images.json");
11996
12187
  function readSandboxState(stateDir) {
11997
12188
  try {
11998
- const v = JSON.parse(readFileSync22(sandboxStatePath(stateDir), "utf8"));
12189
+ const v = JSON.parse(readFileSync23(sandboxStatePath(stateDir), "utf8"));
11999
12190
  return v && typeof v === "object" && !Array.isArray(v) ? v : {};
12000
12191
  } catch {
12001
12192
  return {};
12002
12193
  }
12003
12194
  }
12004
12195
  function writeSandboxState(stateDir, state) {
12005
- mkdirSync15(stateDir, { recursive: true });
12196
+ mkdirSync16(stateDir, { recursive: true });
12006
12197
  const file = sandboxStatePath(stateDir);
12007
- writeFileSync16(`${file}.tmp`, `${JSON.stringify(state, null, 2)}
12198
+ writeFileSync17(`${file}.tmp`, `${JSON.stringify(state, null, 2)}
12008
12199
  `);
12009
12200
  renameSync3(`${file}.tmp`, file);
12010
12201
  }
@@ -12024,7 +12215,7 @@ var spawnBuildRunner = (engine = "docker") => (args, logPath) => new Promise((re
12024
12215
  child.stdout.on("data", (d) => chunks.push(d));
12025
12216
  child.stderr.on("data", (d) => chunks.push(d));
12026
12217
  const done = (code, extra = "") => {
12027
- writeFileSync16(logPath, `$ ${engine} ${args.join(" ")}
12218
+ writeFileSync17(logPath, `$ ${engine} ${args.join(" ")}
12028
12219
  ${Buffer.concat(chunks).toString("utf8")}${extra}`, { flag: "a" });
12029
12220
  resolve8(code);
12030
12221
  };
@@ -12046,7 +12237,7 @@ var engineRemoveImage = (engine = "docker", exec = realExec2) => (tag) => {
12046
12237
  } catch {
12047
12238
  }
12048
12239
  };
12049
- var sandboxLogDir = (stateDir) => join27(stateDir, "sandbox-logs");
12240
+ var sandboxLogDir = (stateDir) => join28(stateDir, "sandbox-logs");
12050
12241
  async function buildSandboxImage(plan, deps) {
12051
12242
  const now = deps.now ?? (() => /* @__PURE__ */ new Date());
12052
12243
  const state = readSandboxState(deps.stateDir);
@@ -12055,20 +12246,20 @@ async function buildSandboxImage(plan, deps) {
12055
12246
  return { kind: "reused", tag: plan.tag };
12056
12247
  }
12057
12248
  const logDir = sandboxLogDir(deps.stateDir);
12058
- mkdirSync15(logDir, { recursive: true });
12059
- const logPath = join27(logDir, `${slug(plan.repo)}-${now().toISOString().replace(/[:.]/g, "-")}.log`);
12249
+ mkdirSync16(logDir, { recursive: true });
12250
+ const logPath = join28(logDir, `${slug(plan.repo)}-${now().toISOString().replace(/[:.]/g, "-")}.log`);
12060
12251
  const builtLayers = [];
12061
12252
  for (const layer of plan.layers) {
12062
12253
  if (deps.hasImage(layer.tag)) continue;
12063
- const ctx = join27(tmpdir3(), `crew-sandbox-${hash([layer.tag, process.pid, Date.now()])}`);
12064
- mkdirSync15(ctx, { recursive: true });
12254
+ const ctx = join28(tmpdir3(), `crew-sandbox-${hash([layer.tag, process.pid, Date.now()])}`);
12255
+ mkdirSync16(ctx, { recursive: true });
12065
12256
  try {
12066
- writeFileSync16(join27(ctx, "Dockerfile"), layer.dockerfile);
12257
+ writeFileSync17(join28(ctx, "Dockerfile"), layer.dockerfile);
12067
12258
  for (const [name, content] of Object.entries(layer.files)) {
12068
- mkdirSync15(dirname11(join27(ctx, name)), { recursive: true });
12069
- writeFileSync16(join27(ctx, name), content);
12259
+ mkdirSync16(dirname12(join28(ctx, name)), { recursive: true });
12260
+ writeFileSync17(join28(ctx, name), content);
12070
12261
  }
12071
- const code = await deps.run(["build", "--tag", layer.tag, "--file", join27(ctx, "Dockerfile"), ctx], logPath);
12262
+ const code = await deps.run(["build", "--tag", layer.tag, "--file", join28(ctx, "Dockerfile"), ctx], logPath);
12072
12263
  if (code !== 0) {
12073
12264
  state[plan.repo] = { ...entry, failure: { tag: plan.tag, at: now().toISOString(), logPath } };
12074
12265
  writeSandboxState(deps.stateDir, state);
@@ -12121,10 +12312,10 @@ async function sandboxBuildTick(repos, deps, force = false) {
12121
12312
  }
12122
12313
 
12123
12314
  // src/doctor-checks.ts
12124
- import { execFileSync as execFileSync8 } from "child_process";
12315
+ import { execFileSync as execFileSync9 } from "child_process";
12125
12316
  var PROBE_BRANCH = "crew-probe-branch";
12126
12317
  var realGit = (cwd, args, env) => {
12127
- execFileSync8("git", args, { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
12318
+ execFileSync9("git", args, { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
12128
12319
  };
12129
12320
  function isSshRemote(url) {
12130
12321
  return /^ssh:\/\//.test(url) || /^[\w.-]+@[\w.-]+:/.test(url);
@@ -12257,9 +12448,9 @@ function managedRuntimeWarnings(kind, ship) {
12257
12448
 
12258
12449
  // src/selftest.ts
12259
12450
  import { spawn as spawn5 } from "child_process";
12260
- import { existsSync as existsSync26, mkdirSync as mkdirSync16, mkdtempSync as mkdtempSync2, readFileSync as readFileSync23, rmSync as rmSync14, writeFileSync as writeFileSync17 } from "fs";
12451
+ import { existsSync as existsSync27, mkdirSync as mkdirSync17, mkdtempSync as mkdtempSync2, readFileSync as readFileSync24, rmSync as rmSync14, writeFileSync as writeFileSync18 } from "fs";
12261
12452
  import { tmpdir as tmpdir4 } from "os";
12262
- import { join as join28 } from "path";
12453
+ import { join as join29 } from "path";
12263
12454
  var SELFTEST_TIMEOUT_MS = 12e4;
12264
12455
  var KEY_VAR = "CREW_API_KEY";
12265
12456
  var SELFTEST_PROMPT = "This is a read-only connectivity check. Use the tracker MCP tool to read this workspace (the workspaces find-one tool, using the workspace id below) and reply with the workspace's name alone, on one line. Do not write anything, do not create files, and do not use any other tool.\n\nWorkspace id: ";
@@ -12420,7 +12611,7 @@ async function runSelftest(i, deps = realDeps) {
12420
12611
  ...adapter.runEnv(spec),
12421
12612
  [KEY_VAR]: i.apiKey
12422
12613
  };
12423
- const cwd = mkdtempSync2(join28(tmpdir4(), "crew-selftest-"));
12614
+ const cwd = mkdtempSync2(join29(tmpdir4(), "crew-selftest-"));
12424
12615
  try {
12425
12616
  const run3 = await deps.run({
12426
12617
  bin: invocation.bin,
@@ -12453,11 +12644,11 @@ function formatResult(r) {
12453
12644
  const secs = `${(r.ms / 1e3).toFixed(1)}s`;
12454
12645
  return r.ok ? `${r.toolchain}: PASS ${r.detail} (${secs}${tok})` : `${r.toolchain}: FAIL ${r.failure} \u2014 ${r.detail} (${secs})`;
12455
12646
  }
12456
- var fileFor2 = (stateDir) => join28(stateDir, "selftest.json");
12647
+ var fileFor2 = (stateDir) => join29(stateDir, "selftest.json");
12457
12648
  function readSelftest(stateDir) {
12458
12649
  try {
12459
12650
  const p = fileFor2(stateDir);
12460
- return existsSync26(p) ? JSON.parse(readFileSync23(p, "utf8")) : {};
12651
+ return existsSync27(p) ? JSON.parse(readFileSync24(p, "utf8")) : {};
12461
12652
  } catch {
12462
12653
  return {};
12463
12654
  }
@@ -12465,8 +12656,8 @@ function readSelftest(stateDir) {
12465
12656
  function recordSelftest(stateDir, results) {
12466
12657
  const all = readSelftest(stateDir);
12467
12658
  for (const r of results) all[r.toolchain] = r;
12468
- mkdirSync16(stateDir, { recursive: true });
12469
- writeFileSync17(fileFor2(stateDir), `${JSON.stringify(all, null, 2)}
12659
+ mkdirSync17(stateDir, { recursive: true });
12660
+ writeFileSync18(fileFor2(stateDir), `${JSON.stringify(all, null, 2)}
12470
12661
  `);
12471
12662
  }
12472
12663
  function doctorSelftestLines(stored, toolchains) {
@@ -12478,7 +12669,7 @@ function doctorSelftestLines(stored, toolchains) {
12478
12669
  }
12479
12670
 
12480
12671
  // src/cli.ts
12481
- import { resolve as resolve7, dirname as dirname29, join as join50, basename as basename16 } from "path";
12672
+ import { resolve as resolve7, dirname as dirname30, join as join51, basename as basename16 } from "path";
12482
12673
  import { fileURLToPath } from "url";
12483
12674
  import { hostname as hostname4, homedir as homedir11 } from "os";
12484
12675
  import { spawnSync as spawnSync10 } from "child_process";
@@ -12675,14 +12866,14 @@ function rosterMarkdown(roster, meId) {
12675
12866
  }
12676
12867
 
12677
12868
  // src/connect-resume.ts
12678
- import { mkdirSync as mkdirSync17, readFileSync as readFileSync24, rmSync as rmSync15, writeFileSync as writeFileSync18 } from "fs";
12679
- import { dirname as dirname12, join as join29 } from "path";
12869
+ import { mkdirSync as mkdirSync18, readFileSync as readFileSync25, rmSync as rmSync15, writeFileSync as writeFileSync19 } from "fs";
12870
+ import { dirname as dirname13, join as join30 } from "path";
12680
12871
  function lastSessionFile(stateDir) {
12681
- return join29(stateDir, "connect-session.json");
12872
+ return join30(stateDir, "connect-session.json");
12682
12873
  }
12683
12874
  function readPointers(stateDir) {
12684
12875
  try {
12685
- const parsed = JSON.parse(readFileSync24(lastSessionFile(stateDir), "utf8"));
12876
+ const parsed = JSON.parse(readFileSync25(lastSessionFile(stateDir), "utf8"));
12686
12877
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
12687
12878
  const out = {};
12688
12879
  for (const [host, v] of Object.entries(parsed)) {
@@ -12696,8 +12887,8 @@ function readPointers(stateDir) {
12696
12887
  }
12697
12888
  function writePointers(stateDir, pointers) {
12698
12889
  const file = lastSessionFile(stateDir);
12699
- mkdirSync17(dirname12(file), { recursive: true });
12700
- writeFileSync18(file, `${JSON.stringify(pointers, null, 2)}
12890
+ mkdirSync18(dirname13(file), { recursive: true });
12891
+ writeFileSync19(file, `${JSON.stringify(pointers, null, 2)}
12701
12892
  `, { mode: 384 });
12702
12893
  }
12703
12894
  function rememberSession(stateDir, host, workspaceSlug) {
@@ -12782,15 +12973,15 @@ async function signOutSessions(o) {
12782
12973
  }
12783
12974
 
12784
12975
  // src/reset.ts
12785
- import { existsSync as existsSync27, mkdirSync as mkdirSync18, readFileSync as readFileSync25, readdirSync as readdirSync12, rmSync as rmSync16, statSync as statSync10, writeFileSync as writeFileSync19 } from "fs";
12786
- import { dirname as dirname13, join as join30 } from "path";
12976
+ import { existsSync as existsSync28, mkdirSync as mkdirSync19, readFileSync as readFileSync26, readdirSync as readdirSync12, rmSync as rmSync16, statSync as statSync10, writeFileSync as writeFileSync20 } from "fs";
12977
+ import { dirname as dirname14, join as join31 } from "path";
12787
12978
  import { hostFromUrl as hostFromUrl3 } from "@tablation/client";
12788
12979
  function clonedCheckoutsFile(stateDir) {
12789
- return join30(stateDir, "cloned-checkouts.json");
12980
+ return join31(stateDir, "cloned-checkouts.json");
12790
12981
  }
12791
12982
  function readClonedCheckouts(stateDir) {
12792
12983
  try {
12793
- const parsed = JSON.parse(readFileSync25(clonedCheckoutsFile(stateDir), "utf8"));
12984
+ const parsed = JSON.parse(readFileSync26(clonedCheckoutsFile(stateDir), "utf8"));
12794
12985
  return Array.isArray(parsed) ? parsed.filter((d) => typeof d === "string") : [];
12795
12986
  } catch {
12796
12987
  return [];
@@ -12800,8 +12991,8 @@ function recordClonedCheckout(stateDir, dir) {
12800
12991
  const known = readClonedCheckouts(stateDir);
12801
12992
  if (known.includes(dir)) return;
12802
12993
  const file = clonedCheckoutsFile(stateDir);
12803
- mkdirSync18(dirname13(file), { recursive: true });
12804
- writeFileSync19(file, `${JSON.stringify([...known, dir], null, 2)}
12994
+ mkdirSync19(dirname14(file), { recursive: true });
12995
+ writeFileSync20(file, `${JSON.stringify([...known, dir], null, 2)}
12805
12996
  `);
12806
12997
  }
12807
12998
  var mark = (dryRun) => dryRun ? "would-remove" : "removed";
@@ -12855,7 +13046,7 @@ async function runReset(o, fx) {
12855
13046
  }
12856
13047
  if (o.beforeState) steps.push(...await o.beforeState());
12857
13048
  steps.push(...o.wipeState ? wipeStateDir(o.stateDir, o.dryRun) : removeState(o.stateDir, o.dryRun));
12858
- if (existsSync27(o.configFile)) {
13049
+ if (existsSync28(o.configFile)) {
12859
13050
  if (!o.dryRun) rmSync16(o.configFile, { force: true });
12860
13051
  steps.push({ step: "config", target: o.configFile, action: mark(o.dryRun) });
12861
13052
  } else {
@@ -12864,7 +13055,7 @@ async function runReset(o, fx) {
12864
13055
  return steps;
12865
13056
  }
12866
13057
  function removeCheckout(dir, dryRun, fx) {
12867
- if (!existsSync27(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
13058
+ if (!existsSync28(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
12868
13059
  const { dirty, linkedWorktrees } = fx.inspectCheckout(dir);
12869
13060
  if (dirty.length) {
12870
13061
  return { step: "checkout", target: dir, action: "kept", detail: `has uncommitted changes (${dirty.length} path${dirty.length === 1 ? "" : "s"}: ${dirty.slice(0, 5).join(", ")}${dirty.length > 5 ? ", \u2026" : ""})` };
@@ -12877,15 +13068,15 @@ function removeCheckout(dir, dryRun, fx) {
12877
13068
  }
12878
13069
  function removeState(stateDir, dryRun) {
12879
13070
  const out = [];
12880
- const targets = [join30(stateDir, "resolved"), join30(stateDir, "keys"), clonedCheckoutsFile(stateDir), join30(stateDir, "connect-session.json"), join30(stateDir, "reload-requested"), join30(stateDir, "daemon.started")];
13071
+ const targets = [join31(stateDir, "resolved"), join31(stateDir, "keys"), clonedCheckoutsFile(stateDir), join31(stateDir, "connect-session.json"), join31(stateDir, "reload-requested"), join31(stateDir, "daemon.started")];
12881
13072
  try {
12882
13073
  for (const name of readdirSync12(stateDir)) {
12883
- if (name.startsWith(".") && statSync10(join30(stateDir, name)).isFile()) targets.push(join30(stateDir, name));
13074
+ if (name.startsWith(".") && statSync10(join31(stateDir, name)).isFile()) targets.push(join31(stateDir, name));
12884
13075
  }
12885
13076
  } catch {
12886
13077
  }
12887
13078
  for (const t of targets) {
12888
- if (!existsSync27(t)) continue;
13079
+ if (!existsSync28(t)) continue;
12889
13080
  if (!dryRun) rmSync16(t, { recursive: true, force: true });
12890
13081
  out.push({ step: "state", target: t, action: mark(dryRun) });
12891
13082
  }
@@ -12900,7 +13091,7 @@ function wipeStateDir(stateDir, dryRun) {
12900
13091
  return [{ step: "state", target: stateDir, action: "absent", detail: "no state directory" }];
12901
13092
  }
12902
13093
  if (!names.length) return [{ step: "state", target: stateDir, action: "absent", detail: "already empty" }];
12903
- if (!dryRun) for (const n of names) rmSync16(join30(stateDir, n), { recursive: true, force: true });
13094
+ if (!dryRun) for (const n of names) rmSync16(join31(stateDir, n), { recursive: true, force: true });
12904
13095
  return [{ step: "state", target: stateDir, action: mark(dryRun), detail: `${names.length} entr${names.length === 1 ? "y" : "ies"}` }];
12905
13096
  }
12906
13097
  function describeResetStep(s) {
@@ -12981,9 +13172,9 @@ async function runDeprovision(o, fx, deps) {
12981
13172
  }
12982
13173
 
12983
13174
  // src/pool-member.ts
12984
- import { mkdirSync as mkdirSync19, readFileSync as readFileSync26, renameSync as renameSync4, writeFileSync as writeFileSync20 } from "fs";
13175
+ import { mkdirSync as mkdirSync20, readFileSync as readFileSync27, renameSync as renameSync4, writeFileSync as writeFileSync21 } from "fs";
12985
13176
  import { homedir as homedir8 } from "os";
12986
- import { dirname as dirname14, join as join31 } from "path";
13177
+ import { dirname as dirname15, join as join32 } from "path";
12987
13178
  var POOL_EXIT = { ok: 0, failed: 1, credential: 2 };
12988
13179
  function backoffMs(attempt) {
12989
13180
  return Math.min(6e4, 5e3 * 2 ** Math.max(0, attempt));
@@ -13214,22 +13405,22 @@ async function runPoolMember(deps) {
13214
13405
  }
13215
13406
  }
13216
13407
  function defaultMemberDir(env = process.env) {
13217
- return env.CREW_POOL_DIR ?? join31(env.XDG_STATE_HOME ?? join31(homedir8(), ".local", "state"), "crew-pool-member");
13408
+ return env.CREW_POOL_DIR ?? join32(env.XDG_STATE_HOME ?? join32(homedir8(), ".local", "state"), "crew-pool-member");
13218
13409
  }
13219
13410
  function fileMemberStore(dir) {
13220
- const file = join31(dir, "member.json");
13411
+ const file = join32(dir, "member.json");
13221
13412
  return {
13222
13413
  read() {
13223
13414
  try {
13224
- return JSON.parse(readFileSync26(file, "utf8"));
13415
+ return JSON.parse(readFileSync27(file, "utf8"));
13225
13416
  } catch {
13226
13417
  return {};
13227
13418
  }
13228
13419
  },
13229
13420
  write(f) {
13230
- mkdirSync19(dirname14(file), { recursive: true, mode: 448 });
13421
+ mkdirSync20(dirname15(file), { recursive: true, mode: 448 });
13231
13422
  const tmp = `${file}.tmp`;
13232
- writeFileSync20(tmp, `${JSON.stringify(f)}
13423
+ writeFileSync21(tmp, `${JSON.stringify(f)}
13233
13424
  `, { mode: 384 });
13234
13425
  renameSync4(tmp, file);
13235
13426
  }
@@ -13281,30 +13472,30 @@ function hostArchitecture(arch = process.arch) {
13281
13472
 
13282
13473
  // src/pool-member-host.ts
13283
13474
  import "child_process";
13284
- import { existsSync as existsSync31 } from "fs";
13475
+ import { existsSync as existsSync32 } from "fs";
13285
13476
  import { hostname as hostname3 } from "os";
13286
13477
 
13287
13478
  // src/machine-id.ts
13288
- import { execFileSync as execFileSync9 } from "child_process";
13479
+ import { execFileSync as execFileSync10 } from "child_process";
13289
13480
  import { createHash as createHash7 } from "crypto";
13290
- import { existsSync as existsSync28, mkdirSync as mkdirSync20, readFileSync as readFileSync27, writeFileSync as writeFileSync21 } from "fs";
13291
- import { dirname as dirname15, join as join32 } from "path";
13481
+ import { existsSync as existsSync29, mkdirSync as mkdirSync21, readFileSync as readFileSync28, writeFileSync as writeFileSync22 } from "fs";
13482
+ import { dirname as dirname16, join as join33 } from "path";
13292
13483
  function machineIdPathFor(stateDir) {
13293
- return join32(stateDir, "machine-id");
13484
+ return join33(stateDir, "machine-id");
13294
13485
  }
13295
13486
  var sha256 = (s) => createHash7("sha256").update(s).digest("hex");
13296
13487
  function readRawMachineId(platform2 = process.platform) {
13297
13488
  try {
13298
13489
  if (platform2 === "darwin") {
13299
- const out = execFileSync9("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
13490
+ const out = execFileSync10("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
13300
13491
  encoding: "utf8",
13301
13492
  stdio: ["ignore", "pipe", "ignore"]
13302
13493
  });
13303
13494
  return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out)?.[1];
13304
13495
  }
13305
13496
  for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
13306
- if (existsSync28(p)) {
13307
- const id = readFileSync27(p, "utf8").trim();
13497
+ if (existsSync29(p)) {
13498
+ const id = readFileSync28(p, "utf8").trim();
13308
13499
  if (id) return id;
13309
13500
  }
13310
13501
  }
@@ -13315,15 +13506,15 @@ function readRawMachineId(platform2 = process.platform) {
13315
13506
  function ensureMachineId(stateDir, opts = {}) {
13316
13507
  if (opts.override?.trim()) return sha256(opts.override.trim());
13317
13508
  const path = machineIdPathFor(stateDir);
13318
- if (existsSync28(path)) {
13319
- const cached = readFileSync27(path, "utf8").trim();
13509
+ if (existsSync29(path)) {
13510
+ const cached = readFileSync28(path, "utf8").trim();
13320
13511
  if (/^[0-9a-f]{64}$/.test(cached)) return cached;
13321
13512
  }
13322
13513
  const raw = (opts.read ?? readRawMachineId)();
13323
13514
  if (!raw) return void 0;
13324
13515
  const hashed = sha256(raw);
13325
- mkdirSync20(dirname15(path), { recursive: true });
13326
- writeFileSync21(path, `${hashed}
13516
+ mkdirSync21(dirname16(path), { recursive: true });
13517
+ writeFileSync22(path, `${hashed}
13327
13518
  `, { mode: 384 });
13328
13519
  return hashed;
13329
13520
  }
@@ -13336,7 +13527,7 @@ function matchShipRows(ships, shipName2, machineId) {
13336
13527
 
13337
13528
  // src/provision.ts
13338
13529
  import { spawnSync as spawnSync7 } from "child_process";
13339
- import { existsSync as existsSync29, readFileSync as readFileSync28, writeFileSync as writeFileSync22 } from "fs";
13530
+ import { existsSync as existsSync30, readFileSync as readFileSync29, writeFileSync as writeFileSync23 } from "fs";
13340
13531
  import { hostname as hostname2 } from "os";
13341
13532
  import { parseDocument as parseDocument3, isMap as isMap2, isScalar } from "yaml";
13342
13533
 
@@ -13345,7 +13536,7 @@ import { parseDocument as parseDocument2 } from "yaml";
13345
13536
 
13346
13537
  // src/repos-cmd.ts
13347
13538
  import { homedir as homedir9 } from "os";
13348
- import { basename as basename10, join as join33, resolve as resolve4 } from "path";
13539
+ import { basename as basename10, join as join34, resolve as resolve4 } from "path";
13349
13540
  import { isMap, isSeq, parseDocument, stringify as stringify2 } from "yaml";
13350
13541
  var ReposError = class extends Error {
13351
13542
  };
@@ -13623,7 +13814,7 @@ function planRepoClone(i) {
13623
13814
  }
13624
13815
  const ship = doc.get("ship", true);
13625
13816
  const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
13626
- const target = i.dir ? expandDir(i.dir, i.base) : join33(expandDir(basePath, i.base), i.route.split("/")[0], name);
13817
+ const target = i.dir ? expandDir(i.dir, i.base) : join34(expandDir(basePath, i.base), i.route.split("/")[0], name);
13627
13818
  const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name, isCheckout: i.isCheckout });
13628
13819
  const forgeHost = i.resolved?.repoForgeHosts?.[id];
13629
13820
  return { ...add, remote, target, ...forgeHost ? { forgeHost } : {} };
@@ -13930,7 +14121,7 @@ function realProvisionDeps(crewHome) {
13930
14121
  };
13931
14122
  const configFile = () => {
13932
14123
  const f = findConfigFile(crewHome);
13933
- return existsSync29(f) ? f : null;
14124
+ return existsSync30(f) ? f : null;
13934
14125
  };
13935
14126
  return {
13936
14127
  crew: spawnCrew,
@@ -13938,13 +14129,13 @@ function realProvisionDeps(crewHome) {
13938
14129
  enableRoute(route2) {
13939
14130
  const file = configFile();
13940
14131
  if (!file) return false;
13941
- const text2 = readFileSync28(file, "utf8");
14132
+ const text2 = readFileSync29(file, "utf8");
13942
14133
  const doc = parseDocument3(text2);
13943
14134
  const routes = doc.get("routes");
13944
14135
  const entry = routes?.items?.find((r) => r.get("route") === route2);
13945
14136
  if (!entry) throw new Error(`route ${route2} is not in ${file}`);
13946
14137
  if (entry.get("enabled") === true) return false;
13947
- writeFileSync22(file, planEnable(text2, route2));
14138
+ writeFileSync23(file, planEnable(text2, route2));
13948
14139
  return true;
13949
14140
  },
13950
14141
  installState() {
@@ -13964,14 +14155,14 @@ function realProvisionDeps(crewHome) {
13964
14155
  continue;
13965
14156
  }
13966
14157
  for (const p of plan.unitPaths) {
13967
- if (!existsSync29(p) || readFileSync28(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
14158
+ if (!existsSync30(p) || readFileSync29(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
13968
14159
  }
13969
14160
  }
13970
14161
  const label = labelFor(crewHome, "run");
13971
14162
  let running = false;
13972
14163
  let lingering = true;
13973
14164
  if (mechanism === "systemd") {
13974
- const system = ctx.root || existsSync29(systemUnitPathFor(label));
14165
+ const system = ctx.root || existsSync30(systemUnitPathFor(label));
13975
14166
  const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
13976
14167
  running = spawnSync7(ctl[0], [...ctl.slice(1), "is-active", "--quiet", `${label}.service`]).status === 0;
13977
14168
  if (!system) {
@@ -13988,18 +14179,18 @@ function realProvisionDeps(crewHome) {
13988
14179
  setShipSetting(key, value2) {
13989
14180
  const file = configFile();
13990
14181
  if (!file) return false;
13991
- const doc = parseDocument3(readFileSync28(file, "utf8"));
14182
+ const doc = parseDocument3(readFileSync29(file, "utf8"));
13992
14183
  if (doc.getIn(["ship", key]) === value2) return false;
13993
14184
  doc.setIn(["ship", key], value2);
13994
- writeFileSync22(file, doc.toString());
14185
+ writeFileSync23(file, doc.toString());
13995
14186
  return true;
13996
14187
  },
13997
14188
  applyManagedDefaults() {
13998
14189
  const file = configFile();
13999
14190
  if (!file) return [];
14000
- const doc = parseDocument3(readFileSync28(file, "utf8"));
14191
+ const doc = parseDocument3(readFileSync29(file, "utf8"));
14001
14192
  const wrote = applyManagedDefaultsToDoc(doc);
14002
- if (wrote.length) writeFileSync22(file, doc.toString());
14193
+ if (wrote.length) writeFileSync23(file, doc.toString());
14003
14194
  return wrote;
14004
14195
  },
14005
14196
  storedToken(host) {
@@ -14016,9 +14207,9 @@ host=${host}
14016
14207
 
14017
14208
  // src/tunnel.ts
14018
14209
  import { spawn as spawn6 } from "child_process";
14019
- import { existsSync as existsSync30, mkdirSync as mkdirSync21, readFileSync as readFileSync29, writeFileSync as writeFileSync23, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "fs";
14210
+ import { existsSync as existsSync31, mkdirSync as mkdirSync22, readFileSync as readFileSync30, writeFileSync as writeFileSync24, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "fs";
14020
14211
  import { randomInt } from "crypto";
14021
- import { join as join34 } from "path";
14212
+ import { join as join35 } from "path";
14022
14213
  var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
14023
14214
  var defaultSpawn = (cmd, args) => spawn6(cmd, args, DEFAULT_SPAWN_OPTIONS);
14024
14215
  var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
@@ -14090,21 +14281,21 @@ function startTunnel(opts) {
14090
14281
  };
14091
14282
  }
14092
14283
  function tunnelStatePath(stateDir, workspaceId) {
14093
- return join34(stateDir, "passengers", `${workspaceId}.json`);
14284
+ return join35(stateDir, "passengers", `${workspaceId}.json`);
14094
14285
  }
14095
14286
  function readPersistedTunnel(stateDir, workspaceId) {
14096
14287
  const path = tunnelStatePath(stateDir, workspaceId);
14097
- if (!existsSync30(path)) return void 0;
14288
+ if (!existsSync31(path)) return void 0;
14098
14289
  try {
14099
- return JSON.parse(readFileSync29(path, "utf8"));
14290
+ return JSON.parse(readFileSync30(path, "utf8"));
14100
14291
  } catch {
14101
14292
  return void 0;
14102
14293
  }
14103
14294
  }
14104
14295
  function writePersistedTunnel(stateDir, state) {
14105
14296
  const path = tunnelStatePath(stateDir, state.workspaceId);
14106
- mkdirSync21(join34(stateDir, "passengers"), { recursive: true });
14107
- writeFileSync23(path, `${JSON.stringify(state, null, 2)}
14297
+ mkdirSync22(join35(stateDir, "passengers"), { recursive: true });
14298
+ writeFileSync24(path, `${JSON.stringify(state, null, 2)}
14108
14299
  `);
14109
14300
  }
14110
14301
  function updatePersistedTunnel(stateDir, workspaceId, patch2) {
@@ -14206,8 +14397,8 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
14206
14397
  }
14207
14398
  }
14208
14399
  function readAllPersistedWorkspaceIds(stateDir) {
14209
- const dir = join34(stateDir, "passengers");
14210
- if (!existsSync30(dir)) return [];
14400
+ const dir = join35(stateDir, "passengers");
14401
+ if (!existsSync31(dir)) return [];
14211
14402
  return readdirSync13(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
14212
14403
  }
14213
14404
 
@@ -14232,7 +14423,7 @@ function realPoolMemberDeps(o) {
14232
14423
  crewVersion: crewVersion(o.crewHome),
14233
14424
  hostname: hostname3()
14234
14425
  },
14235
- hasShipConfig: () => existsSync31(findConfigFile(o.crewHome)),
14426
+ hasShipConfig: () => existsSync32(findConfigFile(o.crewHome)),
14236
14427
  async provision(lease, instanceClass, shipKind) {
14237
14428
  const hosts = lease.forges.map((f) => f.host.toLowerCase());
14238
14429
  const lines2 = [];
@@ -15108,8 +15299,8 @@ ${waiting.map((t) => `| ${displayKey2(t)} | ${t.status} | ${blockers(t, i)} |`).
15108
15299
  }
15109
15300
 
15110
15301
  // src/poll.ts
15111
- import { writeFileSync as writeFileSync24 } from "fs";
15112
- import { join as join35 } from "path";
15302
+ import { writeFileSync as writeFileSync25 } from "fs";
15303
+ import { join as join36 } from "path";
15113
15304
 
15114
15305
  // src/stalled.ts
15115
15306
  var STALLED_AFTER_CYCLES = 30;
@@ -15416,7 +15607,7 @@ async function decideCycle(o) {
15416
15607
  }
15417
15608
  return decision;
15418
15609
  }
15419
- var digestPath = (stateDir, route2, role, runId) => join35(stateDir, `digest-${route2}-${role}${runId ? `-${runId}` : ""}.md`);
15610
+ var digestPath = (stateDir, route2, role, runId) => join36(stateDir, `digest-${route2}-${role}${runId ? `-${runId}` : ""}.md`);
15420
15611
  function otherRunsRemoved(tickets, own) {
15421
15612
  if (!own) return tickets;
15422
15613
  return tickets.filter((t) => t.id === own.ticket.id || !own.inFlight(t.id));
@@ -15494,7 +15685,7 @@ function writeDigest(o, d, role, stateDir, runId, own) {
15494
15685
 
15495
15686
  ${body}` : body;
15496
15687
  const path = digestPath(stateDir, routeSlug(o.route.route), role, runId);
15497
- writeFileSync24(path, text2);
15688
+ writeFileSync25(path, text2);
15498
15689
  o.emit.emit(`queue digest written for ${role} (${Buffer.byteLength(text2)} bytes)`, {
15499
15690
  data: { role, bytes: Buffer.byteLength(text2) }
15500
15691
  });
@@ -15602,8 +15793,8 @@ async function resolveOne(tracker, t, seat, approvedStatus, buildingStatus, affi
15602
15793
  }
15603
15794
 
15604
15795
  // src/repo-refresh.ts
15605
- import { existsSync as existsSync32, mkdirSync as mkdirSync22, readFileSync as readFileSync30, writeFileSync as writeFileSync25 } from "fs";
15606
- import { dirname as dirname17 } from "path";
15796
+ import { existsSync as existsSync33, mkdirSync as mkdirSync23, readFileSync as readFileSync31, writeFileSync as writeFileSync26 } from "fs";
15797
+ import { dirname as dirname18 } from "path";
15607
15798
  var REFRESH_COOLDOWN_MS = 2 * 6e4;
15608
15799
  var UNSERVABLE_PASSES_BEFORE_ATTENTION = 3;
15609
15800
  var lastAsked = /* @__PURE__ */ new Map();
@@ -15632,11 +15823,11 @@ function applyRepoRows(resolved, rows) {
15632
15823
  }
15633
15824
  function persistRepoRows(stateDir, routeName, rows) {
15634
15825
  const path = resolvedPathFor(stateDir, routeName);
15635
- if (!existsSync32(path)) return;
15636
- const raw = JSON.parse(readFileSync30(path, "utf8"));
15826
+ if (!existsSync33(path)) return;
15827
+ const raw = JSON.parse(readFileSync31(path, "utf8"));
15637
15828
  applyRepoRows(raw, rows);
15638
- mkdirSync22(dirname17(path), { recursive: true });
15639
- writeFileSync25(path, `${JSON.stringify(raw, null, 2)}
15829
+ mkdirSync23(dirname18(path), { recursive: true });
15830
+ writeFileSync26(path, `${JSON.stringify(raw, null, 2)}
15640
15831
  `);
15641
15832
  }
15642
15833
  async function refreshUnknownRepos(o) {
@@ -16086,13 +16277,13 @@ Ship ${shipName2} found: ${problem}. The release refuses to squash a ref nobody
16086
16277
  var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "local_ref_skip" && (e.message ?? "").includes(problem));
16087
16278
 
16088
16279
  // src/connect-wizard.ts
16089
- import { delimiter, dirname as dirname18, join as join36 } from "path";
16090
- import { accessSync as accessSync3, constants as constants3, existsSync as existsSync33, mkdirSync as mkdirSync23, writeFileSync as writeFileSync26 } from "fs";
16280
+ import { delimiter, dirname as dirname19, join as join37 } from "path";
16281
+ import { accessSync as accessSync3, constants as constants3, existsSync as existsSync34, mkdirSync as mkdirSync24, writeFileSync as writeFileSync27 } from "fs";
16091
16282
  import { stringify as stringify3 } from "yaml";
16092
16283
  var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
16093
16284
  function shouldRunWizard(opts) {
16094
16285
  if (!opts.isTTY) return false;
16095
- const exists = opts.configExists ?? existsSync33;
16286
+ const exists = opts.configExists ?? existsSync34;
16096
16287
  return !configSearchPath(opts.crewHome).some((p) => exists(p));
16097
16288
  }
16098
16289
  function firstRunConfigPath() {
@@ -16109,7 +16300,7 @@ function defaultIsExecutable(p) {
16109
16300
  function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
16110
16301
  for (const dir of pathEnv.split(delimiter)) {
16111
16302
  if (!dir) continue;
16112
- const candidate = join36(dir, name);
16303
+ const candidate = join37(dir, name);
16113
16304
  if (isExecutable(candidate)) return candidate;
16114
16305
  }
16115
16306
  return void 0;
@@ -16135,9 +16326,9 @@ async function runWizard(p, env) {
16135
16326
  if (entered && isExec(entered)) agentBin = entered;
16136
16327
  else p.say(entered ? `${entered} is not an executable file.` : "A path is required.");
16137
16328
  }
16138
- const nodeDir = dirname18(env.nodePath);
16329
+ const nodeDir = dirname19(env.nodePath);
16139
16330
  const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
16140
- const baseDirs = [nodeDir, pnpmPath ? dirname18(pnpmPath) : void 0];
16331
+ const baseDirs = [nodeDir, pnpmPath ? dirname19(pnpmPath) : void 0];
16141
16332
  p.say(
16142
16333
  `
16143
16334
  Host Passengers lets this machine run the agent sessions that other people's
@@ -16148,7 +16339,7 @@ Tablation workspaces ask for, in a container. More: ${HOST_PASSENGERS_DOCS_URL}`
16148
16339
  p.say("Docker is not available here, so nothing will be hosted until it is. Your answer is recorded anyway.");
16149
16340
  }
16150
16341
  const dockerPath = hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
16151
- const extraPath = deriveExtraPath([...baseDirs, dockerPath ? dirname18(dockerPath) : void 0]);
16342
+ const extraPath = deriveExtraPath([...baseDirs, dockerPath ? dirname19(dockerPath) : void 0]);
16152
16343
  if (extraPath) {
16153
16344
  p.say(`Hooks will get ${extraPath} on their PATH (where node, pnpm${dockerPath ? " and docker" : ""} live).`);
16154
16345
  }
@@ -16170,9 +16361,9 @@ routes:
16170
16361
  ${routeBlock}`;
16171
16362
  }
16172
16363
  function writeNewConfig(path, text2) {
16173
- mkdirSync23(dirname18(path), { recursive: true });
16364
+ mkdirSync24(dirname19(path), { recursive: true });
16174
16365
  try {
16175
- writeFileSync26(path, text2, { mode: 384, flag: "wx" });
16366
+ writeFileSync27(path, text2, { mode: 384, flag: "wx" });
16176
16367
  return true;
16177
16368
  } catch (e) {
16178
16369
  if (e.code === "EEXIST") return false;
@@ -16206,7 +16397,7 @@ Wrote ${o.configPath}.
16206
16397
  // src/connect-json.ts
16207
16398
  import { accessSync as accessSync4, constants as constants4 } from "fs";
16208
16399
  import { homedir as homedir10 } from "os";
16209
- import { dirname as dirname19, join as join37 } from "path";
16400
+ import { dirname as dirname20, join as join38 } from "path";
16210
16401
  import { delimiter as delimiter2 } from "path";
16211
16402
  var CHOICE_EXIT_CODE = 3;
16212
16403
  function createEmitter(json2, write) {
@@ -16245,12 +16436,12 @@ function agentBinSearchDirs(home, pathEnv, extraPath) {
16245
16436
  const dirs = [
16246
16437
  ...pathEnv.split(delimiter2),
16247
16438
  ...(extraPath ?? "").split(delimiter2),
16248
- join37(home, ".local/bin"),
16249
- join37(home, ".claude/local"),
16439
+ join38(home, ".local/bin"),
16440
+ join38(home, ".claude/local"),
16250
16441
  "/opt/homebrew/bin",
16251
16442
  "/usr/local/bin",
16252
- join37(home, ".npm-global/bin"),
16253
- join37(home, ".volta/bin")
16443
+ join38(home, ".npm-global/bin"),
16444
+ join38(home, ".volta/bin")
16254
16445
  ];
16255
16446
  return [...new Set(dirs.filter(Boolean))];
16256
16447
  }
@@ -16272,7 +16463,7 @@ function flagWizardAnswers(f, env) {
16272
16463
  const agentBin = f.agentBin ?? found?.bin;
16273
16464
  const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
16274
16465
  const dockerPath = f.hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
16275
- const extraPath = f.extraPath ?? deriveExtraPath([dirname19(env.nodePath), pnpmPath ? dirname19(pnpmPath) : void 0, dockerPath ? dirname19(dockerPath) : void 0]);
16466
+ const extraPath = f.extraPath ?? deriveExtraPath([dirname20(env.nodePath), pnpmPath ? dirname20(pnpmPath) : void 0, dockerPath ? dirname20(dockerPath) : void 0]);
16276
16467
  return {
16277
16468
  answers: {
16278
16469
  name: f.shipName?.trim() || env.hostname,
@@ -16286,8 +16477,8 @@ function flagWizardAnswers(f, env) {
16286
16477
  }
16287
16478
 
16288
16479
  // src/release-run.ts
16289
- import { readFileSync as readFileSync31, writeFileSync as writeFileSync27, existsSync as existsSync34, copyFileSync, mkdirSync as mkdirSync24, mkdtempSync as mkdtempSync3, rmSync as rmSync17 } from "fs";
16290
- import { dirname as dirname20, join as join38 } from "path";
16480
+ import { readFileSync as readFileSync32, writeFileSync as writeFileSync28, existsSync as existsSync35, copyFileSync, mkdirSync as mkdirSync25, mkdtempSync as mkdtempSync3, rmSync as rmSync17 } from "fs";
16481
+ import { dirname as dirname21, join as join39 } from "path";
16291
16482
  import { tmpdir as tmpdir5 } from "os";
16292
16483
 
16293
16484
  // src/release.ts
@@ -17028,13 +17219,13 @@ async function bump(o, size) {
17028
17219
  }
17029
17220
  return printed;
17030
17221
  }
17031
- const files = o.repo.release.versionFiles.map((f) => join38(o.cwd, f));
17222
+ const files = o.repo.release.versionFiles.map((f) => join39(o.cwd, f));
17032
17223
  const first = files[0];
17033
- if (!existsSync34(first)) {
17224
+ if (!existsSync35(first)) {
17034
17225
  o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
17035
17226
  return null;
17036
17227
  }
17037
- const current = JSON.parse(readFileSync31(first, "utf8")).version;
17228
+ const current = JSON.parse(readFileSync32(first, "utf8")).version;
17038
17229
  if (!current) {
17039
17230
  o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
17040
17231
  return null;
@@ -17047,27 +17238,27 @@ async function bump(o, size) {
17047
17238
  return version;
17048
17239
  }
17049
17240
  for (const f of files) {
17050
- const src = readFileSync31(f, "utf8");
17241
+ const src = readFileSync32(f, "utf8");
17051
17242
  const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
17052
17243
  if (out === src) {
17053
17244
  o.emit.error(`no version field in ${f}`);
17054
17245
  return null;
17055
17246
  }
17056
- writeFileSync27(f, out);
17247
+ writeFileSync28(f, out);
17057
17248
  }
17058
17249
  return version;
17059
17250
  }
17060
17251
  function writeChangelog(o, version, entries) {
17061
17252
  const file = o.repo.release.changelog;
17062
17253
  if (!file || entries.length === 0) return;
17063
- const path = join38(o.cwd, file);
17254
+ const path = join39(o.cwd, file);
17064
17255
  const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
17065
17256
  if (o.dryRun) {
17066
17257
  o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
17067
17258
  return;
17068
17259
  }
17069
- const existing = existsSync34(path) ? readFileSync31(path, "utf8") : "# Changelog\n";
17070
- writeFileSync27(path, insertChangelogSection(existing, section));
17260
+ const existing = existsSync35(path) ? readFileSync32(path, "utf8") : "# Changelog\n";
17261
+ writeFileSync28(path, insertChangelogSection(existing, section));
17071
17262
  }
17072
17263
  async function confirm(o, expected) {
17073
17264
  if (!o.repo.hooks.released) return void 0;
@@ -17160,8 +17351,8 @@ function closeReleaseCheckout(cwd, root, dir, branch, run3 = gitOk) {
17160
17351
  function openReleaseCheckout(o) {
17161
17352
  const { remote, base } = o.repo.branch;
17162
17353
  const branch = `crew/release-${Date.now()}`;
17163
- const root = mkdtempSync3(join38(tmpdir5(), "crew-release-"));
17164
- const dir = join38(root, "checkout");
17354
+ const root = mkdtempSync3(join39(tmpdir5(), "crew-release-"));
17355
+ const dir = join39(root, "checkout");
17165
17356
  o.emit.emit(`release checkout: ${dir}`, { step: "release" });
17166
17357
  const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
17167
17358
  try {
@@ -17171,10 +17362,10 @@ function openReleaseCheckout(o) {
17171
17362
  throw e;
17172
17363
  }
17173
17364
  for (const f of o.repo.worktrees.copy) {
17174
- const src = join38(o.cwd, f);
17175
- if (!existsSync34(src)) continue;
17176
- mkdirSync24(dirname20(join38(dir, f)), { recursive: true });
17177
- copyFileSync(src, join38(dir, f));
17365
+ const src = join39(o.cwd, f);
17366
+ if (!existsSync35(src)) continue;
17367
+ mkdirSync25(dirname21(join39(dir, f)), { recursive: true });
17368
+ copyFileSync(src, join39(dir, f));
17178
17369
  }
17179
17370
  return { dir, branch, close };
17180
17371
  }
@@ -17764,6 +17955,49 @@ async function mergeAndRelease(o, decision, fresh, releaseBranch, isolated = fal
17764
17955
  return { merged, conflicts, unbuildable, version, tag, deployed, integrated, confirmed, decision };
17765
17956
  }
17766
17957
 
17958
+ // src/repo-map.ts
17959
+ var CHARS_PER_TOKEN = 4;
17960
+ var MAP_TIMEOUT_MS = 6e4;
17961
+ function budgetMap(text2, tokenBudget) {
17962
+ const body = text2.replace(/\s+$/, "");
17963
+ const limit = tokenBudget * CHARS_PER_TOKEN;
17964
+ if (body.length <= limit) return body;
17965
+ const lines2 = body.split("\n");
17966
+ const noteFor = (kept2) => `\u2026 ${lines2.length - kept2} of ${lines2.length} lines cut to fit ship.mapTokenBudget (${tokenBudget} tokens)`;
17967
+ let kept = lines2.length;
17968
+ let size = body.length;
17969
+ while (kept > 0 && size + 1 + noteFor(kept).length > limit) {
17970
+ kept--;
17971
+ size -= lines2[kept].length + 1;
17972
+ }
17973
+ return [...lines2.slice(0, kept), noteFor(kept)].join("\n");
17974
+ }
17975
+ function loadRepoMap(repo, tokenBudget, shellFallback) {
17976
+ const script = repo.config.hooks.map;
17977
+ if (!script) return Promise.resolve({ skipped: "" });
17978
+ return runScript(script, {
17979
+ cwd: repo.dir,
17980
+ shell: resolveShell(repo.config.shell ?? shellFallback),
17981
+ env: { CREW_REPO: repo.name },
17982
+ timeoutMs: MAP_TIMEOUT_MS
17983
+ }).then(
17984
+ (r) => {
17985
+ if (r.code !== 0) return { skipped: `the \`map\` hook for ${repo.name} exited ${r.code}, so the brief has no repository map` };
17986
+ if (!r.stdout.trim()) return { skipped: `the \`map\` hook for ${repo.name} printed nothing, so the brief has no repository map` };
17987
+ return { text: budgetMap(r.stdout, tokenBudget) };
17988
+ },
17989
+ (e) => ({ skipped: `the \`map\` hook for ${repo.name} could not run (${e instanceof Error ? e.message : String(e)}), so the brief has no repository map` })
17990
+ );
17991
+ }
17992
+ async function withRepoMaps(repos, tokenBudget, shellFallback) {
17993
+ return Promise.all(repos.map(async (r) => {
17994
+ if (!r.config.hooks.map) return r;
17995
+ const out = await loadRepoMap(r, tokenBudget, shellFallback);
17996
+ if ("skipped" in out) return out.skipped ? { ...r, mapSkipped: out.skipped } : r;
17997
+ return { ...r, map: out.text };
17998
+ }));
17999
+ }
18000
+
17767
18001
  // src/failure-alert.ts
17768
18002
  var FAILURE_ALERT_MARKER = calloutHeader("CAUTION", "Failure Alert");
17769
18003
  function findOpenFailureAlert(tickets, headline) {
@@ -18153,7 +18387,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
18153
18387
  }
18154
18388
 
18155
18389
  // src/watch.ts
18156
- import { watch as watchFile, existsSync as existsSync35, statSync as statSync11, openSync as openSync4, readSync as readSync2, closeSync as closeSync4 } from "fs";
18390
+ import { watch as watchFile, existsSync as existsSync36, statSync as statSync11, openSync as openSync4, readSync as readSync2, closeSync as closeSync4 } from "fs";
18157
18391
  var E = "\x1B[";
18158
18392
  var dim = (s) => `${E}2m${s}${E}0m`;
18159
18393
  var bold = (s) => `${E}1m${s}${E}0m`;
@@ -18254,7 +18488,7 @@ function renderHeaders(states, now = Date.now()) {
18254
18488
  return states.map((s) => renderHeader(s, now)).join("");
18255
18489
  }
18256
18490
  function readFrom(file, from) {
18257
- if (!existsSync35(file)) return { events: [], offset: from };
18491
+ if (!existsSync36(file)) return { events: [], offset: from };
18258
18492
  const size = statSync11(file).size;
18259
18493
  if (size < from) return readFrom(file, 0);
18260
18494
  if (size === from) return { events: [], offset: from };
@@ -18497,8 +18731,8 @@ function nextRoles(decision, ran) {
18497
18731
  }
18498
18732
 
18499
18733
  // src/ship-name.ts
18500
- import { existsSync as existsSync36, mkdirSync as mkdirSync25, readFileSync as readFileSync32, renameSync as renameSync5, writeFileSync as writeFileSync28 } from "fs";
18501
- import { dirname as dirname21 } from "path";
18734
+ import { existsSync as existsSync37, mkdirSync as mkdirSync26, readFileSync as readFileSync33, renameSync as renameSync5, writeFileSync as writeFileSync29 } from "fs";
18735
+ import { dirname as dirname22 } from "path";
18502
18736
  import { parseDocument as parseDocument5 } from "yaml";
18503
18737
  var ShipNameError = class extends Error {
18504
18738
  };
@@ -18559,34 +18793,34 @@ async function syncRouteName(i) {
18559
18793
  return { status: "ok", ...base, action, name: i.local };
18560
18794
  }
18561
18795
  function readSyncedName(resolvedFile) {
18562
- if (!existsSync36(resolvedFile)) return void 0;
18796
+ if (!existsSync37(resolvedFile)) return void 0;
18563
18797
  try {
18564
- const v = JSON.parse(readFileSync32(resolvedFile, "utf8")).shipName;
18798
+ const v = JSON.parse(readFileSync33(resolvedFile, "utf8")).shipName;
18565
18799
  return typeof v === "string" ? v : void 0;
18566
18800
  } catch {
18567
18801
  return void 0;
18568
18802
  }
18569
18803
  }
18570
18804
  function writeSyncedName(resolvedFile, name) {
18571
- if (!existsSync36(resolvedFile)) return;
18805
+ if (!existsSync37(resolvedFile)) return;
18572
18806
  let raw;
18573
18807
  try {
18574
- raw = JSON.parse(readFileSync32(resolvedFile, "utf8"));
18808
+ raw = JSON.parse(readFileSync33(resolvedFile, "utf8"));
18575
18809
  } catch {
18576
18810
  return;
18577
18811
  }
18578
18812
  if (raw.shipName === name) return;
18579
18813
  raw.shipName = name;
18580
- mkdirSync25(dirname21(resolvedFile), { recursive: true });
18814
+ mkdirSync26(dirname22(resolvedFile), { recursive: true });
18581
18815
  const tmp = `${resolvedFile}.tmp${process.pid}`;
18582
- writeFileSync28(tmp, `${JSON.stringify(raw, null, 2)}
18816
+ writeFileSync29(tmp, `${JSON.stringify(raw, null, 2)}
18583
18817
  `);
18584
18818
  renameSync5(tmp, resolvedFile);
18585
18819
  }
18586
18820
  async function reconcileShipName(i) {
18587
18821
  let local = i.ship.name;
18588
18822
  try {
18589
- local = shipNameOfYaml(readFileSync32(i.configFile, "utf8")) ?? local;
18823
+ local = shipNameOfYaml(readFileSync33(i.configFile, "utf8")) ?? local;
18590
18824
  } catch {
18591
18825
  }
18592
18826
  const resolvedFile = resolvedPathFor(i.stateDir, i.route);
@@ -18610,8 +18844,8 @@ async function reconcileShipName(i) {
18610
18844
  }
18611
18845
  if (result.status !== "ok") return result;
18612
18846
  if (result.action === "adopt" || result.action === "row-wins") {
18613
- const text2 = readFileSync32(i.configFile, "utf8");
18614
- writeFileSync28(i.configFile, setShipNameInYaml(text2, result.name));
18847
+ const text2 = readFileSync33(i.configFile, "utf8");
18848
+ writeFileSync29(i.configFile, setShipNameInYaml(text2, result.name));
18615
18849
  i.ship.name = result.name;
18616
18850
  if (result.action === "row-wins") {
18617
18851
  i.emit?.warn(
@@ -19270,8 +19504,8 @@ function describeCapability(i) {
19270
19504
  }
19271
19505
 
19272
19506
  // src/agents-sync-run.ts
19273
- import { existsSync as existsSync37, mkdirSync as mkdirSync26, readFileSync as readFileSync33, writeFileSync as writeFileSync29 } from "fs";
19274
- import { dirname as dirname22 } from "path";
19507
+ import { existsSync as existsSync38, mkdirSync as mkdirSync27, readFileSync as readFileSync34, writeFileSync as writeFileSync30 } from "fs";
19508
+ import { dirname as dirname23 } from "path";
19275
19509
  async function runAgentsSync(target, o) {
19276
19510
  const dryRun = o.dryRun ?? false;
19277
19511
  const force = o.force ?? false;
@@ -19304,12 +19538,12 @@ async function runAgentsSync(target, o) {
19304
19538
  for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
19305
19539
  if (!dryRun) {
19306
19540
  const resolvedPath = resolvedPathFor(o.stateDir, target.route);
19307
- const raw = existsSync37(resolvedPath) ? JSON.parse(readFileSync33(resolvedPath, "utf8")) : {};
19541
+ const raw = existsSync38(resolvedPath) ? JSON.parse(readFileSync34(resolvedPath, "utf8")) : {};
19308
19542
  raw.agentPersonas = personaResult.agentPersonas;
19309
19543
  if (skillResult) raw.agentSkills = skillResult.agentSkills;
19310
19544
  if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
19311
- mkdirSync26(dirname22(resolvedPath), { recursive: true });
19312
- writeFileSync29(resolvedPath, `${JSON.stringify(raw, null, 2)}
19545
+ mkdirSync27(dirname23(resolvedPath), { recursive: true });
19546
+ writeFileSync30(resolvedPath, `${JSON.stringify(raw, null, 2)}
19313
19547
  `);
19314
19548
  if (target.resolved) {
19315
19549
  target.resolved.agentPersonas = personaResult.agentPersonas;
@@ -19413,7 +19647,7 @@ async function showLogEntry(route2, entryId, opts = {}) {
19413
19647
 
19414
19648
  // src/repos-check.ts
19415
19649
  import { spawnSync as spawnSync9 } from "child_process";
19416
- import { readFileSync as readFileSync34 } from "fs";
19650
+ import { readFileSync as readFileSync35 } from "fs";
19417
19651
  import { basename as basename11 } from "path";
19418
19652
  var POSIX_SHELL = /^(?:ba|z|da|k|a|mk)?sh$|^fish$/;
19419
19653
  function checkHookSyntax(script, shell) {
@@ -19454,7 +19688,7 @@ function checkRepoSpec(dir, opts = {}) {
19454
19688
  }
19455
19689
  let parsed;
19456
19690
  try {
19457
- parsed = parseRepoConfig(readFileSync34(file, "utf8"), file);
19691
+ parsed = parseRepoConfig(readFileSync35(file, "utf8"), file);
19458
19692
  } catch (e) {
19459
19693
  if (!(e instanceof RepoConfigError)) throw e;
19460
19694
  return { ...empty, file, error: e.message, exitCode: 1 };
@@ -19733,7 +19967,7 @@ function existingRepoConfig(f) {
19733
19967
  }
19734
19968
 
19735
19969
  // src/repos-hooks-agent.ts
19736
- import { spawn as spawn7, execFileSync as execFileSync10 } from "child_process";
19970
+ import { spawn as spawn7, execFileSync as execFileSync11 } from "child_process";
19737
19971
  import { realpathSync as realpathSync2 } from "fs";
19738
19972
  import { isAbsolute as isAbsolute3, relative } from "path";
19739
19973
  var AgentSuggestError = class extends Error {
@@ -19836,7 +20070,7 @@ function classifyFailure(o) {
19836
20070
  }
19837
20071
  function porcelain(dir) {
19838
20072
  try {
19839
- return execFileSync10("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: dir, encoding: "utf8" });
20073
+ return execFileSync11("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: dir, encoding: "utf8" });
19840
20074
  } catch {
19841
20075
  return void 0;
19842
20076
  }
@@ -19905,7 +20139,7 @@ ${run3.err}` });
19905
20139
  }
19906
20140
 
19907
20141
  // src/sample-ticket.ts
19908
- import { basename as basename12, dirname as dirname23, join as join39 } from "path";
20142
+ import { basename as basename12, dirname as dirname24, join as join40 } from "path";
19909
20143
  var SAMPLE_REPO = "crew-sample";
19910
20144
  var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
19911
20145
  var SAMPLE_MARKER = "sample: true";
@@ -19928,8 +20162,8 @@ has something safe to work on: its test and build hooks always pass, it has no
19928
20162
  remote, and \`crew sample-ticket --remove\` deletes it.
19929
20163
  `;
19930
20164
  function sampleDir(existing, reposBase, workspace) {
19931
- if (existing.length > 0) return join39(dirname23(existing[0]), SAMPLE_REPO);
19932
- return join39(reposBase, workspace, SAMPLE_REPO);
20165
+ if (existing.length > 0) return join40(dirname24(existing[0]), SAMPLE_REPO);
20166
+ return join40(reposBase, workspace, SAMPLE_REPO);
19933
20167
  }
19934
20168
  var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
19935
20169
  function sampleDescription() {
@@ -19973,8 +20207,8 @@ function planSampleTicket(i) {
19973
20207
  }
19974
20208
 
19975
20209
  // src/worktree-sweep.ts
19976
- import { existsSync as existsSync38, readFileSync as readFileSync35, readdirSync as readdirSync14 } from "fs";
19977
- import { basename as basename13, dirname as dirname24, join as join40, resolve as resolve5 } from "path";
20210
+ import { existsSync as existsSync39, readFileSync as readFileSync36, readdirSync as readdirSync14 } from "fs";
20211
+ import { basename as basename13, dirname as dirname25, join as join41, resolve as resolve5 } from "path";
19978
20212
  function planWorktreeSweep(target, tickets, contract) {
19979
20213
  const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
19980
20214
  const verified = contract.statuses.verified;
@@ -20038,7 +20272,7 @@ function worktreeTicketNumbers(target) {
20038
20272
  return [...nums];
20039
20273
  }
20040
20274
  function siblingWorktreeDirs(target) {
20041
- const parent = dirname24(target.dir.replace(/[/\\]+$/, ""));
20275
+ const parent = dirname25(target.dir.replace(/[/\\]+$/, ""));
20042
20276
  const stem = `${basename13(target.dir.replace(/[/\\]+$/, ""))}-`;
20043
20277
  const registered = new Set(worktrees(target.dir).map((w) => resolve5(w.path)));
20044
20278
  let names;
@@ -20051,7 +20285,7 @@ function siblingWorktreeDirs(target) {
20051
20285
  for (const name of names) {
20052
20286
  if (!name.startsWith(stem)) continue;
20053
20287
  const m = DIR_NUMBER_SUFFIXED.exec(name);
20054
- const path = join40(parent, name);
20288
+ const path = join41(parent, name);
20055
20289
  if (m && !registered.has(resolve5(path))) out.push({ path, number: m[1] });
20056
20290
  }
20057
20291
  return out;
@@ -20086,7 +20320,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
20086
20320
  let removed = 0;
20087
20321
  let keptBranches = 0;
20088
20322
  for (const a of actions) {
20089
- if (!dryRun && base && !a.keepBranch && existsSync38(a.path) && isHandBackMerge(a.path, base)) {
20323
+ if (!dryRun && base && !a.keepBranch && existsSync39(a.path) && isHandBackMerge(a.path, base)) {
20090
20324
  if (gitOk(a.path, ["merge", "--abort"]) !== null) {
20091
20325
  log.emit(
20092
20326
  `abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
@@ -20094,7 +20328,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
20094
20328
  );
20095
20329
  }
20096
20330
  }
20097
- if (!dryRun && existsSync38(a.path)) {
20331
+ if (!dryRun && existsSync39(a.path)) {
20098
20332
  let dirty = false;
20099
20333
  try {
20100
20334
  dirty = status(a.path).length > 0;
@@ -20105,7 +20339,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
20105
20339
  continue;
20106
20340
  }
20107
20341
  }
20108
- if (!dryRun && a.requirePushed && a.branch && existsSync38(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
20342
+ if (!dryRun && a.requirePushed && a.branch && existsSync39(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
20109
20343
  refuse(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
20110
20344
  continue;
20111
20345
  }
@@ -20154,7 +20388,7 @@ function isHandBackMerge(path, base) {
20154
20388
  if (!msgPath) return false;
20155
20389
  let first;
20156
20390
  try {
20157
- first = readFileSync35(resolve5(path, msgPath), "utf8").split("\n")[0] ?? "";
20391
+ first = readFileSync36(resolve5(path, msgPath), "utf8").split("\n")[0] ?? "";
20158
20392
  } catch {
20159
20393
  return false;
20160
20394
  }
@@ -20259,14 +20493,14 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
20259
20493
  }
20260
20494
 
20261
20495
  // src/stream-sweep.ts
20262
- import { existsSync as existsSync39, readdirSync as readdirSync15, rmSync as rmSync18, statSync as statSync12 } from "fs";
20263
- import { join as join41 } from "path";
20496
+ import { existsSync as existsSync40, readdirSync as readdirSync15, rmSync as rmSync18, statSync as statSync12 } from "fs";
20497
+ import { join as join42 } from "path";
20264
20498
  var DAY_MS2 = 24 * 60 * 60 * 1e3;
20265
20499
  function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
20266
- const dir = join41(stateDir, "streams");
20267
- if (!existsSync39(dir)) return [];
20500
+ const dir = join42(stateDir, "streams");
20501
+ if (!existsSync40(dir)) return [];
20268
20502
  const cutoff = now - retentionDays * DAY_MS2;
20269
- return readdirSync15(dir).map((name) => join41(dir, name)).filter((path) => {
20503
+ return readdirSync15(dir).map((name) => join42(dir, name)).filter((path) => {
20270
20504
  try {
20271
20505
  return statSync12(path).mtimeMs < cutoff;
20272
20506
  } catch {
@@ -20292,9 +20526,9 @@ function applyStreamSweep(paths, dryRun, log) {
20292
20526
  return removed;
20293
20527
  }
20294
20528
  function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
20295
- if (!existsSync39(stateDir)) return [];
20529
+ if (!existsSync40(stateDir)) return [];
20296
20530
  const cutoff = now - retentionDays * DAY_MS2;
20297
- return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join41(stateDir, name)).filter((path) => {
20531
+ return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join42(stateDir, name)).filter((path) => {
20298
20532
  try {
20299
20533
  return statSync12(path).mtimeMs < cutoff;
20300
20534
  } catch {
@@ -20304,12 +20538,12 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
20304
20538
  }
20305
20539
 
20306
20540
  // src/cli.ts
20307
- import { realpathSync as realpathSync3, readFileSync as readFileSync41, existsSync as existsSync46, mkdirSync as mkdirSync33, writeFileSync as writeFileSync34, rmSync as rmSync23, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync6, statSync as statSync16, readdirSync as readdirSync20 } from "fs";
20541
+ import { realpathSync as realpathSync3, readFileSync as readFileSync42, existsSync as existsSync47, mkdirSync as mkdirSync34, writeFileSync as writeFileSync35, rmSync as rmSync23, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync6, statSync as statSync16, readdirSync as readdirSync20 } from "fs";
20308
20542
  import { createInterface } from "readline/promises";
20309
20543
 
20310
20544
  // src/periscope-cmd.ts
20311
- import { existsSync as existsSync40, mkdirSync as mkdirSync27, readFileSync as readFileSync36, rmSync as rmSync19, writeFileSync as writeFileSync30 } from "fs";
20312
- import { join as join42 } from "path";
20545
+ import { existsSync as existsSync41, mkdirSync as mkdirSync28, readFileSync as readFileSync37, rmSync as rmSync19, writeFileSync as writeFileSync31 } from "fs";
20546
+ import { join as join43 } from "path";
20313
20547
  var PeriscopeError = class extends Error {
20314
20548
  };
20315
20549
  var LivePeriscopeError = class extends PeriscopeError {
@@ -20319,28 +20553,29 @@ var LivePeriscopeError = class extends PeriscopeError {
20319
20553
  this.data = { refused: "live_periscope_exists", ...live };
20320
20554
  }
20321
20555
  };
20322
- var priorDir = (stateDir) => join42(stateDir, "periscope-priors");
20323
- var priorPath = (stateDir, ticketRecordId) => join42(priorDir(stateDir), `${ticketRecordId}.json`);
20556
+ var priorDir = (stateDir) => join43(stateDir, "periscope-priors");
20557
+ var priorPath = (stateDir, ticketRecordId) => join43(priorDir(stateDir), `${ticketRecordId}.json`);
20324
20558
  function removePeriscopePrior(stateDir, ticketRecordId) {
20325
20559
  rmSync19(priorPath(stateDir, ticketRecordId), { force: true });
20326
20560
  }
20327
20561
  function writePeriscopePrior(stateDir, ticketRecordId, prior) {
20328
20562
  try {
20329
- mkdirSync27(priorDir(stateDir), { recursive: true });
20330
- writeFileSync30(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
20563
+ mkdirSync28(priorDir(stateDir), { recursive: true });
20564
+ writeFileSync31(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
20331
20565
  } catch {
20332
20566
  }
20333
20567
  }
20334
20568
  function readPeriscopePrior(stateDir, ticketRecordId) {
20335
20569
  try {
20336
20570
  const p = priorPath(stateDir, ticketRecordId);
20337
- return existsSync40(p) ? JSON.parse(readFileSync36(p, "utf8")) : void 0;
20571
+ return existsSync41(p) ? JSON.parse(readFileSync37(p, "utf8")) : void 0;
20338
20572
  } catch {
20339
20573
  return void 0;
20340
20574
  }
20341
20575
  }
20342
20576
  var DEFAULT_MAX_PERISCOPES2 = 3;
20343
20577
  var DEFAULT_PERISCOPE_IDLE_MINUTES2 = 120;
20578
+ var DEFAULT_INTEL_STARTUP_SECONDS2 = 20;
20344
20579
  function parsePeriscopeUpArgs(positional2, flags) {
20345
20580
  const usage2 = "usage: crew periscope up [[route] REPO] [--topic TEXT | --issue NNN] [--isolate] [--host HOST] [--assignee CREW_ROW_ID] [--requested-by ID] [--no-open] [--json]";
20346
20581
  const rest = positional2.slice(2);
@@ -20408,12 +20643,15 @@ function openerFor(platform2) {
20408
20643
  return platform2 === "darwin" ? "open" : platform2 === "linux" ? "xdg-open" : void 0;
20409
20644
  }
20410
20645
  function upOutput(res, terminal) {
20411
- return terminal ? `${res.ticket}
20646
+ const block = terminal ? `${res.ticket}
20412
20647
  ${res.url}
20413
20648
  ${res.worktree}
20414
20649
  ` : `${res.ticket}
20415
20650
  ${res.worktree}
20416
20651
  ${res.url}
20652
+ `;
20653
+ return `${block}
20654
+ ${res.codeTools}
20417
20655
  `;
20418
20656
  }
20419
20657
  function shouldOpen(f) {
@@ -20510,6 +20748,23 @@ ${tail(preview?.output ?? "")}`);
20510
20748
  if (!url) return abandon(`preview printed no URL as its last line
20511
20749
  ${tail(preview.output)}`);
20512
20750
  d.say("preview", `preview at ${url}`, { url });
20751
+ const intel = await probeIntel(
20752
+ { runHook: (e) => d.runHook("intel", wt.path, e) },
20753
+ env,
20754
+ d.intelStartupSeconds ?? DEFAULT_INTEL_STARTUP_SECONDS2
20755
+ );
20756
+ if (intel.state === "ready") d.say("intel", `symbol service at ${intel.url} (${intel.tools.length} tools)`, { intelUrl: intel.url, intelTools: intel.tools });
20757
+ else if (intel.state === "failed") d.say("warning", `symbol service not started: ${intel.reason}`);
20758
+ let codeOutcome = intel;
20759
+ if (intel.state === "ready" && d.registerCode) {
20760
+ try {
20761
+ for (const why of d.registerCode(wt.path, intel.url).skipped) d.say("warning", why);
20762
+ } catch (e) {
20763
+ codeOutcome = { state: "failed", reason: `registering it failed: ${e.message}` };
20764
+ d.say("warning", `symbol service not registered: ${e.message}`);
20765
+ }
20766
+ }
20767
+ const codeTools = codeToolsParagraph(codeOutcome);
20513
20768
  const now = d.now();
20514
20769
  let row;
20515
20770
  try {
@@ -20541,6 +20796,7 @@ ${tail(preview.output)}`);
20541
20796
  host: d.host,
20542
20797
  state: "live",
20543
20798
  isolated: args.isolate,
20799
+ ...intel.state === "ready" ? { intelUrl: intel.url, intelTools: intel.tools } : {},
20544
20800
  startedAt: now.toISOString(),
20545
20801
  lastActivityAt: now.toISOString(),
20546
20802
  expiresAt: new Date(now.getTime() + d.caps.idleMinutes * 6e4).toISOString()
@@ -20558,6 +20814,7 @@ ${tail(preview.output)}`);
20558
20814
  branch: wt.branch,
20559
20815
  url,
20560
20816
  periscopeRecordId: row.id,
20817
+ codeTools,
20561
20818
  ...prior ? { existing: true, priorStatus: prior.status } : {}
20562
20819
  };
20563
20820
  }
@@ -20737,9 +20994,9 @@ ${tail2(preview.output)}`);
20737
20994
  }
20738
20995
 
20739
20996
  // src/periscope-idle.ts
20740
- import { execFileSync as execFileSync11 } from "child_process";
20997
+ import { execFileSync as execFileSync12 } from "child_process";
20741
20998
  import { statSync as statSync13 } from "fs";
20742
- import { join as join43 } from "path";
20999
+ import { join as join44 } from "path";
20743
21000
  var MAX_SCANNED_FILES = 2e4;
20744
21001
  function newestWorktreeMtime(root) {
20745
21002
  const a = newestTrackedMtime(root);
@@ -20749,7 +21006,7 @@ function newestWorktreeMtime(root) {
20749
21006
  function newestTrackedMtime(root) {
20750
21007
  let listing;
20751
21008
  try {
20752
- listing = execFileSync11("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
21009
+ listing = execFileSync12("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
20753
21010
  cwd: root,
20754
21011
  encoding: "utf8",
20755
21012
  stdio: ["ignore", "pipe", "ignore"],
@@ -20764,7 +21021,7 @@ function newestTrackedMtime(root) {
20764
21021
  if (!rel) continue;
20765
21022
  if (++scanned > MAX_SCANNED_FILES) break;
20766
21023
  try {
20767
- const m = statSync13(join43(root, rel)).mtimeMs;
21024
+ const m = statSync13(join44(root, rel)).mtimeMs;
20768
21025
  if (newest === null || m > newest) newest = m;
20769
21026
  } catch {
20770
21027
  }
@@ -20839,21 +21096,21 @@ async function runPeriscopeIdlePass(d) {
20839
21096
  import { dirname as dirOf2, resolve as resolvePath } from "path";
20840
21097
 
20841
21098
  // src/ssh-keys.ts
20842
- import { execFileSync as execFileSync12 } from "child_process";
20843
- import { existsSync as existsSync41, mkdirSync as mkdirSync28, readFileSync as readFileSync37, chmodSync as chmodSync3 } from "fs";
20844
- import { dirname as dirname25, join as join44 } from "path";
21099
+ import { execFileSync as execFileSync13 } from "child_process";
21100
+ import { existsSync as existsSync42, mkdirSync as mkdirSync29, readFileSync as readFileSync38, chmodSync as chmodSync3 } from "fs";
21101
+ import { dirname as dirname26, join as join45 } from "path";
20845
21102
  var SshKeyError = class extends Error {
20846
21103
  };
20847
21104
  function shipSshKeyPathFor(stateDir) {
20848
- return join44(stateDir, "ssh", "id_ed25519");
21105
+ return join45(stateDir, "ssh", "id_ed25519");
20849
21106
  }
20850
21107
  function ensureShipSshKeypair(stateDir) {
20851
21108
  const privateKeyPath = shipSshKeyPathFor(stateDir);
20852
21109
  const publicKeyPath = `${privateKeyPath}.pub`;
20853
- if (!existsSync41(privateKeyPath)) {
20854
- mkdirSync28(dirname25(privateKeyPath), { recursive: true });
21110
+ if (!existsSync42(privateKeyPath)) {
21111
+ mkdirSync29(dirname26(privateKeyPath), { recursive: true });
20855
21112
  try {
20856
- execFileSync12("ssh-keygen", [
21113
+ execFileSync13("ssh-keygen", [
20857
21114
  "-t",
20858
21115
  "ed25519",
20859
21116
  "-N",
@@ -20871,15 +21128,15 @@ function ensureShipSshKeypair(stateDir) {
20871
21128
  }
20872
21129
  chmodSync3(privateKeyPath, 384);
20873
21130
  }
20874
- if (!existsSync41(publicKeyPath)) {
21131
+ if (!existsSync42(publicKeyPath)) {
20875
21132
  throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
20876
21133
  }
20877
- const publicKey = readFileSync37(publicKeyPath, "utf8").trim();
21134
+ const publicKey = readFileSync38(publicKeyPath, "utf8").trim();
20878
21135
  return { privateKeyPath, publicKeyPath, publicKey };
20879
21136
  }
20880
21137
  function sshKeygenAvailable() {
20881
21138
  try {
20882
- execFileSync12("ssh-keygen", ["-V"], { stdio: ["ignore", "ignore", "ignore"] });
21139
+ execFileSync13("ssh-keygen", ["-V"], { stdio: ["ignore", "ignore", "ignore"] });
20883
21140
  return true;
20884
21141
  } catch (e) {
20885
21142
  return e.code !== "ENOENT";
@@ -21038,12 +21295,12 @@ async function unregisterEndpoint(workspaceId, info, d) {
21038
21295
  import { createServer as createHttpServer } from "http";
21039
21296
  import { spawn as spawn8 } from "child_process";
21040
21297
  import { randomBytes } from "crypto";
21041
- import { existsSync as existsSync42, mkdirSync as mkdirSync30, readFileSync as readFileSync38, writeFileSync as writeFileSync31, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
21042
- import { join as join45 } from "path";
21298
+ import { existsSync as existsSync43, mkdirSync as mkdirSync31, readFileSync as readFileSync39, writeFileSync as writeFileSync32, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
21299
+ import { join as join46 } from "path";
21043
21300
 
21044
21301
  // src/passenger-mirror.ts
21045
- import { mkdirSync as mkdirSync29 } from "fs";
21046
- import { dirname as dirname26 } from "path";
21302
+ import { mkdirSync as mkdirSync30 } from "fs";
21303
+ import { dirname as dirname27 } from "path";
21047
21304
  function mirrorRepoOf(spec) {
21048
21305
  const fromOrigin = spec.originUrl ? parseRemote(spec.originUrl) : null;
21049
21306
  if (fromOrigin) return fromOrigin;
@@ -21061,9 +21318,9 @@ async function materializeMirrorCheckouts(specs, cacheFor = (s) => new SandboxCa
21061
21318
  continue;
21062
21319
  }
21063
21320
  try {
21064
- const parent = dirname26(spec.dir);
21065
- mkdirSync29(parent, { recursive: true });
21066
- mkdirSync29(spec.dir, { recursive: true });
21321
+ const parent = dirname27(spec.dir);
21322
+ mkdirSync30(parent, { recursive: true });
21323
+ mkdirSync30(spec.dir, { recursive: true });
21067
21324
  const r = await cacheFor(spec).materialize(repo, spec.base, parent, spec.name);
21068
21325
  out.push({ ...base, action: r.changed ? "updated" : "unchanged", detail: r.tip });
21069
21326
  } catch (e) {
@@ -21169,21 +21426,21 @@ var defaultSpawn2 = (cmd, args, env) => {
21169
21426
  return child;
21170
21427
  };
21171
21428
  function syncDaemonStatePath(stateDir, workspaceId) {
21172
- return join45(stateDir, "passengers", `${workspaceId}-sync.json`);
21429
+ return join46(stateDir, "passengers", `${workspaceId}-sync.json`);
21173
21430
  }
21174
21431
  function readPersistedSyncDaemon(stateDir, workspaceId) {
21175
21432
  const path = syncDaemonStatePath(stateDir, workspaceId);
21176
- if (!existsSync42(path)) return void 0;
21433
+ if (!existsSync43(path)) return void 0;
21177
21434
  try {
21178
- return JSON.parse(readFileSync38(path, "utf8"));
21435
+ return JSON.parse(readFileSync39(path, "utf8"));
21179
21436
  } catch {
21180
21437
  return void 0;
21181
21438
  }
21182
21439
  }
21183
21440
  function writePersistedSyncDaemon(stateDir, state) {
21184
21441
  const path = syncDaemonStatePath(stateDir, state.workspaceId);
21185
- mkdirSync30(join45(stateDir, "passengers"), { recursive: true });
21186
- writeFileSync31(path, `${JSON.stringify(state, null, 2)}
21442
+ mkdirSync31(join46(stateDir, "passengers"), { recursive: true });
21443
+ writeFileSync32(path, `${JSON.stringify(state, null, 2)}
21187
21444
  `);
21188
21445
  }
21189
21446
  function removePersistedSyncDaemon(stateDir, workspaceId) {
@@ -21203,8 +21460,8 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
21203
21460
  }
21204
21461
  }
21205
21462
  function readAllPersistedSyncWorkspaceIds(stateDir) {
21206
- const dir = join45(stateDir, "passengers");
21207
- if (!existsSync42(dir)) return [];
21463
+ const dir = join46(stateDir, "passengers");
21464
+ if (!existsSync43(dir)) return [];
21208
21465
  const suffix = "-sync.json";
21209
21466
  return readdirSync16(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
21210
21467
  }
@@ -21292,8 +21549,8 @@ function describeCause(err3) {
21292
21549
  }
21293
21550
 
21294
21551
  // src/daemon.ts
21295
- import { existsSync as existsSync43, mkdirSync as mkdirSync31, readFileSync as readFileSync39, readdirSync as readdirSync17, rmSync as rmSync20, statSync as statSync14, writeFileSync as writeFileSync32 } from "fs";
21296
- import { join as join46 } from "path";
21552
+ import { existsSync as existsSync44, mkdirSync as mkdirSync32, readFileSync as readFileSync40, readdirSync as readdirSync17, rmSync as rmSync20, statSync as statSync14, writeFileSync as writeFileSync33 } from "fs";
21553
+ import { join as join47 } from "path";
21297
21554
  import { randomBytes as randomBytes2 } from "crypto";
21298
21555
  var DEFAULT_FLOOR_MS = 5e3;
21299
21556
  var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
@@ -21381,7 +21638,7 @@ function latestMtime(path) {
21381
21638
  if (!stat2.isDirectory()) return 0;
21382
21639
  let latest = stat2.mtimeMs;
21383
21640
  for (const entry of readdirSync17(path, { withFileTypes: true })) {
21384
- latest = Math.max(latest, latestMtime(join46(path, entry.name)));
21641
+ latest = Math.max(latest, latestMtime(join47(path, entry.name)));
21385
21642
  }
21386
21643
  return latest;
21387
21644
  }
@@ -21390,10 +21647,10 @@ function computeTreeSignature(paths) {
21390
21647
  }
21391
21648
  function makeStaleChecker(paths, opts = {}) {
21392
21649
  const initial = computeTreeSignature(paths);
21393
- return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync43(opts.reloadSentinel);
21650
+ return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync44(opts.reloadSentinel);
21394
21651
  }
21395
21652
  function reloadSentinelFor(stateDir) {
21396
- return join46(stateDir, "reload-requested");
21653
+ return join47(stateDir, "reload-requested");
21397
21654
  }
21398
21655
  function readReloadRequestedAt(stateDir) {
21399
21656
  try {
@@ -21403,17 +21660,17 @@ function readReloadRequestedAt(stateDir) {
21403
21660
  }
21404
21661
  }
21405
21662
  function daemonStampFor(stateDir) {
21406
- return join46(stateDir, "daemon.started");
21663
+ return join47(stateDir, "daemon.started");
21407
21664
  }
21408
21665
  function noteDaemonStarted(stateDir, now = Date.now()) {
21409
- mkdirSync31(stateDir, { recursive: true });
21666
+ mkdirSync32(stateDir, { recursive: true });
21410
21667
  rmSync20(reloadSentinelFor(stateDir), { force: true });
21411
- writeFileSync32(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
21668
+ writeFileSync33(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
21412
21669
  `);
21413
21670
  }
21414
21671
  function readDaemonStarted(stateDir) {
21415
21672
  try {
21416
- const v = JSON.parse(readFileSync39(daemonStampFor(stateDir), "utf8")).startedAt;
21673
+ const v = JSON.parse(readFileSync40(daemonStampFor(stateDir), "utf8")).startedAt;
21417
21674
  return typeof v === "number" ? v : null;
21418
21675
  } catch {
21419
21676
  return null;
@@ -21971,7 +22228,7 @@ async function buildEnvironment(route2, ship, ticket) {
21971
22228
  });
21972
22229
  const authorship = await tracker.authorshipProbe().catch(() => null);
21973
22230
  const attachments = await tracker.attachmentsProbe().catch(() => null);
21974
- return renderEnvironment({ route: route2, userAgent: ship.userAgent, repos, contract, sourceTicket: ticket, authorship, attachments, shipName: ship.name, ...sandboxEnv(ship) });
22231
+ return renderEnvironment({ route: route2, userAgent: ship.userAgent, repos: await withRepoMaps(repos, ship.mapTokenBudget, ship.shell), contract, sourceTicket: ticket, authorship, attachments, shipName: ship.name, ...sandboxEnv(ship) });
21975
22232
  }
21976
22233
 
21977
22234
  // src/control-api.ts
@@ -21979,7 +22236,7 @@ import { createHash as createHash10 } from "crypto";
21979
22236
  import {
21980
22237
  chmodSync as chmodSync4,
21981
22238
  closeSync as closeSync5,
21982
- existsSync as existsSync44,
22239
+ existsSync as existsSync45,
21983
22240
  fstatSync,
21984
22241
  openSync as openSync5,
21985
22242
  readSync as readSync3,
@@ -21990,7 +22247,7 @@ import {
21990
22247
  } from "fs";
21991
22248
  import { createConnection } from "net";
21992
22249
  import { createServer } from "http";
21993
- import { basename as basename14, dirname as dirname27, join as join47 } from "path";
22250
+ import { basename as basename14, dirname as dirname28, join as join48 } from "path";
21994
22251
  import { execFile as execFile3, spawn as spawn10 } from "child_process";
21995
22252
 
21996
22253
  // src/periscope-control.ts
@@ -22219,7 +22476,7 @@ function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
22219
22476
  // src/control-api.ts
22220
22477
  var PAUSABLE = ["dev", "design", "qa"];
22221
22478
  var CONTROL_SOCKET_NAME = "control.sock";
22222
- var controlSocketFor = (stateDir) => join47(stateDir, CONTROL_SOCKET_NAME);
22479
+ var controlSocketFor = (stateDir) => join48(stateDir, CONTROL_SOCKET_NAME);
22223
22480
  var MAX_SOCKET_PATH = 100;
22224
22481
  var MAX_TAIL_BYTES = 1024 * 1024;
22225
22482
  var MAX_WAIT_SECONDS = 30;
@@ -22306,7 +22563,7 @@ function candidates(live, rotated) {
22306
22563
  const out = [live];
22307
22564
  if (!rotated) return out;
22308
22565
  try {
22309
- for (const n of readdirSync18(dirname27(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join47(dirname27(live), n));
22566
+ for (const n of readdirSync18(dirname28(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join48(dirname28(live), n));
22310
22567
  } catch {
22311
22568
  }
22312
22569
  return out;
@@ -22350,7 +22607,7 @@ function lastLines2(path, n) {
22350
22607
  }
22351
22608
  function readTail(live, opts) {
22352
22609
  const max = opts.maxBytes ?? MAX_TAIL_BYTES;
22353
- if (!existsSync44(live)) {
22610
+ if (!existsSync45(live)) {
22354
22611
  return { lines: [], cursor: opts.after ?? encodeCursor({ dev: 0, ino: 0, off: 0 }), reset: false };
22355
22612
  }
22356
22613
  const cur = opts.after ? decodeCursor(opts.after) : null;
@@ -22466,7 +22723,7 @@ async function tailReply(path, q2, rotated, abort) {
22466
22723
  const deadline = Date.now() + Math.min(wait, MAX_WAIT_SECONDS) * 1e3;
22467
22724
  let r = readTail(path, { after, tail: tail3, rotated });
22468
22725
  while (after && !r.lines.length && !r.reset && Date.now() < deadline && !abort.aborted) {
22469
- await waitForChange(dirname27(path), deadline - Date.now(), abort);
22726
+ await waitForChange(dirname28(path), deadline - Date.now(), abort);
22470
22727
  r = readTail(path, { after: r.cursor, rotated });
22471
22728
  }
22472
22729
  return reply2(200, r);
@@ -22506,8 +22763,8 @@ async function route(deps, method, url, req, abort) {
22506
22763
  if (p.startsWith("/streams/") && get2) {
22507
22764
  const run3 = decodeURIComponent(p.slice("/streams/".length));
22508
22765
  if (!RUN_NAME_RE.test(run3) || run3.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run3)) return err2(400, "not a run name");
22509
- const file = join47(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
22510
- if (!existsSync44(file)) return err2(404, "no such run");
22766
+ const file = join48(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
22767
+ if (!existsSync45(file)) return err2(404, "no such run");
22511
22768
  return tailReply(file, url.searchParams, false, abort);
22512
22769
  }
22513
22770
  if ((p === "/pause" || p === "/resume") && post3) {
@@ -22551,7 +22808,7 @@ function send(res, r) {
22551
22808
  res.end(body);
22552
22809
  }
22553
22810
  async function socketIsStale(path) {
22554
- if (!existsSync44(path)) return false;
22811
+ if (!existsSync45(path)) return false;
22555
22812
  return new Promise((resolve8) => {
22556
22813
  const c = createConnection(path);
22557
22814
  c.once("connect", () => {
@@ -22570,7 +22827,7 @@ async function startControlServer(deps) {
22570
22827
  return null;
22571
22828
  }
22572
22829
  if (await socketIsStale(path)) rmSync21(path, { force: true });
22573
- else if (existsSync44(path)) {
22830
+ else if (existsSync45(path)) {
22574
22831
  log(`control API not started: ${path} is in use by another process`);
22575
22832
  return null;
22576
22833
  }
@@ -22656,31 +22913,31 @@ function crewJson(args, timeoutMs = 6e4) {
22656
22913
 
22657
22914
  // src/periscope-mcp.ts
22658
22915
  import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
22659
- import { basename as basename15, dirname as dirname28, isAbsolute as isAbsolute4, join as join49, relative as relative2, resolve as resolve6, sep } from "path";
22916
+ import { basename as basename15, dirname as dirname29, isAbsolute as isAbsolute4, join as join50, relative as relative2, resolve as resolve6, sep } from "path";
22660
22917
  import { z } from "zod";
22661
22918
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
22662
22919
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
22663
22920
 
22664
22921
  // src/periscope-endpoints.ts
22665
22922
  import { randomBytes as randomBytes3 } from "crypto";
22666
- import { chmodSync as chmodSync5, existsSync as existsSync45, mkdirSync as mkdirSync32, readdirSync as readdirSync19, readFileSync as readFileSync40, rmSync as rmSync22, writeFileSync as writeFileSync33 } from "fs";
22667
- import { join as join48 } from "path";
22668
- var secretDir = (stateDir) => join48(stateDir, "periscope-mcp");
22669
- var secretFile = (stateDir, id) => join48(secretDir(stateDir), `${id}.json`);
22670
- var endpointDir = (stateDir) => join48(stateDir, "periscope-endpoints");
22671
- var endpointFile = (stateDir, id) => join48(endpointDir(stateDir), `${id}.json`);
22923
+ import { chmodSync as chmodSync5, existsSync as existsSync46, mkdirSync as mkdirSync33, readdirSync as readdirSync19, readFileSync as readFileSync41, rmSync as rmSync22, writeFileSync as writeFileSync34 } from "fs";
22924
+ import { join as join49 } from "path";
22925
+ var secretDir = (stateDir) => join49(stateDir, "periscope-mcp");
22926
+ var secretFile = (stateDir, id) => join49(secretDir(stateDir), `${id}.json`);
22927
+ var endpointDir = (stateDir) => join49(stateDir, "periscope-endpoints");
22928
+ var endpointFile = (stateDir, id) => join49(endpointDir(stateDir), `${id}.json`);
22672
22929
  function mintPeriscopeSecret(stateDir, ticketRecordId) {
22673
- mkdirSync32(secretDir(stateDir), { recursive: true, mode: 448 });
22930
+ mkdirSync33(secretDir(stateDir), { recursive: true, mode: 448 });
22674
22931
  const secret = randomBytes3(32).toString("base64url");
22675
22932
  const file = secretFile(stateDir, ticketRecordId);
22676
- writeFileSync33(file, `${JSON.stringify({ secret })}
22933
+ writeFileSync34(file, `${JSON.stringify({ secret })}
22677
22934
  `, { mode: 384 });
22678
22935
  chmodSync5(file, 384);
22679
22936
  return secret;
22680
22937
  }
22681
22938
  function readPeriscopeSecret(stateDir, ticketRecordId) {
22682
22939
  try {
22683
- const v = JSON.parse(readFileSync40(secretFile(stateDir, ticketRecordId), "utf8"));
22940
+ const v = JSON.parse(readFileSync41(secretFile(stateDir, ticketRecordId), "utf8"));
22684
22941
  return typeof v.secret === "string" && v.secret ? v.secret : void 0;
22685
22942
  } catch {
22686
22943
  return void 0;
@@ -22691,14 +22948,14 @@ function removePeriscopeSecret(stateDir, ticketRecordId) {
22691
22948
  }
22692
22949
  function readPeriscopeEndpoint(stateDir, ticketRecordId) {
22693
22950
  try {
22694
- return JSON.parse(readFileSync40(endpointFile(stateDir, ticketRecordId), "utf8"));
22951
+ return JSON.parse(readFileSync41(endpointFile(stateDir, ticketRecordId), "utf8"));
22695
22952
  } catch {
22696
22953
  return void 0;
22697
22954
  }
22698
22955
  }
22699
22956
  function writePeriscopeEndpoint(stateDir, rec) {
22700
- mkdirSync32(endpointDir(stateDir), { recursive: true });
22701
- writeFileSync33(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
22957
+ mkdirSync33(endpointDir(stateDir), { recursive: true });
22958
+ writeFileSync34(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
22702
22959
  `);
22703
22960
  }
22704
22961
  function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
@@ -22706,7 +22963,7 @@ function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
22706
22963
  }
22707
22964
  function listPeriscopeEndpoints(stateDir) {
22708
22965
  const dir = endpointDir(stateDir);
22709
- if (!existsSync45(dir)) return [];
22966
+ if (!existsSync46(dir)) return [];
22710
22967
  const out = [];
22711
22968
  for (const name of readdirSync19(dir)) {
22712
22969
  if (!name.endsWith(".json")) continue;
@@ -22794,10 +23051,10 @@ async function realAncestor(p) {
22794
23051
  }
22795
23052
  if (isLink) {
22796
23053
  if (++hops > 40) throw new PathRefusal(`too many symbolic links resolving ${p}`);
22797
- cur = resolve6(dirname28(cur), await readlink(cur));
23054
+ cur = resolve6(dirname29(cur), await readlink(cur));
22798
23055
  continue;
22799
23056
  }
22800
- const parent = dirname28(cur);
23057
+ const parent = dirname29(cur);
22801
23058
  if (parent === cur) throw new PathRefusal(`cannot resolve ${p}`);
22802
23059
  tail3.push(basename15(cur));
22803
23060
  cur = parent;
@@ -22809,13 +23066,13 @@ async function resolveInWorktree(root, requested) {
22809
23066
  let abs;
22810
23067
  if (isAbsolute4(requested)) {
22811
23068
  abs = resolve6(requested);
22812
- if (isInside(root, abs)) abs = join49(realRoot, relative2(root, abs));
23069
+ if (isInside(root, abs)) abs = join50(realRoot, relative2(root, abs));
22813
23070
  } else {
22814
23071
  abs = resolve6(realRoot, requested);
22815
23072
  }
22816
23073
  if (!isInside(realRoot, abs)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
22817
23074
  const { real, rest } = await realAncestor(abs);
22818
- const final = rest ? join49(real, rest) : real;
23075
+ const final = rest ? join50(real, rest) : real;
22819
23076
  if (!isInside(realRoot, final)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
22820
23077
  if (relative2(realRoot, final).split(sep).includes(".git")) throw new PathRefusal(".git is not reachable from a periscope");
22821
23078
  return final;
@@ -22883,7 +23140,7 @@ function createPeriscopeMcpServer(d, sc) {
22883
23140
  const entries = (await readdir(abs, { withFileTypes: true })).filter((e) => e.name !== ".git");
22884
23141
  const lines2 = await Promise.all(entries.map(async (e) => {
22885
23142
  if (e.isDirectory()) return `[DIR] ${e.name}`;
22886
- const size = (await lstat(join49(abs, e.name))).size;
23143
+ const size = (await lstat(join50(abs, e.name))).size;
22887
23144
  return `[FILE] ${e.name} (${size} bytes)`;
22888
23145
  }));
22889
23146
  return text(lines2.join("\n") || "(empty)");
@@ -22908,7 +23165,7 @@ function createPeriscopeMcpServer(d, sc) {
22908
23165
  if (Buffer.byteLength(content) > MAX_WRITE_BYTES) return text(`content exceeds ${MAX_WRITE_BYTES} bytes`, true);
22909
23166
  const abs = await inTree(path);
22910
23167
  if (abs === await realpath(root)) return text("path must name a file", true);
22911
- await mkdir(dirname28(abs), { recursive: true });
23168
+ await mkdir(dirname29(abs), { recursive: true });
22912
23169
  await inTree(path);
22913
23170
  await writeFile(abs, content);
22914
23171
  return text(`Wrote ${Buffer.byteLength(content)} bytes to ${await rel(abs)}`);
@@ -22948,8 +23205,8 @@ function createPeriscopeMcpServer(d, sc) {
22948
23205
  inputSchema: { path: z.string() },
22949
23206
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
22950
23207
  }, async ({ path }) => {
22951
- const parent = await inTree(dirname28(path) === "" ? "." : dirname28(path));
22952
- const abs = join49(parent, basename15(path));
23208
+ const parent = await inTree(dirname29(path) === "" ? "." : dirname29(path));
23209
+ const abs = join50(parent, basename15(path));
22953
23210
  if (basename15(path) === ".git" || abs === parent) return text("path must name a file", true);
22954
23211
  const st = await lstat(abs).catch(() => void 0);
22955
23212
  if (!st) return text(`${path} does not exist`, true);
@@ -23070,7 +23327,7 @@ function findPeriscope(sidecars, segment) {
23070
23327
  }
23071
23328
 
23072
23329
  // src/cli.ts
23073
- var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname29(process.execPath) : resolve7(dirname29(fileURLToPath(import.meta.url)), "..");
23330
+ var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname30(process.execPath) : resolve7(dirname30(fileURLToPath(import.meta.url)), "..");
23074
23331
  var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
23075
23332
  var CYCLE_SECONDS2 = 120;
23076
23333
  var STALE_CYCLES = 3;
@@ -23438,7 +23695,7 @@ if (command === "provision") {
23438
23695
  process.exit(outcome.code);
23439
23696
  }
23440
23697
  if (command === "pool-member") {
23441
- const pool = poolConfigFrom(argv, process.env, () => readFileSync41(0, "utf8"));
23698
+ const pool = poolConfigFrom(argv, process.env, () => readFileSync42(0, "utf8"));
23442
23699
  if (typeof pool === "string") {
23443
23700
  process.stderr.write(`crew pool-member: ${pool}
23444
23701
  `);
@@ -23666,7 +23923,7 @@ if (command === "passenger-sync-daemon") {
23666
23923
  } else {
23667
23924
  const toleratesNoRoutes = command === "credential" || command === "forge" || command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "deprovision" || command === "connect" && flag("sign-out");
23668
23925
  const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset") || command === "deprovision";
23669
- if (configless && !existsSync46(findConfigFile(CREW_HOME))) {
23926
+ if (configless && !existsSync47(findConfigFile(CREW_HOME))) {
23670
23927
  cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
23671
23928
  } else {
23672
23929
  cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
@@ -23681,7 +23938,7 @@ if (command === "passenger-sync-daemon") {
23681
23938
  throw e;
23682
23939
  }
23683
23940
  if (command === "credential") {
23684
- const r = runCredential(positional[1], readFileSync41(0, "utf8"), cfg.ship.forges, cfg.ship.stateDir);
23941
+ const r = runCredential(positional[1], readFileSync42(0, "utf8"), cfg.ship.forges, cfg.ship.stateDir);
23685
23942
  process.stdout.write(r.out);
23686
23943
  process.stderr.write(r.err);
23687
23944
  process.exit(r.code);
@@ -23693,9 +23950,9 @@ if (command === "passenger-sync-daemon") {
23693
23950
  forges: cfg.ship.forges,
23694
23951
  stateDir: cfg.ship.stateDir,
23695
23952
  configFile: cfg.configFile,
23696
- readFile: (p) => readFileSync41(p, "utf8"),
23697
- writeFile: (p, t) => writeFileSync34(p, t),
23698
- stdin: () => readFileSync41(0, "utf8"),
23953
+ readFile: (p) => readFileSync42(p, "utf8"),
23954
+ writeFile: (p, t) => writeFileSync35(p, t),
23955
+ stdin: () => readFileSync42(0, "utf8"),
23699
23956
  env: process.env
23700
23957
  }
23701
23958
  );
@@ -23988,8 +24245,8 @@ if (command === "passenger-sync-daemon") {
23988
24245
  }
23989
24246
  if (host === "linux" && hasSystemd) {
23990
24247
  const label = labelFor(CREW_HOME, "run");
23991
- const staleTimer = join50(homedir11(), ".config", "systemd", "user", `${label}.timer`);
23992
- if (existsSync46(staleTimer)) {
24248
+ const staleTimer = join51(homedir11(), ".config", "systemd", "user", `${label}.timer`);
24249
+ if (existsSync47(staleTimer)) {
23993
24250
  process.stdout.write(
23994
24251
  `removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
23995
24252
  `
@@ -24016,7 +24273,7 @@ if (command === "passenger-sync-daemon") {
24016
24273
  const tracker = new Tracker(c, cfg.ship);
24017
24274
  problems.push(...validateContract(tracker.contract).map((p) => `contract: ${p}`));
24018
24275
  for (const r of await resolvedRepos(c)) {
24019
- if (!existsSync46(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
24276
+ if (!existsSync47(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
24020
24277
  if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
24021
24278
  readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
24022
24279
  }
@@ -24043,7 +24300,7 @@ if (command === "passenger-sync-daemon") {
24043
24300
  }
24044
24301
  async function doctorFix(o) {
24045
24302
  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))));
24046
- const unitExists = (p) => existsSync46(p);
24303
+ const unitExists = (p) => existsSync47(p);
24047
24304
  const crontab = o.host === "linux" ? spawnSync10("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
24048
24305
  const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run", installContext()), unitExists, crontab) : true;
24049
24306
  const plan = planFix({ routes, schedulerAbsent, host: o.host });
@@ -24060,7 +24317,7 @@ if (command === "passenger-sync-daemon") {
24060
24317
  for (const a of plan.actions) {
24061
24318
  if (a.kind === "enable") {
24062
24319
  if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
24063
- writeFileSync34(cfg.configFile, planEnable(readFileSync41(cfg.configFile, "utf8"), a.route));
24320
+ writeFileSync35(cfg.configFile, planEnable(readFileSync42(cfg.configFile, "utf8"), a.route));
24064
24321
  enabledSound.add(a.route);
24065
24322
  changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
24066
24323
  } else {
@@ -24137,7 +24394,7 @@ ${changed2.map((c) => ` - ${c}
24137
24394
  return renderEnvironment({
24138
24395
  route: c,
24139
24396
  userAgent: cfg.ship.userAgent,
24140
- repos: await resolvedRepos(c),
24397
+ repos: await withRepoMaps(await resolvedRepos(c), cfg.ship.mapTokenBudget, cfg.ship.shell),
24141
24398
  contract,
24142
24399
  sourceTicket: ticket,
24143
24400
  authorship: await tracker.authorshipProbe().catch(() => null),
@@ -25199,7 +25456,7 @@ ${cu.tail.trimEnd()}
25199
25456
  process.exit(1);
25200
25457
  }
25201
25458
  requireArmed("run the daemon loop");
25202
- const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join50(CREW_HOME, "src"), join50(CREW_HOME, "dist"), join50(CREW_HOME, "bin")];
25459
+ const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join51(CREW_HOME, "src"), join51(CREW_HOME, "dist"), join51(CREW_HOME, "bin")];
25203
25460
  const configPaths = daemonConfigPaths(cfg);
25204
25461
  watchPaths.push(...configPaths);
25205
25462
  noteDaemonStarted(cfg.ship.stateDir);
@@ -25504,10 +25761,10 @@ ${cu.tail.trimEnd()}
25504
25761
  `);
25505
25762
  if (!dryRun) {
25506
25763
  const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
25507
- const raw = existsSync46(resolvedPath) ? JSON.parse(readFileSync41(resolvedPath, "utf8")) : {};
25764
+ const raw = existsSync47(resolvedPath) ? JSON.parse(readFileSync42(resolvedPath, "utf8")) : {};
25508
25765
  raw.agentSkills = result.agentSkills;
25509
- mkdirSync33(dirOf2(resolvedPath), { recursive: true });
25510
- writeFileSync34(resolvedPath, `${JSON.stringify(raw, null, 2)}
25766
+ mkdirSync34(dirOf2(resolvedPath), { recursive: true });
25767
+ writeFileSync35(resolvedPath, `${JSON.stringify(raw, null, 2)}
25511
25768
  `);
25512
25769
  }
25513
25770
  if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
@@ -25530,7 +25787,7 @@ ${cu.tail.trimEnd()}
25530
25787
  throw e;
25531
25788
  }
25532
25789
  const oldName = cfg.ship.name;
25533
- writeFileSync34(cfg.configFile, setShipNameInYaml(readFileSync41(cfg.configFile, "utf8"), newName));
25790
+ writeFileSync35(cfg.configFile, setShipNameInYaml(readFileSync42(cfg.configFile, "utf8"), newName));
25534
25791
  cfg.ship.name = newName;
25535
25792
  let renameMachineId;
25536
25793
  try {
@@ -25577,14 +25834,14 @@ ${cu.tail.trimEnd()}
25577
25834
  process.exit(2);
25578
25835
  };
25579
25836
  if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks" && sub !== "check") usage2();
25580
- const configText = readFileSync41(cfg.configFile, "utf8");
25837
+ const configText = readFileSync42(cfg.configFile, "utf8");
25581
25838
  if (sub === "check") {
25582
25839
  try {
25583
25840
  const routeArg = value("route") ?? positional[2];
25584
25841
  const found = cfg.routes.find((r) => r.route === routeArg);
25585
25842
  if (!found) throw new ReposError(`no route "${routeArg}" in the config (have: ${cfg.routes.map((r) => r.route).join(", ") || "none"})`);
25586
25843
  const named2 = value("route") ? void 0 : positional[3];
25587
- const dir = named2 ? repoDirOf(configText, dirname29(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
25844
+ const dir = named2 ? repoDirOf(configText, dirname30(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
25588
25845
  const target = reposOf(found).find((t) => resolvePath(t.dir) === dir || t.name === named2);
25589
25846
  const o = target ? found.repoOverrides[target.name] : void 0;
25590
25847
  runReposCheck(dir, {
@@ -25607,9 +25864,9 @@ ${cu.tail.trimEnd()}
25607
25864
  if (sub === "hooks") {
25608
25865
  try {
25609
25866
  const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
25610
- const dir = repoDirOf(configText, dirname29(cfg.configFile), a.route, a.name);
25611
- if (!existsSync46(dir)) throw new ReposError(`${dir} does not exist on disk`);
25612
- const files = { exists: (r) => existsSync46(resolvePath(dir, r)), list: (r) => {
25867
+ const dir = repoDirOf(configText, dirname30(cfg.configFile), a.route, a.name);
25868
+ if (!existsSync47(dir)) throw new ReposError(`${dir} does not exist on disk`);
25869
+ const files = { exists: (r) => existsSync47(resolvePath(dir, r)), list: (r) => {
25613
25870
  try {
25614
25871
  return readdirSync20(resolvePath(dir, r));
25615
25872
  } catch {
@@ -25617,7 +25874,7 @@ ${cu.tail.trimEnd()}
25617
25874
  }
25618
25875
  }, read: (r) => {
25619
25876
  try {
25620
- return readFileSync41(resolvePath(dir, r), "utf8");
25877
+ return readFileSync42(resolvePath(dir, r), "utf8");
25621
25878
  } catch {
25622
25879
  return void 0;
25623
25880
  }
@@ -25659,19 +25916,19 @@ ${cu.tail.trimEnd()}
25659
25916
  let cleared;
25660
25917
  if (clear2) {
25661
25918
  const plan = clearRouteHooks(configText, a.route, a.name);
25662
- if (plan.removed.length > 0) writeFileSync34(cfg.configFile, plan.text);
25919
+ if (plan.removed.length > 0) writeFileSync35(cfg.configFile, plan.text);
25663
25920
  cleared = { file: cfg.configFile, removed: plan.removed };
25664
25921
  }
25665
25922
  if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
25666
25923
  if (write === "route") {
25667
25924
  const plan = planRouteHooks(configText, a.route, a.name, picked.hooks, { replace: flag("replace") });
25668
- writeFileSync34(cfg.configFile, plan.text);
25925
+ writeFileSync35(cfg.configFile, plan.text);
25669
25926
  written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept, replaced: plan.replaced };
25670
25927
  } else if (write === "repo") {
25671
25928
  const have = existingRepoConfig(files);
25672
25929
  if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
25673
25930
  const file = resolvePath(dir, ".crew.yaml");
25674
- writeFileSync34(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
25931
+ writeFileSync35(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
25675
25932
  written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [], replaced: [] };
25676
25933
  }
25677
25934
  if (flag("json")) {
@@ -25727,7 +25984,7 @@ ${cu.tail.trimEnd()}
25727
25984
  if (sub === "list" && !routeName) usage2();
25728
25985
  if (sub === "add") {
25729
25986
  try {
25730
- const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync46(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
25987
+ const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync47(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
25731
25988
  routeName = a.route;
25732
25989
  addPath = a.path;
25733
25990
  addName = a.name;
@@ -25757,7 +26014,7 @@ ${cu.tail.trimEnd()}
25757
26014
  const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
25758
26015
  if (sub === "list" || sub === "add") {
25759
26016
  const liveRoute = cfg.routes.find((r) => r.route === routeName);
25760
- if (liveRoute?.resolved?.reposModelId && existsSync46(resolvedFile) && !dryRun) {
26017
+ if (liveRoute?.resolved?.reposModelId && existsSync47(resolvedFile) && !dryRun) {
25761
26018
  try {
25762
26019
  await hydrateApiKeys([liveRoute]);
25763
26020
  persistRepoRows(cfg.ship.stateDir, liveRoute.route, await new Tracker(liveRoute, cfg.ship).repoRowsForMap());
@@ -25766,16 +26023,16 @@ ${cu.tail.trimEnd()}
25766
26023
  }
25767
26024
  }
25768
26025
  let resolvedRemotes;
25769
- if (existsSync46(resolvedFile)) {
26026
+ if (existsSync47(resolvedFile)) {
25770
26027
  try {
25771
- resolvedRemotes = JSON.parse(readFileSync41(resolvedFile, "utf8"));
26028
+ resolvedRemotes = JSON.parse(readFileSync42(resolvedFile, "utf8"));
25772
26029
  } catch {
25773
26030
  }
25774
26031
  }
25775
26032
  const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
25776
26033
  const text2 = configText;
25777
- const base = dirname29(cfg.configFile);
25778
- const isCheckout = (d) => existsSync46(`${d}/.git`);
26034
+ const base = dirname30(cfg.configFile);
26035
+ const isCheckout = (d) => existsSync47(`${d}/.git`);
25779
26036
  try {
25780
26037
  if (sub === "list") {
25781
26038
  if (flag("json")) {
@@ -25785,11 +26042,11 @@ ${cu.tail.trimEnd()}
25785
26042
  base,
25786
26043
  resolved: resolvedRemotes,
25787
26044
  originOf,
25788
- exists: (d) => existsSync46(d),
26045
+ exists: (d) => existsSync47(d),
25789
26046
  isCheckout,
25790
26047
  repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
25791
26048
  try {
25792
- return Object.keys(parseRepoConfig(readFileSync41(resolvePath(d, f), "utf8"), f).hooks).length > 0;
26049
+ return Object.keys(parseRepoConfig(readFileSync42(resolvePath(d, f), "utf8"), f).hooks).length > 0;
25793
26050
  } catch {
25794
26051
  return false;
25795
26052
  }
@@ -25806,7 +26063,7 @@ ${cu.tail.trimEnd()}
25806
26063
  for (const r of repos) {
25807
26064
  const origin2 = originOf(r.dir);
25808
26065
  const row = matchRepoName(origin2, resolvedRemotes);
25809
- const note = !existsSync46(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
26066
+ const note = !existsSync47(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
25810
26067
  process.stdout.write(`${r.name} ${r.dir} ${note}
25811
26068
  `);
25812
26069
  }
@@ -25818,9 +26075,9 @@ ${cu.tail.trimEnd()}
25818
26075
  ` : `${human}
25819
26076
  `);
25820
26077
  const plan2 = planRepoClone({ text: text2, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
25821
- if (existsSync46(plan2.target) && (existsSync46(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
26078
+ if (existsSync47(plan2.target) && (existsSync47(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
25822
26079
  throw new ReposError(
25823
- existsSync46(`${plan2.target}/.git`) ? `${plan2.target} is already a checkout \u2014 attach it with \`crew repos add ${routeName} ${plan2.name} ${plan2.target}\`` : `${plan2.target} exists and is not empty \u2014 pass --dir to clone somewhere else`
26080
+ existsSync47(`${plan2.target}/.git`) ? `${plan2.target} is already a checkout \u2014 attach it with \`crew repos add ${routeName} ${plan2.name} ${plan2.target}\`` : `${plan2.target} exists and is not empty \u2014 pass --dir to clone somewhere else`
25824
26081
  );
25825
26082
  }
25826
26083
  if (dryRun) {
@@ -25838,14 +26095,14 @@ ${cu.tail.trimEnd()}
25838
26095
  throw new ReposError(`${e.message}${authHint}
25839
26096
  crew.yaml was not changed`);
25840
26097
  }
25841
- writeFileSync34(cfg.configFile, plan2.text);
26098
+ writeFileSync35(cfg.configFile, plan2.text);
25842
26099
  if (plan2.droppedDir) process.stderr.write(`crew repos: dropped dir: ${plan2.droppedDir} from route ${routeName} \u2014 it is not a checkout (the clone base); checkouts now live under repos:
25843
26100
  `);
25844
26101
  say("attached", { route: routeName, name: plan2.name, dir: plan2.target, mode: plan2.mode }, `cloned and attached ${plan2.target} to ${routeName} as ${plan2.mode === "dir" ? "dir" : `repos.${plan2.name}`} in ${cfg.configFile}`);
25845
26102
  break;
25846
26103
  }
25847
26104
  const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir11()));
25848
- const top = existsSync46(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
26105
+ const top = existsSync47(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
25849
26106
  if (!top) throw new ReposError(`${target} is not a git checkout`);
25850
26107
  const origin = originOf(top);
25851
26108
  const plan = planRepoAdd({
@@ -25897,15 +26154,15 @@ ${cu.tail.trimEnd()}
25897
26154
  const created = await rowTracker.createRepoRow(rowPayload);
25898
26155
  process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
25899
26156
  `);
25900
- if (existsSync46(resolvedFile)) {
25901
- const raw = JSON.parse(readFileSync41(resolvedFile, "utf8"));
26157
+ if (existsSync47(resolvedFile)) {
26158
+ const raw = JSON.parse(readFileSync42(resolvedFile, "utf8"));
25902
26159
  raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
25903
26160
  raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
25904
- writeFileSync34(resolvedFile, `${JSON.stringify(raw, null, 2)}
26161
+ writeFileSync35(resolvedFile, `${JSON.stringify(raw, null, 2)}
25905
26162
  `);
25906
26163
  }
25907
26164
  }
25908
- writeFileSync34(cfg.configFile, plan.text);
26165
+ writeFileSync35(cfg.configFile, plan.text);
25909
26166
  process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
25910
26167
  `);
25911
26168
  } catch (e) {
@@ -26274,7 +26531,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
26274
26531
  process.exit(2);
26275
26532
  }
26276
26533
  if (found.shipNameAdopted && !dryRun) {
26277
- writeFileSync34(cfg.configFile, setShipNameInYaml(readFileSync41(cfg.configFile, "utf8"), found.shipNameAdopted));
26534
+ writeFileSync35(cfg.configFile, setShipNameInYaml(readFileSync42(cfg.configFile, "utf8"), found.shipNameAdopted));
26278
26535
  cfg.ship.name = found.shipNameAdopted;
26279
26536
  }
26280
26537
  if (found.shipRow) {
@@ -26317,10 +26574,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
26317
26574
  }
26318
26575
  }
26319
26576
  let previous;
26320
- const hadPreviousFile = existsSync46(resolvedPath);
26577
+ const hadPreviousFile = existsSync47(resolvedPath);
26321
26578
  if (hadPreviousFile) {
26322
26579
  try {
26323
- previous = JSON.parse(readFileSync41(resolvedPath, "utf8"));
26580
+ previous = JSON.parse(readFileSync42(resolvedPath, "utf8"));
26324
26581
  } catch {
26325
26582
  }
26326
26583
  }
@@ -26450,10 +26707,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
26450
26707
  process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
26451
26708
  `);
26452
26709
  } else {
26453
- mkdirSync33(dirname29(resolvedPath), { recursive: true });
26710
+ mkdirSync34(dirname30(resolvedPath), { recursive: true });
26454
26711
  if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
26455
26712
  const tmpPath = `${resolvedPath}.tmp${process.pid}`;
26456
- writeFileSync34(tmpPath, `${JSON.stringify(resolved, null, 2)}
26713
+ writeFileSync35(tmpPath, `${JSON.stringify(resolved, null, 2)}
26457
26714
  `);
26458
26715
  renameSync6(tmpPath, resolvedPath);
26459
26716
  process.stderr.write(`Resolved ids written to ${resolvedPath}
@@ -26499,8 +26756,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
26499
26756
  process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
26500
26757
  `);
26501
26758
  } else {
26502
- mkdirSync33(dirname29(apiKeyFile), { recursive: true });
26503
- writeFileSync34(apiKeyFile, `${apiKeyVar}=${apiKey}
26759
+ mkdirSync34(dirname30(apiKeyFile), { recursive: true });
26760
+ writeFileSync35(apiKeyFile, `${apiKeyVar}=${apiKey}
26504
26761
  `, { mode: 384 });
26505
26762
  process.stderr.write(`API key written to ${apiKeyFile}
26506
26763
  `);
@@ -27375,7 +27632,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
27375
27632
  const shellR = hit && resolveShell(hit.repo.config.shell ?? cfg.ship.shell);
27376
27633
  const resumed = await runPeriscopeResume({
27377
27634
  find: async () => hit && { row: hit.row, tag: hit.tag },
27378
- worktreeExists: (p) => existsSync46(p),
27635
+ worktreeExists: (p) => existsSync47(p),
27379
27636
  prepareDropFile,
27380
27637
  caps: periscopeCaps(cfg.ship),
27381
27638
  runHook: async (name, cwd, env) => {
@@ -27470,10 +27727,10 @@ ${where.join("\n")}`);
27470
27727
  },
27471
27728
  prepare: async (path) => {
27472
27729
  for (const f of repoP.config.worktrees.copy) {
27473
- const src = join50(repoP.dir, f);
27474
- if (!existsSync46(src)) continue;
27475
- mkdirSync33(dirOf2(join50(path, f)), { recursive: true });
27476
- copyFileSync2(src, join50(path, f));
27730
+ const src = join51(repoP.dir, f);
27731
+ if (!existsSync47(src)) continue;
27732
+ mkdirSync34(dirOf2(join51(path, f)), { recursive: true });
27733
+ copyFileSync2(src, join51(path, f));
27477
27734
  }
27478
27735
  const setup = repoP.config.hooks.setup;
27479
27736
  return setup ? runScript(setup, { cwd: path, shell: shellP, onLine: (l) => {
@@ -27482,10 +27739,11 @@ ${where.join("\n")}`);
27482
27739
  },
27483
27740
  runHook: async (name, cwd, env) => {
27484
27741
  const script = repoP.config.hooks[name];
27485
- return script ? runScript(script, { cwd, env, shell: shellP, timeoutMs: name === "preview" ? 18e4 : void 0 }) : null;
27742
+ return script ? runScript(script, { cwd, env, shell: shellP, timeoutMs: name === "preview" ? 18e4 : name === "intel" ? 6e4 : void 0 }) : null;
27486
27743
  },
27487
27744
  createRow: (fields) => trackerP.createPeriscope(fields),
27488
27745
  prepareDropFile,
27746
+ registerCode: registerCodeServer,
27489
27747
  recordLocal: (sc) => {
27490
27748
  writePeriscopeSidecar(cfg.ship.stateDir, { ...sc, route: routeP.route });
27491
27749
  if (flag("serve-mcp")) mintPeriscopeSecret(cfg.ship.stateDir, sc.ticketRecordId);
@@ -27545,6 +27803,7 @@ ${where.join("\n")}`);
27545
27803
  },
27546
27804
  now: () => /* @__PURE__ */ new Date(),
27547
27805
  host: a.host ?? "localhost",
27806
+ intelStartupSeconds: cfg.ship.intelStartupSeconds,
27548
27807
  say
27549
27808
  };
27550
27809
  if (dryRun) {
@@ -27628,7 +27887,7 @@ ${where.join("\n")}`);
27628
27887
  }
27629
27888
  case "sync": {
27630
27889
  emit.enter("worktree");
27631
- let tracked = 0;
27890
+ let tracked2 = 0;
27632
27891
  let acted = 0;
27633
27892
  let baseChecked = 0;
27634
27893
  let baseActed = 0;
@@ -27666,7 +27925,7 @@ ${where.join("\n")}`);
27666
27925
  }
27667
27926
  if (!s2.upstream) continue;
27668
27927
  if (w.branch !== r.config.branch.base && s2.upstream === `${r.config.branch.remote}/${r.config.branch.base}`) continue;
27669
- tracked++;
27928
+ tracked2++;
27670
27929
  if (s2.canFastForward) {
27671
27930
  acted++;
27672
27931
  if (dryRun) emit.emit(`would fast-forward ${w.branch}: ${s2.detail}`);
@@ -27679,8 +27938,8 @@ ${where.join("\n")}`);
27679
27938
  }
27680
27939
  if (baseUnsafe > 0) process.exitCode = 1;
27681
27940
  if (baseChecked > 0 && baseActed === 0) emit.emit(`${baseChecked} base branch(es) checked, all level with the remote`);
27682
- if (tracked === 0) emit.emit("no worktree tracks a remote branch \u2014 nothing to sync");
27683
- else if (acted === 0) emit.emit(`${tracked} worktree(s) tracking a remote, all level with it`);
27941
+ if (tracked2 === 0) emit.emit("no worktree tracks a remote branch \u2014 nothing to sync");
27942
+ else if (acted === 0) emit.emit(`${tracked2} worktree(s) tracking a remote, all level with it`);
27684
27943
  break;
27685
27944
  }
27686
27945
  case "pause":
@@ -27769,7 +28028,7 @@ ${where.join("\n")}`);
27769
28028
  };
27770
28029
  const droppedRoutes = [];
27771
28030
  try {
27772
- const rawCfg = parseYaml2(readFileSync41(cfg.configFile, "utf8"));
28031
+ const rawCfg = parseYaml2(readFileSync42(cfg.configFile, "utf8"));
27773
28032
  for (const r of rawCfg?.routes ?? []) {
27774
28033
  if (typeof r?.route !== "string" || cfg.routes.some((x) => x.route === r.route)) continue;
27775
28034
  try {
@@ -27822,8 +28081,8 @@ ${where.join("\n")}`);
27822
28081
  process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
27823
28082
  break;
27824
28083
  }
27825
- mkdirSync33(cfg.ship.stateDir, { recursive: true });
27826
- writeFileSync34(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
28084
+ mkdirSync34(cfg.ship.stateDir, { recursive: true });
28085
+ writeFileSync35(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
27827
28086
  `);
27828
28087
  process.stdout.write(
27829
28088
  "crew reload: requested \u2014 the daemon relaunches with the new config once its running agents end (it starts no new work meanwhile); if it is busy, `crew reload --now` forces the relaunch and interrupts them\n"
@@ -27888,7 +28147,7 @@ ${where.join("\n")}`);
27888
28147
  }
27889
28148
  case "log": {
27890
28149
  try {
27891
- const text2 = readFileSync41(cfg.ship.logFile, "utf8").split("\n");
28150
+ const text2 = readFileSync42(cfg.ship.logFile, "utf8").split("\n");
27892
28151
  process.stdout.write(`${text2.slice(-(Number(value("tail")) || 40)).join("\n")}
27893
28152
  `);
27894
28153
  } catch {
@@ -27911,8 +28170,8 @@ ${where.join("\n")}`);
27911
28170
  };
27912
28171
  if (!route2.resolved) fail(`route ${route2.route} is not connected \u2014 run \`crew connect\` first`);
27913
28172
  const tracker = new Tracker(route2, cfg.ship);
27914
- const configText = readFileSync41(cfg.configFile, "utf8");
27915
- const cfgBase = dirname29(cfg.configFile);
28173
+ const configText = readFileSync42(cfg.configFile, "utf8");
28174
+ const cfgBase = dirname30(cfg.configFile);
27916
28175
  const configured2 = configuredRepos(configText, route2.route, cfgBase);
27917
28176
  const dir = configured2.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured2.map((r) => r.dir), route2.reposBasePath, route2.route.split("/")[0]);
27918
28177
  const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route2.route);
@@ -27940,7 +28199,7 @@ ${where.join("\n")}`);
27940
28199
  }
27941
28200
  if (configured2.some((r) => r.dir === dir)) {
27942
28201
  try {
27943
- writeFileSync34(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
28202
+ writeFileSync35(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
27944
28203
  } catch (e) {
27945
28204
  if (e instanceof ReposError) fail(e.message);
27946
28205
  throw e;
@@ -27948,29 +28207,29 @@ ${where.join("\n")}`);
27948
28207
  }
27949
28208
  if (row) {
27950
28209
  await tracker.deleteRepoRow(row.id);
27951
- if (existsSync46(resolvedFile)) {
27952
- const raw = JSON.parse(readFileSync41(resolvedFile, "utf8"));
28210
+ if (existsSync47(resolvedFile)) {
28211
+ const raw = JSON.parse(readFileSync42(resolvedFile, "utf8"));
27953
28212
  if (raw.repoNames) delete raw.repoNames[row.id];
27954
28213
  if (raw.repoRemotes) delete raw.repoRemotes[row.id];
27955
- writeFileSync34(resolvedFile, `${JSON.stringify(raw, null, 2)}
28214
+ writeFileSync35(resolvedFile, `${JSON.stringify(raw, null, 2)}
27956
28215
  `);
27957
28216
  }
27958
28217
  }
27959
- if (existsSync46(join50(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync23(dir, { recursive: true, force: true });
28218
+ if (existsSync47(join51(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync23(dir, { recursive: true, force: true });
27960
28219
  say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
27961
28220
  break;
27962
28221
  }
27963
28222
  const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
27964
- const fresh = !existsSync46(join50(dir, ".git"));
27965
- if (existsSync46(dir) && fresh && readdirSync20(dir).length > 0) fail(`${dir} exists and is not a git checkout \u2014 move it aside`);
28223
+ const fresh = !existsSync47(join51(dir, ".git"));
28224
+ if (existsSync47(dir) && fresh && readdirSync20(dir).length > 0) fail(`${dir} exists and is not a git checkout \u2014 move it aside`);
27966
28225
  if (dryRun) {
27967
28226
  say("dry-run", { dir, newCheckout: fresh, repoRow: row?.id ?? null }, `would ${fresh ? "create" : "reuse"} ${dir}${row ? "" : ", create the Repos row"}, and file one ${tracker.contract.statuses.approved} ticket`);
27968
28227
  break;
27969
28228
  }
27970
28229
  if (fresh) {
27971
- mkdirSync33(dir, { recursive: true });
27972
- writeFileSync34(join50(dir, "README.md"), SAMPLE_README);
27973
- writeFileSync34(join50(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
28230
+ mkdirSync34(dir, { recursive: true });
28231
+ writeFileSync35(join51(dir, "README.md"), SAMPLE_README);
28232
+ writeFileSync35(join51(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
27974
28233
  const g = (...a) => {
27975
28234
  const r = spawnSync10("git", a, { cwd: dir, encoding: "utf8" });
27976
28235
  if (r.status !== 0) fail(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
@@ -27988,17 +28247,17 @@ ${where.join("\n")}`);
27988
28247
  spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
27989
28248
  });
27990
28249
  repoId = (await tracker.createRepoRow(payload)).id;
27991
- if (existsSync46(resolvedFile)) {
27992
- const raw = JSON.parse(readFileSync41(resolvedFile, "utf8"));
28250
+ if (existsSync47(resolvedFile)) {
28251
+ const raw = JSON.parse(readFileSync42(resolvedFile, "utf8"));
27993
28252
  raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
27994
28253
  raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
27995
- writeFileSync34(resolvedFile, `${JSON.stringify(raw, null, 2)}
28254
+ writeFileSync35(resolvedFile, `${JSON.stringify(raw, null, 2)}
27996
28255
  `);
27997
28256
  }
27998
28257
  }
27999
28258
  if (!configured2.some((r) => r.dir === dir)) {
28000
28259
  try {
28001
- writeFileSync34(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
28260
+ writeFileSync35(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
28002
28261
  } catch (e) {
28003
28262
  if (e instanceof ReposError) fail(e.message);
28004
28263
  throw e;
@@ -28344,7 +28603,7 @@ watermark: ${state.watermark()}
28344
28603
  }
28345
28604
  for (const r of repos) {
28346
28605
  const remote = r.config.branch.remote;
28347
- if (existsSync46(r.dir)) {
28606
+ if (existsSync47(r.dir)) {
28348
28607
  pushAuth.push({
28349
28608
  route: c.route,
28350
28609
  repo: r.name,
@@ -28382,7 +28641,8 @@ watermark: ${state.watermark()}
28382
28641
  notes: validateEffective(r.config),
28383
28642
  base: doctorBaseReport(r, r.config.branch.base, baseHealth),
28384
28643
  hooks: r.config.hooks,
28385
- onPath: (cmd) => !!findOnPath(cmd, doctorSchedulerPath)
28644
+ onPath: (cmd) => !!findOnPath(cmd, doctorSchedulerPath),
28645
+ intelSyntax: r.config.hooks.intel ? checkHookSyntax(r.config.hooks.intel, r.config.shell) : void 0
28386
28646
  });
28387
28647
  problems.push(...f.problems);
28388
28648
  passed += f.passed;
@@ -28433,7 +28693,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
28433
28693
  }
28434
28694
  try {
28435
28695
  const evFile = eventFileFor(cfg.ship.stateDir);
28436
- const evSize = existsSync46(evFile) ? statSync16(evFile).size : 0;
28696
+ const evSize = existsSync47(evFile) ? statSync16(evFile).size : 0;
28437
28697
  process.stdout.write(
28438
28698
  evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
28439
28699
  ` : "events log: ok\n"
@@ -28444,7 +28704,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
28444
28704
  const unitPlan = planInstall(cfg.ship, CREW_HOME, host, detectSystemd(), "run", installContext());
28445
28705
  const abandons = unitAbandonsAgents(unitPlan, (p) => {
28446
28706
  try {
28447
- return readFileSync41(p, "utf8");
28707
+ return readFileSync42(p, "utf8");
28448
28708
  } catch {
28449
28709
  return null;
28450
28710
  }