@tablation/crew 0.121.0 → 0.123.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 +788 -495
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -262,6 +262,9 @@ function intelFlavor(script) {
262
262
  const w = script?.trim();
263
263
  return w && Object.prototype.hasOwnProperty.call(INTEL_FLAVORS, w) ? w : void 0;
264
264
  }
265
+ function wantsCodeBridge(script) {
266
+ return !!script?.trim() && intelFlavor(script) !== "typescript-lsp";
267
+ }
265
268
  var INTEL_MINIMUM_TOOLS = [
266
269
  "find symbols by name",
267
270
  "references to a symbol",
@@ -1309,13 +1312,17 @@ var claudeAdapter = {
1309
1312
  // so the resolved key is never written to an argv string or a config
1310
1313
  // file on disk. Skipped when there's no key to send: a route `crew
1311
1314
  // connect` hasn't resolved yet has nothing to authenticate with.
1312
- ...o.mcp ? ["--mcp-config", JSON.stringify({
1315
+ ...o.mcp || o.code ? ["--mcp-config", JSON.stringify({
1313
1316
  mcpServers: {
1314
- tablation: {
1315
- type: "http",
1316
- url: o.mcp.url,
1317
- headers: { Authorization: `Bearer \${${o.mcp.keyVar}}` }
1318
- }
1317
+ ...o.mcp ? {
1318
+ tablation: {
1319
+ type: "http",
1320
+ url: o.mcp.url,
1321
+ headers: { Authorization: `Bearer \${${o.mcp.keyVar}}` }
1322
+ }
1323
+ } : {},
1324
+ // CREW-2912: the bridge to the worktree's symbol service, attached once `crew worktree` has started it.
1325
+ ...o.code ? { [INTEL_SERVER_NAME]: { type: "stdio", command: o.code.command, args: o.code.args } } : {}
1319
1326
  }
1320
1327
  })] : [],
1321
1328
  // A crew-generated language-server plugin (the `typescript-lsp` intel flavor). Not under oMLX,
@@ -1664,6 +1671,14 @@ var codexAdapter = {
1664
1671
  ...cfg(`mcp_servers.${SERVER}.bearer_token_env_var`, o.mcp.keyVar),
1665
1672
  ...cfg(`mcp_servers.${SERVER}.default_tools_approval_mode`, "approve")
1666
1673
  ] : [],
1674
+ // CREW-2912: the bridge to the worktree's symbol service, scoped to this run like the tracker entry above.
1675
+ ...o.code ? [
1676
+ "-c",
1677
+ `mcp_servers.${INTEL_SERVER_NAME}.command=${tomlString(o.code.command)}`,
1678
+ "-c",
1679
+ `mcp_servers.${INTEL_SERVER_NAME}.args=[${o.code.args.map(tomlString).join(", ")}]`,
1680
+ ...cfg(`mcp_servers.${INTEL_SERVER_NAME}.default_tools_approval_mode`, "approve")
1681
+ ] : [],
1667
1682
  ...spec.args,
1668
1683
  // Prompt on stdin.
1669
1684
  "-"
@@ -2045,7 +2060,8 @@ var geminiAdapter = {
2045
2060
  ];
2046
2061
  return wrapInvocation(spec, { model: o.model, args });
2047
2062
  },
2048
- runEnv: (spec) => ({ ...spec.env }),
2063
+ // CREW-2912: Gemini has no per-run server list, so the bridge is declared in a per-run system settings file the planner wrote.
2064
+ runEnv: (spec, o) => ({ ...spec.env, ...o?.code?.settingsFile ? { GEMINI_CLI_SYSTEM_SETTINGS_PATH: o.code.settingsFile } : {} }),
2049
2065
  provider: () => "google",
2050
2066
  unsetEnv: BILLING_VARS_TO_UNSET2,
2051
2067
  mapStreamLine: mapGeminiLine,
@@ -5834,6 +5850,78 @@ function cloneFailureMarker(dir) {
5834
5850
  return `${dir}.clone-failed`;
5835
5851
  }
5836
5852
 
5853
+ // src/repo-map.ts
5854
+ import { readFileSync as readFileSync18 } from "fs";
5855
+ import { join as join20 } from "path";
5856
+ var CHARS_PER_TOKEN = 4;
5857
+ var MAP_TIMEOUT_MS = 6e4;
5858
+ function budgetMap(text2, tokenBudget) {
5859
+ const body = text2.replace(/\s+$/, "");
5860
+ const limit = tokenBudget * CHARS_PER_TOKEN;
5861
+ if (body.length <= limit) return body;
5862
+ const lines2 = body.split("\n");
5863
+ const noteFor = (kept2) => `\u2026 ${lines2.length - kept2} of ${lines2.length} lines cut to fit ship.mapTokenBudget (${tokenBudget} tokens)`;
5864
+ let kept = lines2.length;
5865
+ let size = body.length;
5866
+ while (kept > 0 && size + 1 + noteFor(kept).length > limit) {
5867
+ kept--;
5868
+ size -= lines2[kept].length + 1;
5869
+ }
5870
+ return [...lines2.slice(0, kept), noteFor(kept)].join("\n");
5871
+ }
5872
+ function mapFilePath(value2) {
5873
+ if (!value2) return null;
5874
+ const v = value2.trim();
5875
+ if (!/^[A-Za-z0-9_.+@-]+(?:\/[A-Za-z0-9_.+@-]+)*\.md$/i.test(v)) return null;
5876
+ if (v.split("/").some((seg) => seg === ".." || seg === ".")) return null;
5877
+ return v;
5878
+ }
5879
+ function inspectMapFile(dir, path) {
5880
+ try {
5881
+ const text2 = readFileSync18(join20(dir, path), "utf8").replace(/\s+$/, "");
5882
+ return { path, exists: true, lines: text2 ? text2.split("\n").length : 0, tokens: Math.ceil(text2.length / CHARS_PER_TOKEN) };
5883
+ } catch {
5884
+ return { path, exists: false, lines: 0, tokens: 0 };
5885
+ }
5886
+ }
5887
+ function loadRepoMap(repo, tokenBudget, shellFallback) {
5888
+ const script = repo.config.hooks.map;
5889
+ if (!script) return Promise.resolve({ skipped: "" });
5890
+ const file = mapFilePath(script);
5891
+ if (file) {
5892
+ const m = inspectMapFile(repo.dir, file);
5893
+ if (!m.exists) return Promise.resolve({ skipped: `the \`map\` file ${file} for ${repo.name} is missing or unreadable, so the brief has no repository map` });
5894
+ let text2 = "";
5895
+ try {
5896
+ text2 = readFileSync18(join20(repo.dir, file), "utf8");
5897
+ } catch {
5898
+ }
5899
+ if (!text2.trim()) return Promise.resolve({ skipped: `the \`map\` file ${file} for ${repo.name} is empty, so the brief has no repository map` });
5900
+ return Promise.resolve({ text: budgetMap(text2, tokenBudget) });
5901
+ }
5902
+ return runScript(script, {
5903
+ cwd: repo.dir,
5904
+ shell: resolveShell(repo.config.shell ?? shellFallback),
5905
+ env: { CREW_REPO: repo.name },
5906
+ timeoutMs: MAP_TIMEOUT_MS
5907
+ }).then(
5908
+ (r) => {
5909
+ if (r.code !== 0) return { skipped: `the \`map\` hook for ${repo.name} exited ${r.code}, so the brief has no repository map` };
5910
+ if (!r.stdout.trim()) return { skipped: `the \`map\` hook for ${repo.name} printed nothing, so the brief has no repository map` };
5911
+ return { text: budgetMap(r.stdout, tokenBudget) };
5912
+ },
5913
+ (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` })
5914
+ );
5915
+ }
5916
+ async function withRepoMaps(repos, tokenBudget, shellFallback) {
5917
+ return Promise.all(repos.map(async (r) => {
5918
+ if (!r.config.hooks.map) return r;
5919
+ const out = await loadRepoMap(r, tokenBudget, shellFallback);
5920
+ if ("skipped" in out) return out.skipped ? { ...r, mapSkipped: out.skipped } : r;
5921
+ return { ...r, map: out.text };
5922
+ }));
5923
+ }
5924
+
5837
5925
  // src/ship-attention.ts
5838
5926
  var RELEASE_STALE_AFTER_INTERVALS = 3;
5839
5927
  function upsert(items, item) {
@@ -6075,6 +6163,7 @@ function clearShipAttention(state, key) {
6075
6163
  if (next.length !== previous.length) store.persist(next);
6076
6164
  }
6077
6165
  function hookCommand(script) {
6166
+ if (mapFilePath(script)) return null;
6078
6167
  for (const raw of script.split("\n")) {
6079
6168
  const line = raw.trim();
6080
6169
  if (!line || line.startsWith("#")) continue;
@@ -6154,6 +6243,18 @@ function repoFindings(o) {
6154
6243
  remedy: "add the directory holding it to ship.extraPath in crew.yaml"
6155
6244
  });
6156
6245
  }
6246
+ if (o.mapFile) {
6247
+ if (!o.mapFile.exists) {
6248
+ problems.push({
6249
+ severity: "attention",
6250
+ kind: "map_file_missing",
6251
+ ...at,
6252
+ hook: "map",
6253
+ message: `${o.route}/${o.repo}: hooks.map names ${o.mapFile.path}, which is not in the checkout, so briefs carry no repository map`,
6254
+ remedy: `commit ${o.mapFile.path}, or correct hooks.map in the repository's .crew.yaml`
6255
+ });
6256
+ } else passed++;
6257
+ }
6157
6258
  if (o.intelSyntax) {
6158
6259
  if (o.intelSyntax.status === "error") {
6159
6260
  problems.push({
@@ -6189,8 +6290,8 @@ function attentionProblems(items, already) {
6189
6290
  import { createHash as createHash6 } from "crypto";
6190
6291
  import { spawn as spawn5 } from "child_process";
6191
6292
  import { execFileSync as execFileSync8 } from "child_process";
6192
- import { mkdirSync as mkdirSync18, readFileSync as readFileSync25, renameSync as renameSync3, writeFileSync as writeFileSync19, rmSync as rmSync14 } from "fs";
6193
- import { dirname as dirname13, join as join30 } from "path";
6293
+ import { mkdirSync as mkdirSync19, readFileSync as readFileSync27, renameSync as renameSync3, writeFileSync as writeFileSync20, rmSync as rmSync15 } from "fs";
6294
+ import { dirname as dirname14, join as join32 } from "path";
6194
6295
  import { tmpdir as tmpdir3 } from "os";
6195
6296
 
6196
6297
  // src/passenger-containers.ts
@@ -6203,23 +6304,23 @@ import { promisify as promisify2 } from "util";
6203
6304
 
6204
6305
  // src/deprovision-host.ts
6205
6306
  import { spawnSync as spawnSync7 } from "child_process";
6206
- import { existsSync as existsSync28, readdirSync as readdirSync11 } from "fs";
6307
+ import { existsSync as existsSync29, readdirSync as readdirSync11 } from "fs";
6207
6308
 
6208
6309
  // src/interrupted.ts
6209
- import { existsSync as existsSync26, readdirSync as readdirSync9, readFileSync as readFileSync24 } from "fs";
6210
- import { join as join28 } from "path";
6310
+ import { existsSync as existsSync27, readdirSync as readdirSync9, readFileSync as readFileSync26 } from "fs";
6311
+ import { join as join30 } from "path";
6211
6312
 
6212
6313
  // src/agent.ts
6213
6314
  import { spawnSync as spawnSync6 } from "child_process";
6214
- import { readFileSync as readFileSync23, existsSync as existsSync25, statSync as statSync8, mkdirSync as mkdirSync17, writeFileSync as writeFileSync18, createWriteStream, openSync as openSync4, closeSync as closeSync4, readSync } from "fs";
6315
+ import { readFileSync as readFileSync25, existsSync as existsSync26, statSync as statSync8, mkdirSync as mkdirSync18, writeFileSync as writeFileSync19, createWriteStream, openSync as openSync4, closeSync as closeSync4, readSync } from "fs";
6215
6316
  import { StringDecoder } from "string_decoder";
6216
6317
  import { finished } from "stream/promises";
6217
- import { join as join27, dirname as dirname12, basename as basename9 } from "path";
6318
+ import { join as join29, dirname as dirname13, basename as basename9 } from "path";
6218
6319
  import { createHash as createHash4 } from "crypto";
6219
6320
 
6220
6321
  // src/state.ts
6221
- import { readFileSync as readFileSync18, writeFileSync as writeFileSync14, existsSync as existsSync19, mkdirSync as mkdirSync13, rmSync as rmSync10, statSync as statSync7, readdirSync as readdirSync5 } from "fs";
6222
- import { join as join20 } from "path";
6322
+ import { readFileSync as readFileSync19, writeFileSync as writeFileSync14, existsSync as existsSync19, mkdirSync as mkdirSync13, rmSync as rmSync10, statSync as statSync7, readdirSync as readdirSync5 } from "fs";
6323
+ import { join as join21 } from "path";
6223
6324
  var EPOCH = "1970-01-01T00:00:00.000Z";
6224
6325
  var State = class _State {
6225
6326
  dir;
@@ -6228,7 +6329,7 @@ var State = class _State {
6228
6329
  mkdirSync13(dir, { recursive: true });
6229
6330
  }
6230
6331
  path(...parts) {
6231
- return join20(this.dir, ...parts);
6332
+ return join21(this.dir, ...parts);
6232
6333
  }
6233
6334
  /**
6234
6335
  * The timestamp every "new since last poll" check is measured against.
@@ -6239,7 +6340,7 @@ var State = class _State {
6239
6340
  */
6240
6341
  watermark() {
6241
6342
  try {
6242
- const v = readFileSync18(this.path(".poll-watermark"), "utf8").trim();
6343
+ const v = readFileSync19(this.path(".poll-watermark"), "utf8").trim();
6243
6344
  return v || EPOCH;
6244
6345
  } catch {
6245
6346
  return EPOCH;
@@ -6325,7 +6426,7 @@ var State = class _State {
6325
6426
  const name = _State.safe(rawName);
6326
6427
  const file = this.path(`.${name}.lock`);
6327
6428
  try {
6328
- const existing = Number.parseInt(readFileSync18(file, "utf8").trim(), 10);
6429
+ const existing = Number.parseInt(readFileSync19(file, "utf8").trim(), 10);
6329
6430
  if (Number.isFinite(existing) && existing > 0) {
6330
6431
  try {
6331
6432
  process.kill(existing, 0);
@@ -6363,7 +6464,7 @@ var State = class _State {
6363
6464
  if (!/^\.release-.+\.lock$/.test(n)) continue;
6364
6465
  let pid = 0;
6365
6466
  try {
6366
- pid = Number.parseInt(readFileSync18(this.path(n), "utf8").trim(), 10);
6467
+ pid = Number.parseInt(readFileSync19(this.path(n), "utf8").trim(), 10);
6367
6468
  } catch {
6368
6469
  continue;
6369
6470
  }
@@ -6449,7 +6550,7 @@ var State = class _State {
6449
6550
  ticketInFlight(recordId) {
6450
6551
  const file = this.path(`.${_State.safe(`crew-ticket-${recordId}`)}.lock`);
6451
6552
  try {
6452
- const pid = Number.parseInt(readFileSync18(file, "utf8").trim(), 10);
6553
+ const pid = Number.parseInt(readFileSync19(file, "utf8").trim(), 10);
6453
6554
  if (!Number.isFinite(pid) || pid <= 0) return false;
6454
6555
  process.kill(pid, 0);
6455
6556
  return true;
@@ -6483,7 +6584,7 @@ var State = class _State {
6483
6584
  const heldReported = `.release-held-${suffix}`;
6484
6585
  const read = (f) => {
6485
6586
  try {
6486
- return readFileSync18(this.path(f), "utf8").trim() || null;
6587
+ return readFileSync19(this.path(f), "utf8").trim() || null;
6487
6588
  } catch {
6488
6589
  return null;
6489
6590
  }
@@ -6533,7 +6634,7 @@ var State = class _State {
6533
6634
  /** Whether the board was already told about this head's red gate. */
6534
6635
  heldReported: () => {
6535
6636
  try {
6536
- return readFileSync18(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
6637
+ return readFileSync19(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
6537
6638
  } catch {
6538
6639
  return null;
6539
6640
  }
@@ -6570,7 +6671,7 @@ var State = class _State {
6570
6671
  const ticketStreakFile = `.fairness-ticket-streak-${suffix}`;
6571
6672
  const read = (f) => {
6572
6673
  try {
6573
- return readFileSync18(this.path(f), "utf8").trim() || null;
6674
+ return readFileSync19(this.path(f), "utf8").trim() || null;
6574
6675
  } catch {
6575
6676
  return null;
6576
6677
  }
@@ -6635,7 +6736,7 @@ var State = class _State {
6635
6736
  return {
6636
6737
  previous: () => {
6637
6738
  try {
6638
- const parsed = JSON.parse(readFileSync18(this.path(file), "utf8"));
6739
+ const parsed = JSON.parse(readFileSync19(this.path(file), "utf8"));
6639
6740
  return parsed && typeof parsed === "object" ? parsed : {};
6640
6741
  } catch {
6641
6742
  return {};
@@ -6658,7 +6759,7 @@ var State = class _State {
6658
6759
  return {
6659
6760
  previous: () => {
6660
6761
  try {
6661
- const parsed = JSON.parse(readFileSync18(this.path(file), "utf8"));
6762
+ const parsed = JSON.parse(readFileSync19(this.path(file), "utf8"));
6662
6763
  return Array.isArray(parsed) ? parsed.filter((x) => typeof x === "string") : [];
6663
6764
  } catch {
6664
6765
  return [];
@@ -6679,7 +6780,7 @@ var State = class _State {
6679
6780
  return {
6680
6781
  previous: () => {
6681
6782
  try {
6682
- const parsed = JSON.parse(readFileSync18(this.path(file), "utf8"));
6783
+ const parsed = JSON.parse(readFileSync19(this.path(file), "utf8"));
6683
6784
  return Array.isArray(parsed) ? parsed : [];
6684
6785
  } catch {
6685
6786
  return [];
@@ -6712,7 +6813,7 @@ var State = class _State {
6712
6813
  }
6713
6814
  readInt(file) {
6714
6815
  try {
6715
- return Number.parseInt(readFileSync18(this.path(file), "utf8").trim(), 10) || 0;
6816
+ return Number.parseInt(readFileSync19(this.path(file), "utf8").trim(), 10) || 0;
6716
6817
  } catch {
6717
6818
  return 0;
6718
6819
  }
@@ -7100,6 +7201,143 @@ function renderEnvironment(i) {
7100
7201
  return lines2.join("\n");
7101
7202
  }
7102
7203
 
7204
+ // src/intel-bridge.ts
7205
+ import { existsSync as existsSync20, mkdirSync as mkdirSync14, readFileSync as readFileSync20, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "fs";
7206
+ import { dirname as dirname10, join as join22 } from "path";
7207
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
7208
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
7209
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
7210
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7211
+ import {
7212
+ CallToolRequestSchema,
7213
+ ListToolsRequestSchema
7214
+ } from "@modelcontextprotocol/sdk/types.js";
7215
+ var STATUS_TOOL = "code_status";
7216
+ var STATUS_WAITING = "The symbol service is not available yet: the worktree has not been cut. Run `crew worktree` for the ticket; the symbol tools appear in this server afterwards.";
7217
+ var STATUS_LOST = "The symbol service stopped answering, so its tools are unavailable. They return if the service comes back; until then use text search.";
7218
+ function intelDir(stateDir, route2, ticket) {
7219
+ const safe = (s) => s.replace(/[^A-Za-z0-9._-]+/g, "_");
7220
+ return join22(stateDir, "intel", safe(route2), safe(ticket));
7221
+ }
7222
+ var attachFilePath = (stateDir, route2, ticket) => join22(intelDir(stateDir, route2, ticket), "url");
7223
+ function writeAttachFile(file, url) {
7224
+ mkdirSync14(dirname10(file), { recursive: true });
7225
+ writeFileSync15(file, `${url}
7226
+ `);
7227
+ }
7228
+ function readAttachFile(file) {
7229
+ try {
7230
+ const url = readFileSync20(file, "utf8").trim();
7231
+ return /^https?:\/\/\S+$/i.test(url) ? url : void 0;
7232
+ } catch {
7233
+ return void 0;
7234
+ }
7235
+ }
7236
+ var statusTool = {
7237
+ name: STATUS_TOOL,
7238
+ description: "Reports whether this repository's symbol service (find symbols, references, definitions, file outlines) is attached. Before `crew worktree` has been run for the ticket it is not; the symbol tools replace this one once it is.",
7239
+ inputSchema: { type: "object", properties: {} }
7240
+ };
7241
+ var defaultConnect = async (url) => {
7242
+ const client2 = new Client({ name: "crew-intel-bridge", version: "0" }, { capabilities: {} });
7243
+ await client2.connect(new StreamableHTTPClientTransport(new URL(url)));
7244
+ return client2;
7245
+ };
7246
+ function createBridge(o) {
7247
+ const connect2 = o.connectUpstream ?? defaultConnect;
7248
+ const server = new Server(
7249
+ { name: INTEL_SERVER_NAME, version: o.version ?? "0" },
7250
+ { capabilities: { tools: { listChanged: true } } }
7251
+ );
7252
+ let upstream;
7253
+ let lost = false;
7254
+ let connecting = false;
7255
+ let timer;
7256
+ const notify2 = () => {
7257
+ void server.sendToolListChanged().catch(() => {
7258
+ });
7259
+ };
7260
+ const drop = () => {
7261
+ const gone = upstream;
7262
+ upstream = void 0;
7263
+ lost = true;
7264
+ if (gone) void gone.close().catch(() => {
7265
+ });
7266
+ notify2();
7267
+ };
7268
+ const status2 = () => ({ content: [{ type: "text", text: lost ? STATUS_LOST : STATUS_WAITING }] });
7269
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
7270
+ if (!upstream) return { tools: [statusTool] };
7271
+ try {
7272
+ return await upstream.listTools();
7273
+ } catch {
7274
+ drop();
7275
+ return { tools: [statusTool] };
7276
+ }
7277
+ });
7278
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
7279
+ if (!upstream) return status2();
7280
+ try {
7281
+ return await upstream.callTool({ name: req.params.name, arguments: req.params.arguments });
7282
+ } catch {
7283
+ drop();
7284
+ return status2();
7285
+ }
7286
+ });
7287
+ const tryAttach = async () => {
7288
+ if (upstream || connecting) return false;
7289
+ const url = readAttachFile(o.attachFile);
7290
+ if (!url) return false;
7291
+ connecting = true;
7292
+ try {
7293
+ const client2 = await connect2(url);
7294
+ client2.onclose = () => {
7295
+ if (upstream === client2) drop();
7296
+ };
7297
+ upstream = client2;
7298
+ lost = false;
7299
+ notify2();
7300
+ return true;
7301
+ } catch {
7302
+ return false;
7303
+ } finally {
7304
+ connecting = false;
7305
+ }
7306
+ };
7307
+ return {
7308
+ server,
7309
+ attached: () => upstream !== void 0,
7310
+ tryAttach,
7311
+ async start(transport) {
7312
+ await server.connect(transport);
7313
+ if (existsSync20(o.attachFile)) await tryAttach();
7314
+ timer = setInterval(() => {
7315
+ void tryAttach();
7316
+ }, o.pollMs ?? 1e3);
7317
+ timer.unref?.();
7318
+ },
7319
+ async stop() {
7320
+ if (timer) clearInterval(timer);
7321
+ const gone = upstream;
7322
+ upstream = void 0;
7323
+ await gone?.close().catch(() => {
7324
+ });
7325
+ await server.close().catch(() => {
7326
+ });
7327
+ }
7328
+ };
7329
+ }
7330
+ async function runBridge(stateDir, route2, ticket) {
7331
+ const bridge = createBridge({ attachFile: attachFilePath(stateDir, route2, ticket) });
7332
+ const closed = new Promise((resolve8) => {
7333
+ bridge.server.onclose = resolve8;
7334
+ process.stdin.on("end", resolve8);
7335
+ });
7336
+ await bridge.start(new StdioServerTransport());
7337
+ await closed;
7338
+ await bridge.stop();
7339
+ }
7340
+
7103
7341
  // src/contract.ts
7104
7342
  var DEFAULT_CONTRACT = {
7105
7343
  columns: {
@@ -7329,8 +7567,8 @@ var BeforeFirstEditTracker = class {
7329
7567
  };
7330
7568
 
7331
7569
  // src/agent-log.ts
7332
- import { existsSync as existsSync20, mkdirSync as mkdirSync14, readdirSync as readdirSync6, readFileSync as readFileSync19, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "fs";
7333
- import { join as join21 } from "path";
7570
+ import { existsSync as existsSync21, mkdirSync as mkdirSync15, readdirSync as readdirSync6, readFileSync as readFileSync21, rmSync as rmSync12, writeFileSync as writeFileSync16 } from "fs";
7571
+ import { join as join23 } from "path";
7334
7572
 
7335
7573
  // src/pending-backoff.ts
7336
7574
  var sleepBackoff = (attempts) => Math.min(6e4 * 2 ** Math.max(0, attempts - 1), 30 * 6e4);
@@ -7533,20 +7771,20 @@ async function addRunToTicket(target, t, recordId, run3) {
7533
7771
  };
7534
7772
  }
7535
7773
  }
7536
- var queueDir = (stateDir) => join21(stateDir, "pending-agent-logs");
7774
+ var queueDir = (stateDir) => join23(stateDir, "pending-agent-logs");
7537
7775
  function queueAgentRun(stateDir, name, q2) {
7538
- mkdirSync14(queueDir(stateDir), { recursive: true });
7539
- writeFileSync15(join21(queueDir(stateDir), `${name}.json`), JSON.stringify(q2));
7776
+ mkdirSync15(queueDir(stateDir), { recursive: true });
7777
+ writeFileSync16(join23(queueDir(stateDir), `${name}.json`), JSON.stringify(q2));
7540
7778
  }
7541
7779
  async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
7542
7780
  const dir = queueDir(stateDir);
7543
- if (!existsSync20(dir)) return 0;
7781
+ if (!existsSync21(dir)) return 0;
7544
7782
  let sent = 0;
7545
7783
  for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
7546
- const path = join21(dir, f);
7784
+ const path = join23(dir, f);
7547
7785
  let q2;
7548
7786
  try {
7549
- q2 = JSON.parse(readFileSync19(path, "utf8"));
7787
+ q2 = JSON.parse(readFileSync21(path, "utf8"));
7550
7788
  } catch {
7551
7789
  continue;
7552
7790
  }
@@ -7565,17 +7803,17 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
7565
7803
  attempts: 0,
7566
7804
  nextAttemptAt: (/* @__PURE__ */ new Date()).toISOString()
7567
7805
  });
7568
- rmSync11(path, { force: true });
7806
+ rmSync12(path, { force: true });
7569
7807
  sent += 1;
7570
7808
  log.emit(`sent a queued Agent Log report (${f})`);
7571
7809
  } catch (e) {
7572
7810
  if (!(e instanceof AgentLogUnreachableError)) {
7573
- rmSync11(path, { force: true });
7811
+ rmSync12(path, { force: true });
7574
7812
  log.warn(`dropped a queued Agent Log report (${f}): ${e.message}`);
7575
7813
  continue;
7576
7814
  }
7577
7815
  const attempts = q2.attempts + 1;
7578
- writeFileSync15(path, JSON.stringify({
7816
+ writeFileSync16(path, JSON.stringify({
7579
7817
  ...q2,
7580
7818
  attempts,
7581
7819
  warned: true,
@@ -7586,20 +7824,20 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
7586
7824
  }
7587
7825
  return sent;
7588
7826
  }
7589
- var countersDir = (stateDir) => join21(stateDir, "pending-ticket-counters");
7827
+ var countersDir = (stateDir) => join23(stateDir, "pending-ticket-counters");
7590
7828
  function queueTicketCounters(stateDir, name, q2) {
7591
- mkdirSync14(countersDir(stateDir), { recursive: true });
7592
- writeFileSync15(join21(countersDir(stateDir), `${name}.json`), JSON.stringify(q2));
7829
+ mkdirSync15(countersDir(stateDir), { recursive: true });
7830
+ writeFileSync16(join23(countersDir(stateDir), `${name}.json`), JSON.stringify(q2));
7593
7831
  }
7594
7832
  async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
7595
7833
  const dir = countersDir(stateDir);
7596
- if (!existsSync20(dir)) return 0;
7834
+ if (!existsSync21(dir)) return 0;
7597
7835
  let sent = 0;
7598
7836
  for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
7599
- const path = join21(dir, f);
7837
+ const path = join23(dir, f);
7600
7838
  let q2;
7601
7839
  try {
7602
- q2 = JSON.parse(readFileSync19(path, "utf8"));
7840
+ q2 = JSON.parse(readFileSync21(path, "utf8"));
7603
7841
  } catch {
7604
7842
  continue;
7605
7843
  }
@@ -7608,17 +7846,17 @@ async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /
7608
7846
  const apiKey = await keyFor(q2.role);
7609
7847
  if (!apiKey) continue;
7610
7848
  await writeCounters({ ...q2.target, apiKey }, q2.write);
7611
- rmSync11(path, { force: true });
7849
+ rmSync12(path, { force: true });
7612
7850
  sent += 1;
7613
7851
  log.emit(`added a queued run's spend to ${q2.write.ticketReference ?? q2.write.recordId} (${f})`);
7614
7852
  } catch (e) {
7615
7853
  if (!isRetryable(e)) {
7616
- rmSync11(path, { force: true });
7854
+ rmSync12(path, { force: true });
7617
7855
  log.warn(`dropped a queued ticket counter update (${f}): ${e.message}`);
7618
7856
  continue;
7619
7857
  }
7620
7858
  const attempts = q2.attempts + 1;
7621
- writeFileSync15(path, JSON.stringify({
7859
+ writeFileSync16(path, JSON.stringify({
7622
7860
  ...q2,
7623
7861
  attempts,
7624
7862
  warned: true,
@@ -7821,8 +8059,8 @@ var HandoffTranscriptTracker = class {
7821
8059
  };
7822
8060
 
7823
8061
  // src/pending-handoff.ts
7824
- import { existsSync as existsSync22, mkdirSync as mkdirSync15, readdirSync as readdirSync7, readFileSync as readFileSync20, rmSync as rmSync12, writeFileSync as writeFileSync16 } from "fs";
7825
- import { join as join23 } from "path";
8062
+ import { existsSync as existsSync23, mkdirSync as mkdirSync16, readdirSync as readdirSync7, readFileSync as readFileSync22, rmSync as rmSync13, writeFileSync as writeFileSync17 } from "fs";
8063
+ import { join as join25 } from "path";
7826
8064
 
7827
8065
  // src/ticket-branch.ts
7828
8066
  import { basename as basename7 } from "path";
@@ -8988,8 +9226,8 @@ function holdWarnings(tickets, ships, contract, humanHolds, now = Date.now()) {
8988
9226
  }
8989
9227
 
8990
9228
  // src/run-worktree.ts
8991
- import { existsSync as existsSync21 } from "fs";
8992
- import { dirname as dirname10, join as join22 } from "path";
9229
+ import { existsSync as existsSync22 } from "fs";
9230
+ import { dirname as dirname11, join as join24 } from "path";
8993
9231
  var describeRunWorktreePlan = (p) => {
8994
9232
  switch (p.action) {
8995
9233
  case "cut-remote":
@@ -9030,7 +9268,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
9030
9268
  const existing = worktreeForNumber(dir, num);
9031
9269
  const located = locateBranchForTicket(dir, cfg, t, role);
9032
9270
  const baseRef = hasRemote ? `${remote}/${base}` : base;
9033
- if (existing && !existing.prunable && existsSync21(existing.path)) {
9271
+ if (existing && !existing.prunable && existsSync22(existing.path)) {
9034
9272
  const branch = existing.branch;
9035
9273
  if (status(existing.path).length > 0) {
9036
9274
  return { action: opts.resumeInterrupted ? "resume-dirty" : "leave-dirty", path: existing.path, branch };
@@ -9046,7 +9284,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
9046
9284
  if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
9047
9285
  return { action: "recut", path: existing.path, branch, base: baseRef };
9048
9286
  }
9049
- const path = join22(dirname10(dir), worktreeForTicket(dir, cfg, t, role));
9287
+ const path = join24(dirname11(dir), worktreeForTicket(dir, cfg, t, role));
9050
9288
  if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
9051
9289
  return { action: "cut-remote", path, branch: located.branch };
9052
9290
  }
@@ -9100,7 +9338,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
9100
9338
  if (remoteConfigured(dir, remote) && remoteBranchExists(dir, remote, branch)) return false;
9101
9339
  if (countCommits(dir, `${baseRef}..${branch}`) !== 0) return false;
9102
9340
  const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
9103
- if (wt && existsSync21(wt.path)) {
9341
+ if (wt && existsSync22(wt.path)) {
9104
9342
  if (status(wt.path).length > 0) return false;
9105
9343
  if (gitOk(dir, ["worktree", "remove", wt.path]) === null) return false;
9106
9344
  }
@@ -9108,7 +9346,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
9108
9346
  }
9109
9347
  async function finishRunWorktree(dir, cfg, branch) {
9110
9348
  const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
9111
- if (!wt || !existsSync21(wt.path)) return { kind: "none" };
9349
+ if (!wt || !existsSync22(wt.path)) return { kind: "none" };
9112
9350
  if (cfg.hooks.handoff) return { kind: "kept-handoff", path: wt.path };
9113
9351
  if (status(wt.path).length > 0) return { kind: "kept-dirty", path: wt.path };
9114
9352
  if (remoteConfigured(dir, cfg.branch.remote) && worktreeHoldsUnpushed(wt.path, cfg.branch.remote)) {
@@ -9120,7 +9358,7 @@ async function finishRunWorktree(dir, cfg, branch) {
9120
9358
  if (pids.length) await killGently(pids);
9121
9359
  }
9122
9360
  gitOk(dir, ["worktree", "remove", wt.path]);
9123
- return existsSync21(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
9361
+ return existsSync22(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
9124
9362
  }
9125
9363
 
9126
9364
  // src/handoff-guard.ts
@@ -9503,20 +9741,20 @@ async function guardRunStrays(route2, ship, tracker, role, workingId, since2, lo
9503
9741
 
9504
9742
  // src/pending-handoff.ts
9505
9743
  var PENDING_MAX_AGE_MS = 48 * 36e5;
9506
- var pendingDirFor = (stateDir) => join23(stateDir, "pending-handoffs");
9507
- var stateDirOfStream = (streamPath) => join23(streamPath, "..", "..");
9744
+ var pendingDirFor = (stateDir) => join25(stateDir, "pending-handoffs");
9745
+ var stateDirOfStream = (streamPath) => join25(streamPath, "..", "..");
9508
9746
  function writePendingHandoff(stateDir, rec) {
9509
9747
  const dir = pendingDirFor(stateDir);
9510
- mkdirSync15(dir, { recursive: true });
9511
- writeFileSync16(join23(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
9748
+ mkdirSync16(dir, { recursive: true });
9749
+ writeFileSync17(join25(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
9512
9750
  }
9513
9751
  function listPendingHandoffs(stateDir, route2) {
9514
9752
  const dir = pendingDirFor(stateDir);
9515
- if (!existsSync22(dir)) return [];
9753
+ if (!existsSync23(dir)) return [];
9516
9754
  const out = [];
9517
9755
  for (const f of readdirSync7(dir).filter((n) => n.endsWith(".json")).sort()) {
9518
9756
  try {
9519
- const rec = JSON.parse(readFileSync20(join23(dir, f), "utf8"));
9757
+ const rec = JSON.parse(readFileSync22(join25(dir, f), "utf8"));
9520
9758
  if (rec.version === 1 && rec.ticketRecordId && (!route2 || rec.route === route2)) out.push(rec);
9521
9759
  } catch {
9522
9760
  }
@@ -9524,7 +9762,7 @@ function listPendingHandoffs(stateDir, route2) {
9524
9762
  return out;
9525
9763
  }
9526
9764
  function removePendingHandoff(stateDir, ticketRecordId) {
9527
- rmSync12(join23(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
9765
+ rmSync13(join25(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
9528
9766
  }
9529
9767
  function pendingHandoffFor(stateDir, ticketRecordId) {
9530
9768
  return listPendingHandoffs(stateDir).find((r) => r.ticketRecordId === ticketRecordId);
@@ -9634,8 +9872,8 @@ function expandSkillMarkers(text2, lookup2, opts = {}) {
9634
9872
  }
9635
9873
 
9636
9874
  // src/skills.ts
9637
- import { readFileSync as readFileSync22, readdirSync as readdirSync8, existsSync as existsSync24 } from "fs";
9638
- import { join as join25 } from "path";
9875
+ import { readFileSync as readFileSync24, readdirSync as readdirSync8, existsSync as existsSync25 } from "fs";
9876
+ import { join as join27 } from "path";
9639
9877
  import { parse as parseYaml } from "yaml";
9640
9878
  import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError3, TablationApiError as TablationApiError2 } from "@tablation/client";
9641
9879
 
@@ -9651,8 +9889,8 @@ ${additions}`;
9651
9889
  }
9652
9890
 
9653
9891
  // src/agents.ts
9654
- import { readFileSync as readFileSync21, existsSync as existsSync23 } from "fs";
9655
- import { join as join24 } from "path";
9892
+ import { readFileSync as readFileSync23, existsSync as existsSync24 } from "fs";
9893
+ import { join as join26 } from "path";
9656
9894
  import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError2, TablationApiError } from "@tablation/client";
9657
9895
  var PERSONA_NAME = {
9658
9896
  dev: "Developer",
@@ -9666,12 +9904,12 @@ var CREW_AGENT_COLUMN = "agent_id";
9666
9904
  var AgentsSyncError = class extends Error {
9667
9905
  };
9668
9906
  function personaDefaultPrompt(promptsDir, role) {
9669
- const briefPath = join24(promptsDir, "personas", `lane-${role}.md`);
9670
- if (!existsSync23(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
9671
- if (SELF_CONTAINED_ROLES.has(role)) return readFileSync21(briefPath, "utf8");
9672
- const commonPath = join24(promptsDir, "personas", "common.md");
9673
- if (!existsSync23(commonPath)) return readFileSync21(briefPath, "utf8");
9674
- return `${readFileSync21(commonPath, "utf8")}${readFileSync21(briefPath, "utf8")}`;
9907
+ const briefPath = join26(promptsDir, "personas", `lane-${role}.md`);
9908
+ if (!existsSync24(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
9909
+ if (SELF_CONTAINED_ROLES.has(role)) return readFileSync23(briefPath, "utf8");
9910
+ const commonPath = join26(promptsDir, "personas", "common.md");
9911
+ if (!existsSync24(commonPath)) return readFileSync23(briefPath, "utf8");
9912
+ return `${readFileSync23(commonPath, "utf8")}${readFileSync23(briefPath, "utf8")}`;
9675
9913
  }
9676
9914
  async function resolveAgentId(route2, role, opts) {
9677
9915
  const cached = route2.resolved?.agentPersonas?.[role]?.agentId;
@@ -10118,7 +10356,7 @@ function describeCrewLink(o) {
10118
10356
  var SkillSyncError = class extends Error {
10119
10357
  };
10120
10358
  function parseSkillFile(path) {
10121
- const text2 = readFileSync22(path, "utf8");
10359
+ const text2 = readFileSync24(path, "utf8");
10122
10360
  const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text2);
10123
10361
  if (!match) throw new SkillSyncError(`${path}: missing YAML frontmatter (expected a leading --- block)`);
10124
10362
  const frontmatter = match[1];
@@ -10131,9 +10369,9 @@ function parseSkillFile(path) {
10131
10369
  return { name: meta.name, description: meta.description, prompt: body, file: path };
10132
10370
  }
10133
10371
  function listLocalSkills(promptsDir) {
10134
- const dir = join25(promptsDir, "skills");
10135
- if (!existsSync24(dir)) return [];
10136
- return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join25(dir, f)));
10372
+ const dir = join27(promptsDir, "skills");
10373
+ if (!existsSync25(dir)) return [];
10374
+ return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join27(dir, f)));
10137
10375
  }
10138
10376
  async function syncSkills(route2, opts) {
10139
10377
  if (!route2.resolved) {
@@ -10318,9 +10556,9 @@ function skillMarkerWarnings(promptsDir, personaPrompts) {
10318
10556
 
10319
10557
  // src/runner.ts
10320
10558
  import { spawn as spawn3, execFile, execFileSync as execFileSync6 } from "child_process";
10321
- import { mkdtempSync, mkdirSync as mkdirSync16, writeFileSync as writeFileSync17, rmSync as rmSync13 } from "fs";
10559
+ import { mkdtempSync, mkdirSync as mkdirSync17, writeFileSync as writeFileSync18, rmSync as rmSync14 } from "fs";
10322
10560
  import { tmpdir as tmpdir2 } from "os";
10323
- import { basename as basename8, dirname as dirname11, join as join26 } from "path";
10561
+ import { basename as basename8, dirname as dirname12, join as join28 } from "path";
10324
10562
  import { promisify } from "util";
10325
10563
 
10326
10564
  // src/run-limit.ts
@@ -10449,8 +10687,8 @@ var SandboxRunner = class {
10449
10687
  throw new Error(`cannot generate the sandbox policy: ${e.message}`);
10450
10688
  }
10451
10689
  policyFile = policyPathFor(plan.streamPath);
10452
- mkdirSync16(dirname11(policyFile), { recursive: true });
10453
- writeFileSync17(policyFile, generated.yaml, { mode: 384 });
10690
+ mkdirSync17(dirname12(policyFile), { recursive: true });
10691
+ writeFileSync18(policyFile, generated.yaml, { mode: 384 });
10454
10692
  }
10455
10693
  const create2 = ["sandbox", "create", "--name", name, "--detach"];
10456
10694
  if (policyFile) create2.push("--policy", policyFile);
@@ -10493,13 +10731,13 @@ var SandboxRunner = class {
10493
10731
  const harness = cfg.agentBin ?? basename8(plan.bin);
10494
10732
  const viaUpload = Buffer.byteLength(plan.prompt) > SANDBOX_STDIN_LIMIT;
10495
10733
  if (viaUpload) {
10496
- const tmp = mkdtempSync(join26(tmpdir2(), "crew-prompt-"));
10734
+ const tmp = mkdtempSync(join28(tmpdir2(), "crew-prompt-"));
10497
10735
  try {
10498
- const file = join26(tmp, "prompt.txt");
10499
- writeFileSync17(file, plan.prompt, { mode: 384 });
10736
+ const file = join28(tmp, "prompt.txt");
10737
+ writeFileSync18(file, plan.prompt, { mode: 384 });
10500
10738
  await this.os(["sandbox", "upload", name, file, SANDBOX_PROMPT_PATH]);
10501
10739
  } finally {
10502
- rmSync13(tmp, { recursive: true, force: true });
10740
+ rmSync14(tmp, { recursive: true, force: true });
10503
10741
  }
10504
10742
  }
10505
10743
  const withStubs = `PATH=${shQuote2(SANDBOX_STUB_DIR)}:"$PATH"; export PATH; `;
@@ -10649,7 +10887,7 @@ var ALLOWANCE_REFUSAL_MARKER = "AI_ALLOWANCE_EXCEEDED";
10649
10887
  function streamShowsAllowanceRefusal(streamPath) {
10650
10888
  let raw;
10651
10889
  try {
10652
- raw = readFileSync23(streamPath, "utf8");
10890
+ raw = readFileSync25(streamPath, "utf8");
10653
10891
  } catch {
10654
10892
  return false;
10655
10893
  }
@@ -10672,26 +10910,49 @@ function streamShowsAllowanceRefusal(streamPath) {
10672
10910
  }
10673
10911
  return false;
10674
10912
  }
10675
- var digestPathFor = (stateDir, routeLabel, role, runId) => join27(stateDir, `digest-${routeLabel}-${role}${runId ? `-${runId}` : ""}.md`);
10913
+ var digestPathFor = (stateDir, routeLabel, role, runId) => join29(stateDir, `digest-${routeLabel}-${role}${runId ? `-${runId}` : ""}.md`);
10914
+ function codeBridgeFor(o, harness) {
10915
+ if (o.ship.runner === "sandbox" || !o.ticket) return void 0;
10916
+ const number = /(\d+)$/.exec(o.ticket)?.[1];
10917
+ if (!number) return void 0;
10918
+ let intel;
10919
+ try {
10920
+ const repos = reposOf(o.route);
10921
+ const target = repos.find((r) => r.name === o.repo) ?? (repos.length === 1 ? repos[0] : void 0);
10922
+ intel = target ? loadRepoConfig(target.dir)?.hooks?.intel : void 0;
10923
+ } catch {
10924
+ }
10925
+ if (!wantsCodeBridge(intel)) return void 0;
10926
+ const args = [...process.execArgv, process.argv[1] ?? "crew", "intel", "bridge", o.route.route, number, "--state-dir", o.stateDir];
10927
+ const bridge = { command: process.execPath, args };
10928
+ if (harness === "gemini") {
10929
+ const file = join29(intelDir(o.stateDir, o.route.route, number), "gemini-settings.json");
10930
+ mkdirSync18(dirname13(file), { recursive: true });
10931
+ writeFileSync19(file, `${JSON.stringify({ mcpServers: { [INTEL_SERVER_NAME]: { command: bridge.command, args } } }, null, 2)}
10932
+ `);
10933
+ bridge.settingsFile = file;
10934
+ }
10935
+ return bridge;
10936
+ }
10676
10937
  function planAgentRun(o) {
10677
- const briefPath = join27(o.route.promptsDir, "personas", `lane-${o.role}.md`);
10678
- if (!existsSync25(briefPath)) {
10938
+ const briefPath = join29(o.route.promptsDir, "personas", `lane-${o.role}.md`);
10939
+ if (!existsSync26(briefPath)) {
10679
10940
  throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
10680
10941
  }
10681
- const commonPath = join27(o.route.promptsDir, "personas", "common.md");
10682
- const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync25(commonPath);
10942
+ const commonPath = join29(o.route.promptsDir, "personas", "common.md");
10943
+ const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync26(commonPath);
10683
10944
  const now = o.now ?? (() => Date.now());
10684
10945
  const routeLabel = routeSlug(o.route.route);
10685
10946
  const ownDigest = o.runId ? digestPathFor(o.stateDir, routeLabel, o.role, o.runId) : void 0;
10686
- const digestPath2 = ownDigest && existsSync25(ownDigest) ? ownDigest : digestPathFor(o.stateDir, routeLabel, o.role);
10947
+ const digestPath2 = ownDigest && existsSync26(ownDigest) ? ownDigest : digestPathFor(o.stateDir, routeLabel, o.role);
10687
10948
  let digest;
10688
10949
  let ageSeconds;
10689
- if (existsSync25(digestPath2)) {
10950
+ if (existsSync26(digestPath2)) {
10690
10951
  ageSeconds = Math.floor((now() - statSync8(digestPath2).mtimeMs) / 1e3);
10691
- if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync23(digestPath2, "utf8");
10952
+ if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync25(digestPath2, "utf8");
10692
10953
  }
10693
- const common = usesCommon ? readFileSync23(commonPath, "utf8") : "";
10694
- const brief = readFileSync23(briefPath, "utf8");
10954
+ const common = usesCommon ? readFileSync25(commonPath, "utf8") : "";
10955
+ const brief = readFileSync25(briefPath, "utf8");
10695
10956
  const effectiveCommon = o.divergedPrompt ? "" : common;
10696
10957
  const effectiveBrief = appendAdditions(o.divergedPrompt ?? brief, o.personaAdditions);
10697
10958
  const localSkills = new Map(listLocalSkills(o.route.promptsDir).map((s) => [s.name, s.prompt]));
@@ -10714,7 +10975,7 @@ function planAgentRun(o) {
10714
10975
  digest
10715
10976
  });
10716
10977
  const promptSha = createHash4("sha256").update(expanded.text).digest("hex");
10717
- const streamsDir = join27(o.stateDir, "streams");
10978
+ const streamsDir = join29(o.stateDir, "streams");
10718
10979
  const base = `${routeLabel}-${o.role}-${o.cycle}${o.runId ? `-${o.runId}` : ""}`;
10719
10980
  const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
10720
10981
  const { model } = seat;
@@ -10729,7 +10990,9 @@ function planAgentRun(o) {
10729
10990
  toolchain = { ...toolchain, inference: { baseUrl: inferenceBaseUrl(o.route.baseUrl, o.route.resolved.workspaceId), token: o.apiKey } };
10730
10991
  }
10731
10992
  const adapter = adapterFor(toolchain.harness);
10993
+ const code = codeBridgeFor(o, adapter.harness);
10732
10994
  const invocation = adapter.invocation(toolchain, {
10995
+ code,
10733
10996
  role: o.role,
10734
10997
  model,
10735
10998
  pluginDirs: adapter.harness === "claude" ? lspPluginDirsFor(o.route, o.repo, o.stateDir) : void 0,
@@ -10788,7 +11051,7 @@ function planAgentRun(o) {
10788
11051
  unsetEnv: adapter.unsetEnv,
10789
11052
  setEnv: {
10790
11053
  ...o.apiKey ? { [API_KEY_VAR]: o.apiKey } : {},
10791
- ...adapter.runEnv(toolchain),
11054
+ ...adapter.runEnv(toolchain, { code }),
10792
11055
  // The operator's global SessionStart hook (pair-context-hook.sh) injects
10793
11056
  // the interactive Pair persona's prompt for any session it can't tell
10794
11057
  // apart from a person's own terminal. Every headless lane run this
@@ -10805,8 +11068,8 @@ function planAgentRun(o) {
10805
11068
  digestAttached: digest !== void 0,
10806
11069
  digestAgeSeconds: ageSeconds,
10807
11070
  model,
10808
- streamPath: join27(streamsDir, `${base}.jsonl`),
10809
- eventsPath: join27(streamsDir, `${base}.events.jsonl`),
11071
+ streamPath: join29(streamsDir, `${base}.jsonl`),
11072
+ eventsPath: join29(streamsDir, `${base}.events.jsonl`),
10810
11073
  ticket: o.ticket,
10811
11074
  ticketRecordId: o.ticketRecordId ?? null,
10812
11075
  repo: o.repo ?? null,
@@ -10837,7 +11100,7 @@ function describePlan(p) {
10837
11100
  var pidPathFor = (streamPath) => `${streamPath}.pid`;
10838
11101
  function readRunSidecar(streamPath) {
10839
11102
  try {
10840
- const sc = JSON.parse(readFileSync23(pidPathFor(streamPath), "utf8"));
11103
+ const sc = JSON.parse(readFileSync25(pidPathFor(streamPath), "utf8"));
10841
11104
  return typeof sc.pid === "number" && typeof sc.spawnedAt === "string" && typeof sc.role === "string" ? sc : null;
10842
11105
  } catch {
10843
11106
  return null;
@@ -10884,7 +11147,7 @@ async function killForDeadline(kill, pid, emit, role, ticket, spawnedAt, limitMi
10884
11147
  var withoutKey = ({ apiKey: _apiKey, ...rest }) => rest;
10885
11148
  function openSink(path) {
10886
11149
  try {
10887
- mkdirSync17(dirname12(path), { recursive: true });
11150
+ mkdirSync18(dirname13(path), { recursive: true });
10888
11151
  return createWriteStream(path, { flags: "a" });
10889
11152
  } catch {
10890
11153
  return void 0;
@@ -11070,7 +11333,7 @@ var RunObserver = class {
11070
11333
  };
11071
11334
  function writeRunMeta(id, startedAt, liveTicket, extra) {
11072
11335
  try {
11073
- writeFileSync18(`${id.streamPath}.meta.json`, JSON.stringify({
11336
+ writeFileSync19(`${id.streamPath}.meta.json`, JSON.stringify({
11074
11337
  route: id.route,
11075
11338
  role: id.role,
11076
11339
  hint: id.ticket,
@@ -11236,7 +11499,7 @@ function spawnAgent(plan, emit, hintMoved) {
11236
11499
  const id = identityOf(plan, adapter.harness);
11237
11500
  let streamFd;
11238
11501
  try {
11239
- mkdirSync17(dirname12(plan.streamPath), { recursive: true });
11502
+ mkdirSync18(dirname13(plan.streamPath), { recursive: true });
11240
11503
  streamFd = openSync4(plan.streamPath, "a");
11241
11504
  } catch (e) {
11242
11505
  void endSink(eventsSink);
@@ -11294,7 +11557,7 @@ function spawnAgent(plan, emit, hintMoved) {
11294
11557
  promptSha: plan.promptSha
11295
11558
  };
11296
11559
  try {
11297
- writeFileSync18(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
11560
+ writeFileSync19(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
11298
11561
  } catch {
11299
11562
  }
11300
11563
  }
@@ -11408,21 +11671,21 @@ var pidAlive = (pid) => {
11408
11671
  };
11409
11672
  function runFinished(streamPath) {
11410
11673
  const path = `${streamPath}.meta.json`;
11411
- if (!existsSync26(path)) return false;
11674
+ if (!existsSync27(path)) return false;
11412
11675
  try {
11413
- return JSON.parse(readFileSync24(path, "utf8")).running !== true;
11676
+ return JSON.parse(readFileSync26(path, "utf8")).running !== true;
11414
11677
  } catch {
11415
11678
  return true;
11416
11679
  }
11417
11680
  }
11418
11681
  function orphanedStreams(o) {
11419
- const dir = join28(o.stateDir, "streams");
11420
- if (!existsSync26(dir)) return [];
11682
+ const dir = join30(o.stateDir, "streams");
11683
+ if (!existsSync27(dir)) return [];
11421
11684
  const ownerAlive = o.isOwnerAlive ?? pidAlive;
11422
11685
  const out = [];
11423
11686
  for (const name of readdirSync9(dir)) {
11424
11687
  if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
11425
- const streamPath = join28(dir, name);
11688
+ const streamPath = join30(dir, name);
11426
11689
  if (runFinished(streamPath)) continue;
11427
11690
  const sidecar = readRunSidecar(streamPath);
11428
11691
  if (!sidecar || sidecar.route !== o.routeLabel) continue;
@@ -11475,15 +11738,15 @@ async function reconcileRuns(o) {
11475
11738
  }
11476
11739
 
11477
11740
  // src/live-runs.ts
11478
- import { existsSync as existsSync27, readdirSync as readdirSync10, statSync as statSync9 } from "fs";
11479
- import { join as join29 } from "path";
11741
+ import { existsSync as existsSync28, readdirSync as readdirSync10, statSync as statSync9 } from "fs";
11742
+ import { join as join31 } from "path";
11480
11743
  function liveRuns(stateDir, isAlive = runIsAlive) {
11481
- const dir = join29(stateDir, "streams");
11482
- if (!existsSync27(dir)) return [];
11744
+ const dir = join31(stateDir, "streams");
11745
+ if (!existsSync28(dir)) return [];
11483
11746
  const out = [];
11484
11747
  for (const name of readdirSync10(dir)) {
11485
11748
  if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
11486
- const streamPath = join29(dir, name);
11749
+ const streamPath = join31(dir, name);
11487
11750
  if (runFinished(streamPath)) continue;
11488
11751
  const sidecar = readRunSidecar(streamPath);
11489
11752
  if (!sidecar || !isAlive(sidecar)) continue;
@@ -11493,12 +11756,12 @@ function liveRuns(stateDir, isAlive = runIsAlive) {
11493
11756
  }
11494
11757
  function runActivity(stateDir, isAlive = runIsAlive) {
11495
11758
  const activeRuns = liveRuns(stateDir, isAlive).length;
11496
- const dir = join29(stateDir, "streams");
11759
+ const dir = join31(stateDir, "streams");
11497
11760
  let newest = 0;
11498
- if (existsSync27(dir)) {
11761
+ if (existsSync28(dir)) {
11499
11762
  for (const name of readdirSync10(dir)) {
11500
11763
  if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
11501
- const streamPath = join29(dir, name);
11764
+ const streamPath = join31(dir, name);
11502
11765
  if (!runFinished(streamPath)) continue;
11503
11766
  try {
11504
11767
  newest = Math.max(newest, statSync9(`${streamPath}.meta.json`).mtimeMs);
@@ -11869,7 +12132,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
11869
12132
  if (host === "windows") return [];
11870
12133
  const ctx = installContext();
11871
12134
  const systemd = detectSystemd();
11872
- return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) => existsSync28(p));
12135
+ return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) => existsSync29(p));
11873
12136
  };
11874
12137
  return {
11875
12138
  liveRuns: () => liveRuns(stateDir).map((r) => ({ role: r.role, route: r.route, pid: r.pid, ticketRecordId: r.ticketRecordId ?? void 0, streamPath: r.streamPath })),
@@ -11930,7 +12193,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
11930
12193
  },
11931
12194
  remaining() {
11932
12195
  const left = [];
11933
- if (existsSync28(cfg.configFile)) left.push(`config ${cfg.configFile}`);
12196
+ if (existsSync29(cfg.configFile)) left.push(`config ${cfg.configFile}`);
11934
12197
  for (const h of forgeHosts()) if (hasForgeToken(cfg.ship.forges, h, stateDir)) left.push(`forge token for ${h}`);
11935
12198
  try {
11936
12199
  for (const k of readdirSync11(keysDir(stateDir))) left.push(`seat key ${k}`);
@@ -12538,19 +12801,19 @@ function planSandbox(repo, decl, warm = {}) {
12538
12801
  const layer = repoLayer(repo, decl, crew, warm);
12539
12802
  return { repo, tag: layer.tag, claude: SANDBOX_CLAUDE_VERSION, layers: [base, crew, layer] };
12540
12803
  }
12541
- var sandboxStatePath = (stateDir) => join30(stateDir, "sandbox-images.json");
12804
+ var sandboxStatePath = (stateDir) => join32(stateDir, "sandbox-images.json");
12542
12805
  function readSandboxState(stateDir) {
12543
12806
  try {
12544
- const v = JSON.parse(readFileSync25(sandboxStatePath(stateDir), "utf8"));
12807
+ const v = JSON.parse(readFileSync27(sandboxStatePath(stateDir), "utf8"));
12545
12808
  return v && typeof v === "object" && !Array.isArray(v) ? v : {};
12546
12809
  } catch {
12547
12810
  return {};
12548
12811
  }
12549
12812
  }
12550
12813
  function writeSandboxState(stateDir, state) {
12551
- mkdirSync18(stateDir, { recursive: true });
12814
+ mkdirSync19(stateDir, { recursive: true });
12552
12815
  const file = sandboxStatePath(stateDir);
12553
- writeFileSync19(`${file}.tmp`, `${JSON.stringify(state, null, 2)}
12816
+ writeFileSync20(`${file}.tmp`, `${JSON.stringify(state, null, 2)}
12554
12817
  `);
12555
12818
  renameSync3(`${file}.tmp`, file);
12556
12819
  }
@@ -12570,7 +12833,7 @@ var spawnBuildRunner = (engine = "docker") => (args, logPath) => new Promise((re
12570
12833
  child.stdout.on("data", (d) => chunks.push(d));
12571
12834
  child.stderr.on("data", (d) => chunks.push(d));
12572
12835
  const done = (code, extra = "") => {
12573
- writeFileSync19(logPath, `$ ${engine} ${args.join(" ")}
12836
+ writeFileSync20(logPath, `$ ${engine} ${args.join(" ")}
12574
12837
  ${Buffer.concat(chunks).toString("utf8")}${extra}`, { flag: "a" });
12575
12838
  resolve8(code);
12576
12839
  };
@@ -12592,7 +12855,7 @@ var engineRemoveImage = (engine = "docker", exec = realExec2) => (tag) => {
12592
12855
  } catch {
12593
12856
  }
12594
12857
  };
12595
- var sandboxLogDir = (stateDir) => join30(stateDir, "sandbox-logs");
12858
+ var sandboxLogDir = (stateDir) => join32(stateDir, "sandbox-logs");
12596
12859
  async function buildSandboxImage(plan, deps) {
12597
12860
  const now = deps.now ?? (() => /* @__PURE__ */ new Date());
12598
12861
  const state = readSandboxState(deps.stateDir);
@@ -12601,20 +12864,20 @@ async function buildSandboxImage(plan, deps) {
12601
12864
  return { kind: "reused", tag: plan.tag };
12602
12865
  }
12603
12866
  const logDir = sandboxLogDir(deps.stateDir);
12604
- mkdirSync18(logDir, { recursive: true });
12605
- const logPath = join30(logDir, `${slug(plan.repo)}-${now().toISOString().replace(/[:.]/g, "-")}.log`);
12867
+ mkdirSync19(logDir, { recursive: true });
12868
+ const logPath = join32(logDir, `${slug(plan.repo)}-${now().toISOString().replace(/[:.]/g, "-")}.log`);
12606
12869
  const builtLayers = [];
12607
12870
  for (const layer of plan.layers) {
12608
12871
  if (deps.hasImage(layer.tag)) continue;
12609
- const ctx = join30(tmpdir3(), `crew-sandbox-${hash([layer.tag, process.pid, Date.now()])}`);
12610
- mkdirSync18(ctx, { recursive: true });
12872
+ const ctx = join32(tmpdir3(), `crew-sandbox-${hash([layer.tag, process.pid, Date.now()])}`);
12873
+ mkdirSync19(ctx, { recursive: true });
12611
12874
  try {
12612
- writeFileSync19(join30(ctx, "Dockerfile"), layer.dockerfile);
12875
+ writeFileSync20(join32(ctx, "Dockerfile"), layer.dockerfile);
12613
12876
  for (const [name, content] of Object.entries(layer.files)) {
12614
- mkdirSync18(dirname13(join30(ctx, name)), { recursive: true });
12615
- writeFileSync19(join30(ctx, name), content);
12877
+ mkdirSync19(dirname14(join32(ctx, name)), { recursive: true });
12878
+ writeFileSync20(join32(ctx, name), content);
12616
12879
  }
12617
- const code = await deps.run(["build", "--tag", layer.tag, "--file", join30(ctx, "Dockerfile"), ctx], logPath);
12880
+ const code = await deps.run(["build", "--tag", layer.tag, "--file", join32(ctx, "Dockerfile"), ctx], logPath);
12618
12881
  if (code !== 0) {
12619
12882
  state[plan.repo] = { ...entry, failure: { tag: plan.tag, at: now().toISOString(), logPath } };
12620
12883
  writeSandboxState(deps.stateDir, state);
@@ -12622,7 +12885,7 @@ async function buildSandboxImage(plan, deps) {
12622
12885
  }
12623
12886
  builtLayers.push(layer.name);
12624
12887
  } finally {
12625
- rmSync14(ctx, { recursive: true, force: true });
12888
+ rmSync15(ctx, { recursive: true, force: true });
12626
12889
  }
12627
12890
  }
12628
12891
  const tags = [{ tag: plan.tag, builtAt: now().toISOString(), claude: plan.claude }, ...entry.tags.filter((t) => t.tag !== plan.tag)];
@@ -12803,9 +13066,9 @@ function managedRuntimeWarnings(kind, ship) {
12803
13066
 
12804
13067
  // src/selftest.ts
12805
13068
  import { spawn as spawn6 } from "child_process";
12806
- import { existsSync as existsSync29, mkdirSync as mkdirSync19, mkdtempSync as mkdtempSync2, readFileSync as readFileSync26, rmSync as rmSync15, writeFileSync as writeFileSync20 } from "fs";
13069
+ import { existsSync as existsSync30, mkdirSync as mkdirSync20, mkdtempSync as mkdtempSync2, readFileSync as readFileSync28, rmSync as rmSync16, writeFileSync as writeFileSync21 } from "fs";
12807
13070
  import { tmpdir as tmpdir4 } from "os";
12808
- import { join as join31 } from "path";
13071
+ import { join as join33 } from "path";
12809
13072
  var SELFTEST_TIMEOUT_MS = 12e4;
12810
13073
  var KEY_VAR = "CREW_API_KEY";
12811
13074
  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: ";
@@ -12966,7 +13229,7 @@ async function runSelftest(i, deps = realDeps) {
12966
13229
  ...adapter.runEnv(spec),
12967
13230
  [KEY_VAR]: i.apiKey
12968
13231
  };
12969
- const cwd = mkdtempSync2(join31(tmpdir4(), "crew-selftest-"));
13232
+ const cwd = mkdtempSync2(join33(tmpdir4(), "crew-selftest-"));
12970
13233
  try {
12971
13234
  const run3 = await deps.run({
12972
13235
  bin: invocation.bin,
@@ -12988,7 +13251,7 @@ async function runSelftest(i, deps = realDeps) {
12988
13251
  return finish({ ok: false, failure, detail, ...usage2 });
12989
13252
  } finally {
12990
13253
  try {
12991
- rmSync15(cwd, { recursive: true, force: true });
13254
+ rmSync16(cwd, { recursive: true, force: true });
12992
13255
  } catch {
12993
13256
  }
12994
13257
  }
@@ -12999,11 +13262,11 @@ function formatResult(r) {
12999
13262
  const secs = `${(r.ms / 1e3).toFixed(1)}s`;
13000
13263
  return r.ok ? `${r.toolchain}: PASS ${r.detail} (${secs}${tok})` : `${r.toolchain}: FAIL ${r.failure} \u2014 ${r.detail} (${secs})`;
13001
13264
  }
13002
- var fileFor2 = (stateDir) => join31(stateDir, "selftest.json");
13265
+ var fileFor2 = (stateDir) => join33(stateDir, "selftest.json");
13003
13266
  function readSelftest(stateDir) {
13004
13267
  try {
13005
13268
  const p = fileFor2(stateDir);
13006
- return existsSync29(p) ? JSON.parse(readFileSync26(p, "utf8")) : {};
13269
+ return existsSync30(p) ? JSON.parse(readFileSync28(p, "utf8")) : {};
13007
13270
  } catch {
13008
13271
  return {};
13009
13272
  }
@@ -13011,8 +13274,8 @@ function readSelftest(stateDir) {
13011
13274
  function recordSelftest(stateDir, results) {
13012
13275
  const all = readSelftest(stateDir);
13013
13276
  for (const r of results) all[r.toolchain] = r;
13014
- mkdirSync19(stateDir, { recursive: true });
13015
- writeFileSync20(fileFor2(stateDir), `${JSON.stringify(all, null, 2)}
13277
+ mkdirSync20(stateDir, { recursive: true });
13278
+ writeFileSync21(fileFor2(stateDir), `${JSON.stringify(all, null, 2)}
13016
13279
  `);
13017
13280
  }
13018
13281
  function doctorSelftestLines(stored, toolchains) {
@@ -13024,7 +13287,7 @@ function doctorSelftestLines(stored, toolchains) {
13024
13287
  }
13025
13288
 
13026
13289
  // src/cli.ts
13027
- import { resolve as resolve7, dirname as dirname32, join as join53, basename as basename16 } from "path";
13290
+ import { resolve as resolve7, dirname as dirname33, join as join55, basename as basename16 } from "path";
13028
13291
  import { fileURLToPath } from "url";
13029
13292
  import { hostname as hostname4, homedir as homedir11 } from "os";
13030
13293
  import { spawnSync as spawnSync11 } from "child_process";
@@ -13032,6 +13295,7 @@ import { parse as parseYaml2 } from "yaml";
13032
13295
 
13033
13296
  // src/argv.ts
13034
13297
  var VALUE_FLAGS = /* @__PURE__ */ new Set([
13298
+ "state-dir",
13035
13299
  "agent-bin",
13036
13300
  "area",
13037
13301
  "base-url",
@@ -13221,14 +13485,14 @@ function rosterMarkdown(roster, meId) {
13221
13485
  }
13222
13486
 
13223
13487
  // src/connect-resume.ts
13224
- import { mkdirSync as mkdirSync20, readFileSync as readFileSync27, rmSync as rmSync16, writeFileSync as writeFileSync21 } from "fs";
13225
- import { dirname as dirname14, join as join32 } from "path";
13488
+ import { mkdirSync as mkdirSync21, readFileSync as readFileSync29, rmSync as rmSync17, writeFileSync as writeFileSync22 } from "fs";
13489
+ import { dirname as dirname15, join as join34 } from "path";
13226
13490
  function lastSessionFile(stateDir) {
13227
- return join32(stateDir, "connect-session.json");
13491
+ return join34(stateDir, "connect-session.json");
13228
13492
  }
13229
13493
  function readPointers(stateDir) {
13230
13494
  try {
13231
- const parsed = JSON.parse(readFileSync27(lastSessionFile(stateDir), "utf8"));
13495
+ const parsed = JSON.parse(readFileSync29(lastSessionFile(stateDir), "utf8"));
13232
13496
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
13233
13497
  const out = {};
13234
13498
  for (const [host, v] of Object.entries(parsed)) {
@@ -13242,8 +13506,8 @@ function readPointers(stateDir) {
13242
13506
  }
13243
13507
  function writePointers(stateDir, pointers) {
13244
13508
  const file = lastSessionFile(stateDir);
13245
- mkdirSync20(dirname14(file), { recursive: true });
13246
- writeFileSync21(file, `${JSON.stringify(pointers, null, 2)}
13509
+ mkdirSync21(dirname15(file), { recursive: true });
13510
+ writeFileSync22(file, `${JSON.stringify(pointers, null, 2)}
13247
13511
  `, { mode: 384 });
13248
13512
  }
13249
13513
  function rememberSession(stateDir, host, workspaceSlug) {
@@ -13260,7 +13524,7 @@ function forgetWorkspace(stateDir, host, workspaceSlug) {
13260
13524
  const { [host]: _old, ...rest } = all;
13261
13525
  const next = left.length ? { ...rest, [host]: left } : rest;
13262
13526
  if (Object.keys(next).length) writePointers(stateDir, next);
13263
- else rmSync16(lastSessionFile(stateDir), { force: true });
13527
+ else rmSync17(lastSessionFile(stateDir), { force: true });
13264
13528
  } catch {
13265
13529
  }
13266
13530
  }
@@ -13300,7 +13564,7 @@ function forgetSession(stateDir, host) {
13300
13564
  try {
13301
13565
  const { [host]: _gone, ...rest } = readPointers(stateDir);
13302
13566
  if (Object.keys(rest).length) writePointers(stateDir, rest);
13303
- else rmSync16(lastSessionFile(stateDir), { force: true });
13567
+ else rmSync17(lastSessionFile(stateDir), { force: true });
13304
13568
  } catch {
13305
13569
  }
13306
13570
  }
@@ -13328,15 +13592,15 @@ async function signOutSessions(o) {
13328
13592
  }
13329
13593
 
13330
13594
  // src/reset.ts
13331
- import { existsSync as existsSync30, mkdirSync as mkdirSync21, readFileSync as readFileSync28, readdirSync as readdirSync12, rmSync as rmSync17, statSync as statSync10, writeFileSync as writeFileSync22 } from "fs";
13332
- import { dirname as dirname15, join as join33 } from "path";
13595
+ import { existsSync as existsSync31, mkdirSync as mkdirSync22, readFileSync as readFileSync30, readdirSync as readdirSync12, rmSync as rmSync18, statSync as statSync10, writeFileSync as writeFileSync23 } from "fs";
13596
+ import { dirname as dirname16, join as join35 } from "path";
13333
13597
  import { hostFromUrl as hostFromUrl3 } from "@tablation/client";
13334
13598
  function clonedCheckoutsFile(stateDir) {
13335
- return join33(stateDir, "cloned-checkouts.json");
13599
+ return join35(stateDir, "cloned-checkouts.json");
13336
13600
  }
13337
13601
  function readClonedCheckouts(stateDir) {
13338
13602
  try {
13339
- const parsed = JSON.parse(readFileSync28(clonedCheckoutsFile(stateDir), "utf8"));
13603
+ const parsed = JSON.parse(readFileSync30(clonedCheckoutsFile(stateDir), "utf8"));
13340
13604
  return Array.isArray(parsed) ? parsed.filter((d) => typeof d === "string") : [];
13341
13605
  } catch {
13342
13606
  return [];
@@ -13346,8 +13610,8 @@ function recordClonedCheckout(stateDir, dir) {
13346
13610
  const known = readClonedCheckouts(stateDir);
13347
13611
  if (known.includes(dir)) return;
13348
13612
  const file = clonedCheckoutsFile(stateDir);
13349
- mkdirSync21(dirname15(file), { recursive: true });
13350
- writeFileSync22(file, `${JSON.stringify([...known, dir], null, 2)}
13613
+ mkdirSync22(dirname16(file), { recursive: true });
13614
+ writeFileSync23(file, `${JSON.stringify([...known, dir], null, 2)}
13351
13615
  `);
13352
13616
  }
13353
13617
  var mark = (dryRun) => dryRun ? "would-remove" : "removed";
@@ -13401,8 +13665,8 @@ async function runReset(o, fx) {
13401
13665
  }
13402
13666
  if (o.beforeState) steps.push(...await o.beforeState());
13403
13667
  steps.push(...o.wipeState ? wipeStateDir(o.stateDir, o.dryRun) : removeState(o.stateDir, o.dryRun));
13404
- if (existsSync30(o.configFile)) {
13405
- if (!o.dryRun) rmSync17(o.configFile, { force: true });
13668
+ if (existsSync31(o.configFile)) {
13669
+ if (!o.dryRun) rmSync18(o.configFile, { force: true });
13406
13670
  steps.push({ step: "config", target: o.configFile, action: mark(o.dryRun) });
13407
13671
  } else {
13408
13672
  steps.push({ step: "config", target: o.configFile, action: "absent" });
@@ -13410,7 +13674,7 @@ async function runReset(o, fx) {
13410
13674
  return steps;
13411
13675
  }
13412
13676
  function removeCheckout(dir, dryRun, fx) {
13413
- if (!existsSync30(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
13677
+ if (!existsSync31(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
13414
13678
  const { dirty, linkedWorktrees } = fx.inspectCheckout(dir);
13415
13679
  if (dirty.length) {
13416
13680
  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" : ""})` };
@@ -13418,21 +13682,21 @@ function removeCheckout(dir, dryRun, fx) {
13418
13682
  if (linkedWorktrees.length) {
13419
13683
  return { step: "checkout", target: dir, action: "kept", detail: `has linked worktrees (${linkedWorktrees.join(", ")}); remove them first` };
13420
13684
  }
13421
- if (!dryRun) rmSync17(dir, { recursive: true, force: true });
13685
+ if (!dryRun) rmSync18(dir, { recursive: true, force: true });
13422
13686
  return { step: "checkout", target: dir, action: mark(dryRun) };
13423
13687
  }
13424
13688
  function removeState(stateDir, dryRun) {
13425
13689
  const out = [];
13426
- const targets = [join33(stateDir, "resolved"), join33(stateDir, "keys"), clonedCheckoutsFile(stateDir), join33(stateDir, "connect-session.json"), join33(stateDir, "reload-requested"), join33(stateDir, "daemon.started")];
13690
+ const targets = [join35(stateDir, "resolved"), join35(stateDir, "keys"), clonedCheckoutsFile(stateDir), join35(stateDir, "connect-session.json"), join35(stateDir, "reload-requested"), join35(stateDir, "daemon.started")];
13427
13691
  try {
13428
13692
  for (const name of readdirSync12(stateDir)) {
13429
- if (name.startsWith(".") && statSync10(join33(stateDir, name)).isFile()) targets.push(join33(stateDir, name));
13693
+ if (name.startsWith(".") && statSync10(join35(stateDir, name)).isFile()) targets.push(join35(stateDir, name));
13430
13694
  }
13431
13695
  } catch {
13432
13696
  }
13433
13697
  for (const t of targets) {
13434
- if (!existsSync30(t)) continue;
13435
- if (!dryRun) rmSync17(t, { recursive: true, force: true });
13698
+ if (!existsSync31(t)) continue;
13699
+ if (!dryRun) rmSync18(t, { recursive: true, force: true });
13436
13700
  out.push({ step: "state", target: t, action: mark(dryRun) });
13437
13701
  }
13438
13702
  if (!out.length) out.push({ step: "state", target: stateDir, action: "absent", detail: "nothing to remove" });
@@ -13446,7 +13710,7 @@ function wipeStateDir(stateDir, dryRun) {
13446
13710
  return [{ step: "state", target: stateDir, action: "absent", detail: "no state directory" }];
13447
13711
  }
13448
13712
  if (!names.length) return [{ step: "state", target: stateDir, action: "absent", detail: "already empty" }];
13449
- if (!dryRun) for (const n of names) rmSync17(join33(stateDir, n), { recursive: true, force: true });
13713
+ if (!dryRun) for (const n of names) rmSync18(join35(stateDir, n), { recursive: true, force: true });
13450
13714
  return [{ step: "state", target: stateDir, action: mark(dryRun), detail: `${names.length} entr${names.length === 1 ? "y" : "ies"}` }];
13451
13715
  }
13452
13716
  function describeResetStep(s) {
@@ -13527,9 +13791,9 @@ async function runDeprovision(o, fx, deps) {
13527
13791
  }
13528
13792
 
13529
13793
  // src/pool-member.ts
13530
- import { mkdirSync as mkdirSync22, readFileSync as readFileSync29, renameSync as renameSync4, writeFileSync as writeFileSync23 } from "fs";
13794
+ import { mkdirSync as mkdirSync23, readFileSync as readFileSync31, renameSync as renameSync4, writeFileSync as writeFileSync24 } from "fs";
13531
13795
  import { homedir as homedir8 } from "os";
13532
- import { dirname as dirname16, join as join34 } from "path";
13796
+ import { dirname as dirname17, join as join36 } from "path";
13533
13797
  var POOL_EXIT = { ok: 0, failed: 1, credential: 2 };
13534
13798
  function backoffMs(attempt) {
13535
13799
  return Math.min(6e4, 5e3 * 2 ** Math.max(0, attempt));
@@ -13760,22 +14024,22 @@ async function runPoolMember(deps) {
13760
14024
  }
13761
14025
  }
13762
14026
  function defaultMemberDir(env = process.env) {
13763
- return env.CREW_POOL_DIR ?? join34(env.XDG_STATE_HOME ?? join34(homedir8(), ".local", "state"), "crew-pool-member");
14027
+ return env.CREW_POOL_DIR ?? join36(env.XDG_STATE_HOME ?? join36(homedir8(), ".local", "state"), "crew-pool-member");
13764
14028
  }
13765
14029
  function fileMemberStore(dir) {
13766
- const file = join34(dir, "member.json");
14030
+ const file = join36(dir, "member.json");
13767
14031
  return {
13768
14032
  read() {
13769
14033
  try {
13770
- return JSON.parse(readFileSync29(file, "utf8"));
14034
+ return JSON.parse(readFileSync31(file, "utf8"));
13771
14035
  } catch {
13772
14036
  return {};
13773
14037
  }
13774
14038
  },
13775
14039
  write(f) {
13776
- mkdirSync22(dirname16(file), { recursive: true, mode: 448 });
14040
+ mkdirSync23(dirname17(file), { recursive: true, mode: 448 });
13777
14041
  const tmp = `${file}.tmp`;
13778
- writeFileSync23(tmp, `${JSON.stringify(f)}
14042
+ writeFileSync24(tmp, `${JSON.stringify(f)}
13779
14043
  `, { mode: 384 });
13780
14044
  renameSync4(tmp, file);
13781
14045
  }
@@ -13827,16 +14091,16 @@ function hostArchitecture(arch = process.arch) {
13827
14091
 
13828
14092
  // src/pool-member-host.ts
13829
14093
  import "child_process";
13830
- import { existsSync as existsSync34 } from "fs";
14094
+ import { existsSync as existsSync35 } from "fs";
13831
14095
  import { hostname as hostname3 } from "os";
13832
14096
 
13833
14097
  // src/machine-id.ts
13834
14098
  import { execFileSync as execFileSync10 } from "child_process";
13835
14099
  import { createHash as createHash7 } from "crypto";
13836
- import { existsSync as existsSync31, mkdirSync as mkdirSync23, readFileSync as readFileSync30, writeFileSync as writeFileSync24 } from "fs";
13837
- import { dirname as dirname17, join as join35 } from "path";
14100
+ import { existsSync as existsSync32, mkdirSync as mkdirSync24, readFileSync as readFileSync32, writeFileSync as writeFileSync25 } from "fs";
14101
+ import { dirname as dirname18, join as join37 } from "path";
13838
14102
  function machineIdPathFor(stateDir) {
13839
- return join35(stateDir, "machine-id");
14103
+ return join37(stateDir, "machine-id");
13840
14104
  }
13841
14105
  var sha256 = (s) => createHash7("sha256").update(s).digest("hex");
13842
14106
  function readRawMachineId(platform2 = process.platform) {
@@ -13849,8 +14113,8 @@ function readRawMachineId(platform2 = process.platform) {
13849
14113
  return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out)?.[1];
13850
14114
  }
13851
14115
  for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
13852
- if (existsSync31(p)) {
13853
- const id = readFileSync30(p, "utf8").trim();
14116
+ if (existsSync32(p)) {
14117
+ const id = readFileSync32(p, "utf8").trim();
13854
14118
  if (id) return id;
13855
14119
  }
13856
14120
  }
@@ -13861,15 +14125,15 @@ function readRawMachineId(platform2 = process.platform) {
13861
14125
  function ensureMachineId(stateDir, opts = {}) {
13862
14126
  if (opts.override?.trim()) return sha256(opts.override.trim());
13863
14127
  const path = machineIdPathFor(stateDir);
13864
- if (existsSync31(path)) {
13865
- const cached = readFileSync30(path, "utf8").trim();
14128
+ if (existsSync32(path)) {
14129
+ const cached = readFileSync32(path, "utf8").trim();
13866
14130
  if (/^[0-9a-f]{64}$/.test(cached)) return cached;
13867
14131
  }
13868
14132
  const raw = (opts.read ?? readRawMachineId)();
13869
14133
  if (!raw) return void 0;
13870
14134
  const hashed = sha256(raw);
13871
- mkdirSync23(dirname17(path), { recursive: true });
13872
- writeFileSync24(path, `${hashed}
14135
+ mkdirSync24(dirname18(path), { recursive: true });
14136
+ writeFileSync25(path, `${hashed}
13873
14137
  `, { mode: 384 });
13874
14138
  return hashed;
13875
14139
  }
@@ -13882,7 +14146,7 @@ function matchShipRows(ships, shipName2, machineId) {
13882
14146
 
13883
14147
  // src/provision.ts
13884
14148
  import { spawnSync as spawnSync8 } from "child_process";
13885
- import { existsSync as existsSync32, readFileSync as readFileSync31, writeFileSync as writeFileSync25 } from "fs";
14149
+ import { existsSync as existsSync33, readFileSync as readFileSync33, writeFileSync as writeFileSync26 } from "fs";
13886
14150
  import { hostname as hostname2 } from "os";
13887
14151
  import { parseDocument as parseDocument3, isMap as isMap2, isScalar } from "yaml";
13888
14152
 
@@ -13891,7 +14155,7 @@ import { parseDocument as parseDocument2 } from "yaml";
13891
14155
 
13892
14156
  // src/repos-cmd.ts
13893
14157
  import { homedir as homedir9 } from "os";
13894
- import { basename as basename10, join as join36, resolve as resolve4 } from "path";
14158
+ import { basename as basename10, join as join38, resolve as resolve4 } from "path";
13895
14159
  import { isMap, isSeq, parseDocument, stringify as stringify2 } from "yaml";
13896
14160
  var ReposError = class extends Error {
13897
14161
  };
@@ -14169,7 +14433,7 @@ function planRepoClone(i) {
14169
14433
  }
14170
14434
  const ship = doc.get("ship", true);
14171
14435
  const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
14172
- const target = i.dir ? expandDir(i.dir, i.base) : join36(expandDir(basePath, i.base), i.route.split("/")[0], name);
14436
+ const target = i.dir ? expandDir(i.dir, i.base) : join38(expandDir(basePath, i.base), i.route.split("/")[0], name);
14173
14437
  const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name, isCheckout: i.isCheckout });
14174
14438
  const forgeHost = i.resolved?.repoForgeHosts?.[id];
14175
14439
  return { ...add, remote, target, ...forgeHost ? { forgeHost } : {} };
@@ -14476,7 +14740,7 @@ function realProvisionDeps(crewHome) {
14476
14740
  };
14477
14741
  const configFile = () => {
14478
14742
  const f = findConfigFile(crewHome);
14479
- return existsSync32(f) ? f : null;
14743
+ return existsSync33(f) ? f : null;
14480
14744
  };
14481
14745
  return {
14482
14746
  crew: spawnCrew,
@@ -14484,13 +14748,13 @@ function realProvisionDeps(crewHome) {
14484
14748
  enableRoute(route2) {
14485
14749
  const file = configFile();
14486
14750
  if (!file) return false;
14487
- const text2 = readFileSync31(file, "utf8");
14751
+ const text2 = readFileSync33(file, "utf8");
14488
14752
  const doc = parseDocument3(text2);
14489
14753
  const routes = doc.get("routes");
14490
14754
  const entry = routes?.items?.find((r) => r.get("route") === route2);
14491
14755
  if (!entry) throw new Error(`route ${route2} is not in ${file}`);
14492
14756
  if (entry.get("enabled") === true) return false;
14493
- writeFileSync25(file, planEnable(text2, route2));
14757
+ writeFileSync26(file, planEnable(text2, route2));
14494
14758
  return true;
14495
14759
  },
14496
14760
  installState() {
@@ -14510,14 +14774,14 @@ function realProvisionDeps(crewHome) {
14510
14774
  continue;
14511
14775
  }
14512
14776
  for (const p of plan.unitPaths) {
14513
- if (!existsSync32(p) || readFileSync31(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
14777
+ if (!existsSync33(p) || readFileSync33(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
14514
14778
  }
14515
14779
  }
14516
14780
  const label = labelFor(crewHome, "run");
14517
14781
  let running = false;
14518
14782
  let lingering = true;
14519
14783
  if (mechanism === "systemd") {
14520
- const system = ctx.root || existsSync32(systemUnitPathFor(label));
14784
+ const system = ctx.root || existsSync33(systemUnitPathFor(label));
14521
14785
  const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
14522
14786
  running = spawnSync8(ctl[0], [...ctl.slice(1), "is-active", "--quiet", `${label}.service`]).status === 0;
14523
14787
  if (!system) {
@@ -14534,18 +14798,18 @@ function realProvisionDeps(crewHome) {
14534
14798
  setShipSetting(key, value2) {
14535
14799
  const file = configFile();
14536
14800
  if (!file) return false;
14537
- const doc = parseDocument3(readFileSync31(file, "utf8"));
14801
+ const doc = parseDocument3(readFileSync33(file, "utf8"));
14538
14802
  if (doc.getIn(["ship", key]) === value2) return false;
14539
14803
  doc.setIn(["ship", key], value2);
14540
- writeFileSync25(file, doc.toString());
14804
+ writeFileSync26(file, doc.toString());
14541
14805
  return true;
14542
14806
  },
14543
14807
  applyManagedDefaults() {
14544
14808
  const file = configFile();
14545
14809
  if (!file) return [];
14546
- const doc = parseDocument3(readFileSync31(file, "utf8"));
14810
+ const doc = parseDocument3(readFileSync33(file, "utf8"));
14547
14811
  const wrote = applyManagedDefaultsToDoc(doc);
14548
- if (wrote.length) writeFileSync25(file, doc.toString());
14812
+ if (wrote.length) writeFileSync26(file, doc.toString());
14549
14813
  return wrote;
14550
14814
  },
14551
14815
  storedToken(host) {
@@ -14562,9 +14826,9 @@ host=${host}
14562
14826
 
14563
14827
  // src/tunnel.ts
14564
14828
  import { spawn as spawn7 } from "child_process";
14565
- import { existsSync as existsSync33, mkdirSync as mkdirSync24, readFileSync as readFileSync32, writeFileSync as writeFileSync26, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "fs";
14829
+ import { existsSync as existsSync34, mkdirSync as mkdirSync25, readFileSync as readFileSync34, writeFileSync as writeFileSync27, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "fs";
14566
14830
  import { randomInt } from "crypto";
14567
- import { join as join37 } from "path";
14831
+ import { join as join39 } from "path";
14568
14832
  var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
14569
14833
  var defaultSpawn = (cmd, args) => spawn7(cmd, args, DEFAULT_SPAWN_OPTIONS);
14570
14834
  var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
@@ -14636,21 +14900,21 @@ function startTunnel(opts) {
14636
14900
  };
14637
14901
  }
14638
14902
  function tunnelStatePath(stateDir, workspaceId) {
14639
- return join37(stateDir, "passengers", `${workspaceId}.json`);
14903
+ return join39(stateDir, "passengers", `${workspaceId}.json`);
14640
14904
  }
14641
14905
  function readPersistedTunnel(stateDir, workspaceId) {
14642
14906
  const path = tunnelStatePath(stateDir, workspaceId);
14643
- if (!existsSync33(path)) return void 0;
14907
+ if (!existsSync34(path)) return void 0;
14644
14908
  try {
14645
- return JSON.parse(readFileSync32(path, "utf8"));
14909
+ return JSON.parse(readFileSync34(path, "utf8"));
14646
14910
  } catch {
14647
14911
  return void 0;
14648
14912
  }
14649
14913
  }
14650
14914
  function writePersistedTunnel(stateDir, state) {
14651
14915
  const path = tunnelStatePath(stateDir, state.workspaceId);
14652
- mkdirSync24(join37(stateDir, "passengers"), { recursive: true });
14653
- writeFileSync26(path, `${JSON.stringify(state, null, 2)}
14916
+ mkdirSync25(join39(stateDir, "passengers"), { recursive: true });
14917
+ writeFileSync27(path, `${JSON.stringify(state, null, 2)}
14654
14918
  `);
14655
14919
  }
14656
14920
  function updatePersistedTunnel(stateDir, workspaceId, patch2) {
@@ -14752,8 +15016,8 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
14752
15016
  }
14753
15017
  }
14754
15018
  function readAllPersistedWorkspaceIds(stateDir) {
14755
- const dir = join37(stateDir, "passengers");
14756
- if (!existsSync33(dir)) return [];
15019
+ const dir = join39(stateDir, "passengers");
15020
+ if (!existsSync34(dir)) return [];
14757
15021
  return readdirSync13(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
14758
15022
  }
14759
15023
 
@@ -14778,7 +15042,7 @@ function realPoolMemberDeps(o) {
14778
15042
  crewVersion: crewVersion(o.crewHome),
14779
15043
  hostname: hostname3()
14780
15044
  },
14781
- hasShipConfig: () => existsSync34(findConfigFile(o.crewHome)),
15045
+ hasShipConfig: () => existsSync35(findConfigFile(o.crewHome)),
14782
15046
  async provision(lease, instanceClass, shipKind) {
14783
15047
  const hosts = lease.forges.map((f) => f.host.toLowerCase());
14784
15048
  const lines2 = [];
@@ -15654,8 +15918,8 @@ ${waiting.map((t) => `| ${displayKey2(t)} | ${t.status} | ${blockers(t, i)} |`).
15654
15918
  }
15655
15919
 
15656
15920
  // src/poll.ts
15657
- import { writeFileSync as writeFileSync27 } from "fs";
15658
- import { join as join38 } from "path";
15921
+ import { writeFileSync as writeFileSync28 } from "fs";
15922
+ import { join as join40 } from "path";
15659
15923
 
15660
15924
  // src/stalled.ts
15661
15925
  var STALLED_AFTER_CYCLES = 30;
@@ -15962,7 +16226,7 @@ async function decideCycle(o) {
15962
16226
  }
15963
16227
  return decision;
15964
16228
  }
15965
- var digestPath = (stateDir, route2, role, runId) => join38(stateDir, `digest-${route2}-${role}${runId ? `-${runId}` : ""}.md`);
16229
+ var digestPath = (stateDir, route2, role, runId) => join40(stateDir, `digest-${route2}-${role}${runId ? `-${runId}` : ""}.md`);
15966
16230
  function otherRunsRemoved(tickets, own) {
15967
16231
  if (!own) return tickets;
15968
16232
  return tickets.filter((t) => t.id === own.ticket.id || !own.inFlight(t.id));
@@ -16040,7 +16304,7 @@ function writeDigest(o, d, role, stateDir, runId, own) {
16040
16304
 
16041
16305
  ${body}` : body;
16042
16306
  const path = digestPath(stateDir, routeSlug(o.route.route), role, runId);
16043
- writeFileSync27(path, text2);
16307
+ writeFileSync28(path, text2);
16044
16308
  o.emit.emit(`queue digest written for ${role} (${Buffer.byteLength(text2)} bytes)`, {
16045
16309
  data: { role, bytes: Buffer.byteLength(text2) }
16046
16310
  });
@@ -16148,8 +16412,8 @@ async function resolveOne(tracker, t, seat, approvedStatus, buildingStatus, affi
16148
16412
  }
16149
16413
 
16150
16414
  // src/repo-refresh.ts
16151
- import { existsSync as existsSync35, mkdirSync as mkdirSync25, readFileSync as readFileSync33, writeFileSync as writeFileSync28 } from "fs";
16152
- import { dirname as dirname19 } from "path";
16415
+ import { existsSync as existsSync36, mkdirSync as mkdirSync26, readFileSync as readFileSync35, writeFileSync as writeFileSync29 } from "fs";
16416
+ import { dirname as dirname20 } from "path";
16153
16417
  var REFRESH_COOLDOWN_MS = 2 * 6e4;
16154
16418
  var UNSERVABLE_PASSES_BEFORE_ATTENTION = 3;
16155
16419
  var lastAsked = /* @__PURE__ */ new Map();
@@ -16178,11 +16442,11 @@ function applyRepoRows(resolved, rows) {
16178
16442
  }
16179
16443
  function persistRepoRows(stateDir, routeName, rows) {
16180
16444
  const path = resolvedPathFor(stateDir, routeName);
16181
- if (!existsSync35(path)) return;
16182
- const raw = JSON.parse(readFileSync33(path, "utf8"));
16445
+ if (!existsSync36(path)) return;
16446
+ const raw = JSON.parse(readFileSync35(path, "utf8"));
16183
16447
  applyRepoRows(raw, rows);
16184
- mkdirSync25(dirname19(path), { recursive: true });
16185
- writeFileSync28(path, `${JSON.stringify(raw, null, 2)}
16448
+ mkdirSync26(dirname20(path), { recursive: true });
16449
+ writeFileSync29(path, `${JSON.stringify(raw, null, 2)}
16186
16450
  `);
16187
16451
  }
16188
16452
  async function refreshUnknownRepos(o) {
@@ -16632,13 +16896,13 @@ Ship ${shipName2} found: ${problem}. The release refuses to squash a ref nobody
16632
16896
  var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "local_ref_skip" && (e.message ?? "").includes(problem));
16633
16897
 
16634
16898
  // src/connect-wizard.ts
16635
- import { delimiter, dirname as dirname20, join as join39 } from "path";
16636
- import { accessSync as accessSync3, constants as constants3, existsSync as existsSync36, mkdirSync as mkdirSync26, writeFileSync as writeFileSync29 } from "fs";
16899
+ import { delimiter, dirname as dirname21, join as join41 } from "path";
16900
+ import { accessSync as accessSync3, constants as constants3, existsSync as existsSync37, mkdirSync as mkdirSync27, writeFileSync as writeFileSync30 } from "fs";
16637
16901
  import { stringify as stringify3 } from "yaml";
16638
16902
  var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
16639
16903
  function shouldRunWizard(opts) {
16640
16904
  if (!opts.isTTY) return false;
16641
- const exists = opts.configExists ?? existsSync36;
16905
+ const exists = opts.configExists ?? existsSync37;
16642
16906
  return !configSearchPath(opts.crewHome).some((p) => exists(p));
16643
16907
  }
16644
16908
  function firstRunConfigPath() {
@@ -16655,7 +16919,7 @@ function defaultIsExecutable(p) {
16655
16919
  function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
16656
16920
  for (const dir of pathEnv.split(delimiter)) {
16657
16921
  if (!dir) continue;
16658
- const candidate = join39(dir, name);
16922
+ const candidate = join41(dir, name);
16659
16923
  if (isExecutable(candidate)) return candidate;
16660
16924
  }
16661
16925
  return void 0;
@@ -16681,9 +16945,9 @@ async function runWizard(p, env) {
16681
16945
  if (entered && isExec(entered)) agentBin = entered;
16682
16946
  else p.say(entered ? `${entered} is not an executable file.` : "A path is required.");
16683
16947
  }
16684
- const nodeDir = dirname20(env.nodePath);
16948
+ const nodeDir = dirname21(env.nodePath);
16685
16949
  const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
16686
- const baseDirs = [nodeDir, pnpmPath ? dirname20(pnpmPath) : void 0];
16950
+ const baseDirs = [nodeDir, pnpmPath ? dirname21(pnpmPath) : void 0];
16687
16951
  p.say(
16688
16952
  `
16689
16953
  Host Passengers lets this machine run the agent sessions that other people's
@@ -16694,7 +16958,7 @@ Tablation workspaces ask for, in a container. More: ${HOST_PASSENGERS_DOCS_URL}`
16694
16958
  p.say("Docker is not available here, so nothing will be hosted until it is. Your answer is recorded anyway.");
16695
16959
  }
16696
16960
  const dockerPath = hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
16697
- const extraPath = deriveExtraPath([...baseDirs, dockerPath ? dirname20(dockerPath) : void 0]);
16961
+ const extraPath = deriveExtraPath([...baseDirs, dockerPath ? dirname21(dockerPath) : void 0]);
16698
16962
  if (extraPath) {
16699
16963
  p.say(`Hooks will get ${extraPath} on their PATH (where node, pnpm${dockerPath ? " and docker" : ""} live).`);
16700
16964
  }
@@ -16716,9 +16980,9 @@ routes:
16716
16980
  ${routeBlock}`;
16717
16981
  }
16718
16982
  function writeNewConfig(path, text2) {
16719
- mkdirSync26(dirname20(path), { recursive: true });
16983
+ mkdirSync27(dirname21(path), { recursive: true });
16720
16984
  try {
16721
- writeFileSync29(path, text2, { mode: 384, flag: "wx" });
16985
+ writeFileSync30(path, text2, { mode: 384, flag: "wx" });
16722
16986
  return true;
16723
16987
  } catch (e) {
16724
16988
  if (e.code === "EEXIST") return false;
@@ -16752,7 +17016,7 @@ Wrote ${o.configPath}.
16752
17016
  // src/connect-json.ts
16753
17017
  import { accessSync as accessSync4, constants as constants4 } from "fs";
16754
17018
  import { homedir as homedir10 } from "os";
16755
- import { dirname as dirname21, join as join40 } from "path";
17019
+ import { dirname as dirname22, join as join42 } from "path";
16756
17020
  import { delimiter as delimiter2 } from "path";
16757
17021
  var CHOICE_EXIT_CODE = 3;
16758
17022
  function createEmitter(json2, write) {
@@ -16791,12 +17055,12 @@ function agentBinSearchDirs(home, pathEnv, extraPath) {
16791
17055
  const dirs = [
16792
17056
  ...pathEnv.split(delimiter2),
16793
17057
  ...(extraPath ?? "").split(delimiter2),
16794
- join40(home, ".local/bin"),
16795
- join40(home, ".claude/local"),
17058
+ join42(home, ".local/bin"),
17059
+ join42(home, ".claude/local"),
16796
17060
  "/opt/homebrew/bin",
16797
17061
  "/usr/local/bin",
16798
- join40(home, ".npm-global/bin"),
16799
- join40(home, ".volta/bin")
17062
+ join42(home, ".npm-global/bin"),
17063
+ join42(home, ".volta/bin")
16800
17064
  ];
16801
17065
  return [...new Set(dirs.filter(Boolean))];
16802
17066
  }
@@ -16818,7 +17082,7 @@ function flagWizardAnswers(f, env) {
16818
17082
  const agentBin = f.agentBin ?? found?.bin;
16819
17083
  const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
16820
17084
  const dockerPath = f.hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
16821
- const extraPath = f.extraPath ?? deriveExtraPath([dirname21(env.nodePath), pnpmPath ? dirname21(pnpmPath) : void 0, dockerPath ? dirname21(dockerPath) : void 0]);
17085
+ const extraPath = f.extraPath ?? deriveExtraPath([dirname22(env.nodePath), pnpmPath ? dirname22(pnpmPath) : void 0, dockerPath ? dirname22(dockerPath) : void 0]);
16822
17086
  return {
16823
17087
  answers: {
16824
17088
  name: f.shipName?.trim() || env.hostname,
@@ -16832,8 +17096,8 @@ function flagWizardAnswers(f, env) {
16832
17096
  }
16833
17097
 
16834
17098
  // src/release-run.ts
16835
- import { readFileSync as readFileSync34, writeFileSync as writeFileSync30, existsSync as existsSync37, copyFileSync, mkdirSync as mkdirSync27, mkdtempSync as mkdtempSync3, rmSync as rmSync18 } from "fs";
16836
- import { dirname as dirname22, join as join41 } from "path";
17099
+ import { readFileSync as readFileSync36, writeFileSync as writeFileSync31, existsSync as existsSync38, copyFileSync, mkdirSync as mkdirSync28, mkdtempSync as mkdtempSync3, rmSync as rmSync19 } from "fs";
17100
+ import { dirname as dirname23, join as join43 } from "path";
16837
17101
  import { tmpdir as tmpdir5 } from "os";
16838
17102
 
16839
17103
  // src/release.ts
@@ -17574,13 +17838,13 @@ async function bump(o, size) {
17574
17838
  }
17575
17839
  return printed;
17576
17840
  }
17577
- const files = o.repo.release.versionFiles.map((f) => join41(o.cwd, f));
17841
+ const files = o.repo.release.versionFiles.map((f) => join43(o.cwd, f));
17578
17842
  const first = files[0];
17579
- if (!existsSync37(first)) {
17843
+ if (!existsSync38(first)) {
17580
17844
  o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
17581
17845
  return null;
17582
17846
  }
17583
- const current = JSON.parse(readFileSync34(first, "utf8")).version;
17847
+ const current = JSON.parse(readFileSync36(first, "utf8")).version;
17584
17848
  if (!current) {
17585
17849
  o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
17586
17850
  return null;
@@ -17593,27 +17857,27 @@ async function bump(o, size) {
17593
17857
  return version;
17594
17858
  }
17595
17859
  for (const f of files) {
17596
- const src = readFileSync34(f, "utf8");
17860
+ const src = readFileSync36(f, "utf8");
17597
17861
  const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
17598
17862
  if (out === src) {
17599
17863
  o.emit.error(`no version field in ${f}`);
17600
17864
  return null;
17601
17865
  }
17602
- writeFileSync30(f, out);
17866
+ writeFileSync31(f, out);
17603
17867
  }
17604
17868
  return version;
17605
17869
  }
17606
17870
  function writeChangelog(o, version, entries) {
17607
17871
  const file = o.repo.release.changelog;
17608
17872
  if (!file || entries.length === 0) return;
17609
- const path = join41(o.cwd, file);
17873
+ const path = join43(o.cwd, file);
17610
17874
  const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
17611
17875
  if (o.dryRun) {
17612
17876
  o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
17613
17877
  return;
17614
17878
  }
17615
- const existing = existsSync37(path) ? readFileSync34(path, "utf8") : "# Changelog\n";
17616
- writeFileSync30(path, insertChangelogSection(existing, section));
17879
+ const existing = existsSync38(path) ? readFileSync36(path, "utf8") : "# Changelog\n";
17880
+ writeFileSync31(path, insertChangelogSection(existing, section));
17617
17881
  }
17618
17882
  async function confirm(o, expected) {
17619
17883
  if (!o.repo.hooks.released) return void 0;
@@ -17696,7 +17960,7 @@ function closeReleaseCheckout(cwd, root, dir, branch, run3 = gitOk) {
17696
17960
  run3(cwd, ["worktree", "remove", "--force", dir]);
17697
17961
  } finally {
17698
17962
  try {
17699
- rmSync18(root, { recursive: true, force: true });
17963
+ rmSync19(root, { recursive: true, force: true });
17700
17964
  } finally {
17701
17965
  run3(cwd, ["worktree", "prune"]);
17702
17966
  run3(cwd, ["branch", "-D", branch]);
@@ -17706,8 +17970,8 @@ function closeReleaseCheckout(cwd, root, dir, branch, run3 = gitOk) {
17706
17970
  function openReleaseCheckout(o) {
17707
17971
  const { remote, base } = o.repo.branch;
17708
17972
  const branch = `crew/release-${Date.now()}`;
17709
- const root = mkdtempSync3(join41(tmpdir5(), "crew-release-"));
17710
- const dir = join41(root, "checkout");
17973
+ const root = mkdtempSync3(join43(tmpdir5(), "crew-release-"));
17974
+ const dir = join43(root, "checkout");
17711
17975
  o.emit.emit(`release checkout: ${dir}`, { step: "release" });
17712
17976
  const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
17713
17977
  try {
@@ -17717,10 +17981,10 @@ function openReleaseCheckout(o) {
17717
17981
  throw e;
17718
17982
  }
17719
17983
  for (const f of o.repo.worktrees.copy) {
17720
- const src = join41(o.cwd, f);
17721
- if (!existsSync37(src)) continue;
17722
- mkdirSync27(dirname22(join41(dir, f)), { recursive: true });
17723
- copyFileSync(src, join41(dir, f));
17984
+ const src = join43(o.cwd, f);
17985
+ if (!existsSync38(src)) continue;
17986
+ mkdirSync28(dirname23(join43(dir, f)), { recursive: true });
17987
+ copyFileSync(src, join43(dir, f));
17724
17988
  }
17725
17989
  return { dir, branch, close };
17726
17990
  }
@@ -18310,49 +18574,6 @@ async function mergeAndRelease(o, decision, fresh, releaseBranch, isolated = fal
18310
18574
  return { merged, conflicts, unbuildable, version, tag, deployed, integrated, confirmed, decision };
18311
18575
  }
18312
18576
 
18313
- // src/repo-map.ts
18314
- var CHARS_PER_TOKEN = 4;
18315
- var MAP_TIMEOUT_MS = 6e4;
18316
- function budgetMap(text2, tokenBudget) {
18317
- const body = text2.replace(/\s+$/, "");
18318
- const limit = tokenBudget * CHARS_PER_TOKEN;
18319
- if (body.length <= limit) return body;
18320
- const lines2 = body.split("\n");
18321
- const noteFor = (kept2) => `\u2026 ${lines2.length - kept2} of ${lines2.length} lines cut to fit ship.mapTokenBudget (${tokenBudget} tokens)`;
18322
- let kept = lines2.length;
18323
- let size = body.length;
18324
- while (kept > 0 && size + 1 + noteFor(kept).length > limit) {
18325
- kept--;
18326
- size -= lines2[kept].length + 1;
18327
- }
18328
- return [...lines2.slice(0, kept), noteFor(kept)].join("\n");
18329
- }
18330
- function loadRepoMap(repo, tokenBudget, shellFallback) {
18331
- const script = repo.config.hooks.map;
18332
- if (!script) return Promise.resolve({ skipped: "" });
18333
- return runScript(script, {
18334
- cwd: repo.dir,
18335
- shell: resolveShell(repo.config.shell ?? shellFallback),
18336
- env: { CREW_REPO: repo.name },
18337
- timeoutMs: MAP_TIMEOUT_MS
18338
- }).then(
18339
- (r) => {
18340
- if (r.code !== 0) return { skipped: `the \`map\` hook for ${repo.name} exited ${r.code}, so the brief has no repository map` };
18341
- if (!r.stdout.trim()) return { skipped: `the \`map\` hook for ${repo.name} printed nothing, so the brief has no repository map` };
18342
- return { text: budgetMap(r.stdout, tokenBudget) };
18343
- },
18344
- (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` })
18345
- );
18346
- }
18347
- async function withRepoMaps(repos, tokenBudget, shellFallback) {
18348
- return Promise.all(repos.map(async (r) => {
18349
- if (!r.config.hooks.map) return r;
18350
- const out = await loadRepoMap(r, tokenBudget, shellFallback);
18351
- if ("skipped" in out) return out.skipped ? { ...r, mapSkipped: out.skipped } : r;
18352
- return { ...r, map: out.text };
18353
- }));
18354
- }
18355
-
18356
18577
  // src/failure-alert.ts
18357
18578
  var FAILURE_ALERT_MARKER = calloutHeader("CAUTION", "Failure Alert");
18358
18579
  function findOpenFailureAlert(tickets, headline) {
@@ -18742,7 +18963,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
18742
18963
  }
18743
18964
 
18744
18965
  // src/watch.ts
18745
- import { watch as watchFile, existsSync as existsSync38, statSync as statSync11, openSync as openSync5, readSync as readSync2, closeSync as closeSync5 } from "fs";
18966
+ import { watch as watchFile, existsSync as existsSync39, statSync as statSync11, openSync as openSync5, readSync as readSync2, closeSync as closeSync5 } from "fs";
18746
18967
  var E = "\x1B[";
18747
18968
  var dim = (s) => `${E}2m${s}${E}0m`;
18748
18969
  var bold = (s) => `${E}1m${s}${E}0m`;
@@ -18843,7 +19064,7 @@ function renderHeaders(states, now = Date.now()) {
18843
19064
  return states.map((s) => renderHeader(s, now)).join("");
18844
19065
  }
18845
19066
  function readFrom(file, from) {
18846
- if (!existsSync38(file)) return { events: [], offset: from };
19067
+ if (!existsSync39(file)) return { events: [], offset: from };
18847
19068
  const size = statSync11(file).size;
18848
19069
  if (size < from) return readFrom(file, 0);
18849
19070
  if (size === from) return { events: [], offset: from };
@@ -19086,8 +19307,8 @@ function nextRoles(decision, ran) {
19086
19307
  }
19087
19308
 
19088
19309
  // src/ship-name.ts
19089
- import { existsSync as existsSync39, mkdirSync as mkdirSync28, readFileSync as readFileSync35, renameSync as renameSync5, writeFileSync as writeFileSync31 } from "fs";
19090
- import { dirname as dirname23 } from "path";
19310
+ import { existsSync as existsSync40, mkdirSync as mkdirSync29, readFileSync as readFileSync37, renameSync as renameSync5, writeFileSync as writeFileSync32 } from "fs";
19311
+ import { dirname as dirname24 } from "path";
19091
19312
  import { parseDocument as parseDocument5 } from "yaml";
19092
19313
  var ShipNameError = class extends Error {
19093
19314
  };
@@ -19148,34 +19369,34 @@ async function syncRouteName(i) {
19148
19369
  return { status: "ok", ...base, action, name: i.local };
19149
19370
  }
19150
19371
  function readSyncedName(resolvedFile) {
19151
- if (!existsSync39(resolvedFile)) return void 0;
19372
+ if (!existsSync40(resolvedFile)) return void 0;
19152
19373
  try {
19153
- const v = JSON.parse(readFileSync35(resolvedFile, "utf8")).shipName;
19374
+ const v = JSON.parse(readFileSync37(resolvedFile, "utf8")).shipName;
19154
19375
  return typeof v === "string" ? v : void 0;
19155
19376
  } catch {
19156
19377
  return void 0;
19157
19378
  }
19158
19379
  }
19159
19380
  function writeSyncedName(resolvedFile, name) {
19160
- if (!existsSync39(resolvedFile)) return;
19381
+ if (!existsSync40(resolvedFile)) return;
19161
19382
  let raw;
19162
19383
  try {
19163
- raw = JSON.parse(readFileSync35(resolvedFile, "utf8"));
19384
+ raw = JSON.parse(readFileSync37(resolvedFile, "utf8"));
19164
19385
  } catch {
19165
19386
  return;
19166
19387
  }
19167
19388
  if (raw.shipName === name) return;
19168
19389
  raw.shipName = name;
19169
- mkdirSync28(dirname23(resolvedFile), { recursive: true });
19390
+ mkdirSync29(dirname24(resolvedFile), { recursive: true });
19170
19391
  const tmp = `${resolvedFile}.tmp${process.pid}`;
19171
- writeFileSync31(tmp, `${JSON.stringify(raw, null, 2)}
19392
+ writeFileSync32(tmp, `${JSON.stringify(raw, null, 2)}
19172
19393
  `);
19173
19394
  renameSync5(tmp, resolvedFile);
19174
19395
  }
19175
19396
  async function reconcileShipName(i) {
19176
19397
  let local = i.ship.name;
19177
19398
  try {
19178
- local = shipNameOfYaml(readFileSync35(i.configFile, "utf8")) ?? local;
19399
+ local = shipNameOfYaml(readFileSync37(i.configFile, "utf8")) ?? local;
19179
19400
  } catch {
19180
19401
  }
19181
19402
  const resolvedFile = resolvedPathFor(i.stateDir, i.route);
@@ -19199,8 +19420,8 @@ async function reconcileShipName(i) {
19199
19420
  }
19200
19421
  if (result.status !== "ok") return result;
19201
19422
  if (result.action === "adopt" || result.action === "row-wins") {
19202
- const text2 = readFileSync35(i.configFile, "utf8");
19203
- writeFileSync31(i.configFile, setShipNameInYaml(text2, result.name));
19423
+ const text2 = readFileSync37(i.configFile, "utf8");
19424
+ writeFileSync32(i.configFile, setShipNameInYaml(text2, result.name));
19204
19425
  i.ship.name = result.name;
19205
19426
  if (result.action === "row-wins") {
19206
19427
  i.emit?.warn(
@@ -19859,8 +20080,8 @@ function describeCapability(i) {
19859
20080
  }
19860
20081
 
19861
20082
  // src/agents-sync-run.ts
19862
- import { existsSync as existsSync40, mkdirSync as mkdirSync29, readFileSync as readFileSync36, writeFileSync as writeFileSync32 } from "fs";
19863
- import { dirname as dirname24 } from "path";
20083
+ import { existsSync as existsSync41, mkdirSync as mkdirSync30, readFileSync as readFileSync38, writeFileSync as writeFileSync33 } from "fs";
20084
+ import { dirname as dirname25 } from "path";
19864
20085
  async function runAgentsSync(target, o) {
19865
20086
  const dryRun = o.dryRun ?? false;
19866
20087
  const force = o.force ?? false;
@@ -19893,12 +20114,12 @@ async function runAgentsSync(target, o) {
19893
20114
  for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
19894
20115
  if (!dryRun) {
19895
20116
  const resolvedPath = resolvedPathFor(o.stateDir, target.route);
19896
- const raw = existsSync40(resolvedPath) ? JSON.parse(readFileSync36(resolvedPath, "utf8")) : {};
20117
+ const raw = existsSync41(resolvedPath) ? JSON.parse(readFileSync38(resolvedPath, "utf8")) : {};
19897
20118
  raw.agentPersonas = personaResult.agentPersonas;
19898
20119
  if (skillResult) raw.agentSkills = skillResult.agentSkills;
19899
20120
  if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
19900
- mkdirSync29(dirname24(resolvedPath), { recursive: true });
19901
- writeFileSync32(resolvedPath, `${JSON.stringify(raw, null, 2)}
20121
+ mkdirSync30(dirname25(resolvedPath), { recursive: true });
20122
+ writeFileSync33(resolvedPath, `${JSON.stringify(raw, null, 2)}
19902
20123
  `);
19903
20124
  if (target.resolved) {
19904
20125
  target.resolved.agentPersonas = personaResult.agentPersonas;
@@ -20015,7 +20236,7 @@ async function showLogEntry(route2, entryId, opts = {}) {
20015
20236
 
20016
20237
  // src/repos-check.ts
20017
20238
  import { spawnSync as spawnSync10 } from "child_process";
20018
- import { readFileSync as readFileSync37 } from "fs";
20239
+ import { readFileSync as readFileSync39 } from "fs";
20019
20240
  import { basename as basename11 } from "path";
20020
20241
  var POSIX_SHELL = /^(?:ba|z|da|k|a|mk)?sh$|^fish$/;
20021
20242
  function checkHookSyntax(script, shell) {
@@ -20049,14 +20270,14 @@ function renderSamples(cfg, dir) {
20049
20270
  });
20050
20271
  }
20051
20272
  function checkRepoSpec(dir, opts = {}) {
20052
- const empty = { parsed: null, effective: null, readiness: [], hooks: [], shadowed: [], samples: [] };
20273
+ const empty = { parsed: null, effective: null, readiness: [], hooks: [], shadowed: [], mapFile: null, samples: [] };
20053
20274
  const file = findRepoConfig(dir);
20054
20275
  if (!file) {
20055
20276
  return { ...empty, file: null, error: `no .crew.yaml, .crew.yml or .crew.json in ${dir}`, exitCode: 1 };
20056
20277
  }
20057
20278
  let parsed;
20058
20279
  try {
20059
- parsed = parseRepoConfig(readFileSync37(file, "utf8"), file);
20280
+ parsed = parseRepoConfig(readFileSync39(file, "utf8"), file);
20060
20281
  } catch (e) {
20061
20282
  if (!(e instanceof RepoConfigError)) throw e;
20062
20283
  return { ...empty, file, error: e.message, exitCode: 1 };
@@ -20067,14 +20288,16 @@ function checkRepoSpec(dir, opts = {}) {
20067
20288
  const engine = effective.hooks.setup ? containerEngineUse(effective.hooks.setup) : null;
20068
20289
  if (engine) readiness.push(`hooks.setup runs \`${engine}\`, and a sandbox has no container engine \u2014 start the service as a process instead (REPO_SPEC.md, "Services inside the sandbox")`);
20069
20290
  }
20070
- const hooks = Object.keys(effective.hooks).map((name) => ({
20291
+ const mapPath = mapFilePath(effective.hooks.map);
20292
+ const mapFile = mapPath ? { ...inspectMapFile(dir, mapPath), budget: opts.ship?.mapTokenBudget ?? DEFAULT_MAP_TOKEN_BUDGET } : null;
20293
+ const hooks = Object.keys(effective.hooks).filter((name) => !(name === "map" && mapPath)).map((name) => ({
20071
20294
  name,
20072
20295
  label: hookLabel(effective, name),
20073
20296
  // CREW-2914: a flavor name is not shell, so it is not syntax-checked.
20074
20297
  syntax: name === "intel" && intelFlavor(effective.hooks.intel) ? { status: "ok" } : checkHookSyntax(effective.hooks[name], effective.shell),
20075
20298
  ...name === "intel" && intelFlavor(effective.hooks.intel) ? { flavor: intelFlavor(effective.hooks.intel) } : {}
20076
20299
  }));
20077
- const failed = hooks.some((h) => h.syntax.status === "error");
20300
+ const failed = hooks.some((h) => h.syntax.status === "error") || mapFile !== null && !mapFile.exists;
20078
20301
  return {
20079
20302
  file,
20080
20303
  parsed,
@@ -20082,6 +20305,7 @@ function checkRepoSpec(dir, opts = {}) {
20082
20305
  readiness,
20083
20306
  hooks,
20084
20307
  shadowed: effective.shadowed,
20308
+ mapFile,
20085
20309
  samples: renderSamples(effective, dir),
20086
20310
  error: null,
20087
20311
  exitCode: failed || opts.strict && readiness.length > 0 ? 1 : 0
@@ -20101,6 +20325,10 @@ function renderRepoCheck(r) {
20101
20325
  ` branch.push ${e.branch.push}`
20102
20326
  ];
20103
20327
  for (const s of r.samples) lines2.push(` ${s.case}: branch ${s.branch}, worktree ../${s.worktree}`);
20328
+ if (r.mapFile) {
20329
+ const m = r.mapFile;
20330
+ lines2.push(m.exists ? ` map: file ${m.path} (${m.lines} lines, about ${m.tokens} tokens; ship budget ${m.budget})` : ` map: file ${m.path} NOT FOUND \u2014 the brief will carry an absence note`);
20331
+ }
20104
20332
  lines2.push(r.hooks.length ? " hooks" : " hooks none");
20105
20333
  for (const h of r.hooks) {
20106
20334
  const named = h.label !== e.hooks[h.name] ? ` (${h.label})` : "";
@@ -20513,7 +20741,7 @@ ${run3.err}` });
20513
20741
  }
20514
20742
 
20515
20743
  // src/sample-ticket.ts
20516
- import { basename as basename12, dirname as dirname25, join as join42 } from "path";
20744
+ import { basename as basename12, dirname as dirname26, join as join44 } from "path";
20517
20745
  var SAMPLE_REPO = "crew-sample";
20518
20746
  var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
20519
20747
  var SAMPLE_MARKER = "sample: true";
@@ -20536,8 +20764,8 @@ has something safe to work on: its test and build hooks always pass, it has no
20536
20764
  remote, and \`crew sample-ticket --remove\` deletes it.
20537
20765
  `;
20538
20766
  function sampleDir(existing, reposBase, workspace) {
20539
- if (existing.length > 0) return join42(dirname25(existing[0]), SAMPLE_REPO);
20540
- return join42(reposBase, workspace, SAMPLE_REPO);
20767
+ if (existing.length > 0) return join44(dirname26(existing[0]), SAMPLE_REPO);
20768
+ return join44(reposBase, workspace, SAMPLE_REPO);
20541
20769
  }
20542
20770
  var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
20543
20771
  function sampleDescription() {
@@ -20581,8 +20809,8 @@ function planSampleTicket(i) {
20581
20809
  }
20582
20810
 
20583
20811
  // src/worktree-sweep.ts
20584
- import { existsSync as existsSync41, readFileSync as readFileSync38, readdirSync as readdirSync14 } from "fs";
20585
- import { basename as basename13, dirname as dirname26, join as join43, resolve as resolve5 } from "path";
20812
+ import { existsSync as existsSync42, readFileSync as readFileSync40, readdirSync as readdirSync14 } from "fs";
20813
+ import { basename as basename13, dirname as dirname27, join as join45, resolve as resolve5 } from "path";
20586
20814
  function planWorktreeSweep(target, tickets, contract) {
20587
20815
  const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
20588
20816
  const verified = contract.statuses.verified;
@@ -20646,7 +20874,7 @@ function worktreeTicketNumbers(target) {
20646
20874
  return [...nums];
20647
20875
  }
20648
20876
  function siblingWorktreeDirs(target) {
20649
- const parent = dirname26(target.dir.replace(/[/\\]+$/, ""));
20877
+ const parent = dirname27(target.dir.replace(/[/\\]+$/, ""));
20650
20878
  const stem = `${basename13(target.dir.replace(/[/\\]+$/, ""))}-`;
20651
20879
  const registered = new Set(worktrees(target.dir).map((w) => resolve5(w.path)));
20652
20880
  let names;
@@ -20659,7 +20887,7 @@ function siblingWorktreeDirs(target) {
20659
20887
  for (const name of names) {
20660
20888
  if (!name.startsWith(stem)) continue;
20661
20889
  const m = DIR_NUMBER_SUFFIXED.exec(name);
20662
- const path = join43(parent, name);
20890
+ const path = join45(parent, name);
20663
20891
  if (m && !registered.has(resolve5(path))) out.push({ path, number: m[1] });
20664
20892
  }
20665
20893
  return out;
@@ -20694,7 +20922,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
20694
20922
  let removed = 0;
20695
20923
  let keptBranches = 0;
20696
20924
  for (const a of actions) {
20697
- if (!dryRun && base && !a.keepBranch && existsSync41(a.path) && isHandBackMerge(a.path, base)) {
20925
+ if (!dryRun && base && !a.keepBranch && existsSync42(a.path) && isHandBackMerge(a.path, base)) {
20698
20926
  if (gitOk(a.path, ["merge", "--abort"]) !== null) {
20699
20927
  log.emit(
20700
20928
  `abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
@@ -20702,7 +20930,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
20702
20930
  );
20703
20931
  }
20704
20932
  }
20705
- if (!dryRun && existsSync41(a.path)) {
20933
+ if (!dryRun && existsSync42(a.path)) {
20706
20934
  let dirty = false;
20707
20935
  try {
20708
20936
  dirty = status(a.path).length > 0;
@@ -20713,7 +20941,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
20713
20941
  continue;
20714
20942
  }
20715
20943
  }
20716
- if (!dryRun && a.requirePushed && a.branch && existsSync41(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
20944
+ if (!dryRun && a.requirePushed && a.branch && existsSync42(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
20717
20945
  refuse2(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
20718
20946
  continue;
20719
20947
  }
@@ -20762,7 +20990,7 @@ function isHandBackMerge(path, base) {
20762
20990
  if (!msgPath) return false;
20763
20991
  let first;
20764
20992
  try {
20765
- first = readFileSync38(resolve5(path, msgPath), "utf8").split("\n")[0] ?? "";
20993
+ first = readFileSync40(resolve5(path, msgPath), "utf8").split("\n")[0] ?? "";
20766
20994
  } catch {
20767
20995
  return false;
20768
20996
  }
@@ -20867,14 +21095,14 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
20867
21095
  }
20868
21096
 
20869
21097
  // src/stream-sweep.ts
20870
- import { existsSync as existsSync42, readdirSync as readdirSync15, rmSync as rmSync19, statSync as statSync12 } from "fs";
20871
- import { join as join44 } from "path";
21098
+ import { existsSync as existsSync43, readdirSync as readdirSync15, rmSync as rmSync20, statSync as statSync12 } from "fs";
21099
+ import { join as join46 } from "path";
20872
21100
  var DAY_MS2 = 24 * 60 * 60 * 1e3;
20873
21101
  function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
20874
- const dir = join44(stateDir, "streams");
20875
- if (!existsSync42(dir)) return [];
21102
+ const dir = join46(stateDir, "streams");
21103
+ if (!existsSync43(dir)) return [];
20876
21104
  const cutoff = now - retentionDays * DAY_MS2;
20877
- return readdirSync15(dir).map((name) => join44(dir, name)).filter((path) => {
21105
+ return readdirSync15(dir).map((name) => join46(dir, name)).filter((path) => {
20878
21106
  try {
20879
21107
  return statSync12(path).mtimeMs < cutoff;
20880
21108
  } catch {
@@ -20890,7 +21118,7 @@ function applyStreamSweep(paths, dryRun, log) {
20890
21118
  continue;
20891
21119
  }
20892
21120
  try {
20893
- rmSync19(path, { force: true });
21121
+ rmSync20(path, { force: true });
20894
21122
  removed++;
20895
21123
  log.emit(`removed stream artifact ${path}`, { step: "worktree" });
20896
21124
  } catch (e) {
@@ -20900,9 +21128,9 @@ function applyStreamSweep(paths, dryRun, log) {
20900
21128
  return removed;
20901
21129
  }
20902
21130
  function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
20903
- if (!existsSync42(stateDir)) return [];
21131
+ if (!existsSync43(stateDir)) return [];
20904
21132
  const cutoff = now - retentionDays * DAY_MS2;
20905
- return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join44(stateDir, name)).filter((path) => {
21133
+ return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join46(stateDir, name)).filter((path) => {
20906
21134
  try {
20907
21135
  return statSync12(path).mtimeMs < cutoff;
20908
21136
  } catch {
@@ -20912,12 +21140,12 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
20912
21140
  }
20913
21141
 
20914
21142
  // src/cli.ts
20915
- import { realpathSync as realpathSync3, readFileSync as readFileSync44, existsSync as existsSync49, mkdirSync as mkdirSync37, writeFileSync as writeFileSync37, rmSync as rmSync24, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync6, statSync as statSync16, readdirSync as readdirSync20 } from "fs";
21143
+ import { realpathSync as realpathSync3, readFileSync as readFileSync46, existsSync as existsSync50, mkdirSync as mkdirSync38, writeFileSync as writeFileSync38, rmSync as rmSync25, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync6, statSync as statSync16, readdirSync as readdirSync20 } from "fs";
20916
21144
  import { createInterface } from "readline/promises";
20917
21145
 
20918
21146
  // src/periscope-cmd.ts
20919
- import { existsSync as existsSync43, mkdirSync as mkdirSync30, readFileSync as readFileSync39, rmSync as rmSync20, writeFileSync as writeFileSync33 } from "fs";
20920
- import { join as join45 } from "path";
21147
+ import { existsSync as existsSync44, mkdirSync as mkdirSync31, readFileSync as readFileSync41, rmSync as rmSync21, writeFileSync as writeFileSync34 } from "fs";
21148
+ import { join as join47 } from "path";
20921
21149
  var PeriscopeError = class extends Error {
20922
21150
  };
20923
21151
  var LivePeriscopeError = class extends PeriscopeError {
@@ -20927,22 +21155,22 @@ var LivePeriscopeError = class extends PeriscopeError {
20927
21155
  this.data = { refused: "live_periscope_exists", ...live };
20928
21156
  }
20929
21157
  };
20930
- var priorDir = (stateDir) => join45(stateDir, "periscope-priors");
20931
- var priorPath = (stateDir, ticketRecordId) => join45(priorDir(stateDir), `${ticketRecordId}.json`);
21158
+ var priorDir = (stateDir) => join47(stateDir, "periscope-priors");
21159
+ var priorPath = (stateDir, ticketRecordId) => join47(priorDir(stateDir), `${ticketRecordId}.json`);
20932
21160
  function removePeriscopePrior(stateDir, ticketRecordId) {
20933
- rmSync20(priorPath(stateDir, ticketRecordId), { force: true });
21161
+ rmSync21(priorPath(stateDir, ticketRecordId), { force: true });
20934
21162
  }
20935
21163
  function writePeriscopePrior(stateDir, ticketRecordId, prior) {
20936
21164
  try {
20937
- mkdirSync30(priorDir(stateDir), { recursive: true });
20938
- writeFileSync33(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
21165
+ mkdirSync31(priorDir(stateDir), { recursive: true });
21166
+ writeFileSync34(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
20939
21167
  } catch {
20940
21168
  }
20941
21169
  }
20942
21170
  function readPeriscopePrior(stateDir, ticketRecordId) {
20943
21171
  try {
20944
21172
  const p = priorPath(stateDir, ticketRecordId);
20945
- return existsSync43(p) ? JSON.parse(readFileSync39(p, "utf8")) : void 0;
21173
+ return existsSync44(p) ? JSON.parse(readFileSync41(p, "utf8")) : void 0;
20946
21174
  } catch {
20947
21175
  return void 0;
20948
21176
  }
@@ -21396,7 +21624,7 @@ ${tail2(preview.output)}`);
21396
21624
  // src/periscope-idle.ts
21397
21625
  import { execFileSync as execFileSync12 } from "child_process";
21398
21626
  import { statSync as statSync13 } from "fs";
21399
- import { join as join46 } from "path";
21627
+ import { join as join48 } from "path";
21400
21628
  var MAX_SCANNED_FILES = 2e4;
21401
21629
  function newestWorktreeMtime(root) {
21402
21630
  const a = newestTrackedMtime(root);
@@ -21421,7 +21649,7 @@ function newestTrackedMtime(root) {
21421
21649
  if (!rel) continue;
21422
21650
  if (++scanned > MAX_SCANNED_FILES) break;
21423
21651
  try {
21424
- const m = statSync13(join46(root, rel)).mtimeMs;
21652
+ const m = statSync13(join48(root, rel)).mtimeMs;
21425
21653
  if (newest === null || m > newest) newest = m;
21426
21654
  } catch {
21427
21655
  }
@@ -21497,18 +21725,18 @@ import { dirname as dirOf2, resolve as resolvePath } from "path";
21497
21725
 
21498
21726
  // src/ssh-keys.ts
21499
21727
  import { execFileSync as execFileSync13 } from "child_process";
21500
- import { existsSync as existsSync44, mkdirSync as mkdirSync31, readFileSync as readFileSync40, chmodSync as chmodSync3 } from "fs";
21501
- import { dirname as dirname27, join as join47 } from "path";
21728
+ import { existsSync as existsSync45, mkdirSync as mkdirSync32, readFileSync as readFileSync42, chmodSync as chmodSync3 } from "fs";
21729
+ import { dirname as dirname28, join as join49 } from "path";
21502
21730
  var SshKeyError = class extends Error {
21503
21731
  };
21504
21732
  function shipSshKeyPathFor(stateDir) {
21505
- return join47(stateDir, "ssh", "id_ed25519");
21733
+ return join49(stateDir, "ssh", "id_ed25519");
21506
21734
  }
21507
21735
  function ensureShipSshKeypair(stateDir) {
21508
21736
  const privateKeyPath = shipSshKeyPathFor(stateDir);
21509
21737
  const publicKeyPath = `${privateKeyPath}.pub`;
21510
- if (!existsSync44(privateKeyPath)) {
21511
- mkdirSync31(dirname27(privateKeyPath), { recursive: true });
21738
+ if (!existsSync45(privateKeyPath)) {
21739
+ mkdirSync32(dirname28(privateKeyPath), { recursive: true });
21512
21740
  try {
21513
21741
  execFileSync13("ssh-keygen", [
21514
21742
  "-t",
@@ -21528,10 +21756,10 @@ function ensureShipSshKeypair(stateDir) {
21528
21756
  }
21529
21757
  chmodSync3(privateKeyPath, 384);
21530
21758
  }
21531
- if (!existsSync44(publicKeyPath)) {
21759
+ if (!existsSync45(publicKeyPath)) {
21532
21760
  throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
21533
21761
  }
21534
- const publicKey = readFileSync40(publicKeyPath, "utf8").trim();
21762
+ const publicKey = readFileSync42(publicKeyPath, "utf8").trim();
21535
21763
  return { privateKeyPath, publicKeyPath, publicKey };
21536
21764
  }
21537
21765
  function sshKeygenAvailable() {
@@ -21695,12 +21923,12 @@ async function unregisterEndpoint(workspaceId, info, d) {
21695
21923
  import { createServer as createHttpServer } from "http";
21696
21924
  import { spawn as spawn9 } from "child_process";
21697
21925
  import { randomBytes } from "crypto";
21698
- import { existsSync as existsSync45, mkdirSync as mkdirSync33, readFileSync as readFileSync41, writeFileSync as writeFileSync34, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
21699
- import { join as join48 } from "path";
21926
+ import { existsSync as existsSync46, mkdirSync as mkdirSync34, readFileSync as readFileSync43, writeFileSync as writeFileSync35, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
21927
+ import { join as join50 } from "path";
21700
21928
 
21701
21929
  // src/passenger-mirror.ts
21702
- import { mkdirSync as mkdirSync32 } from "fs";
21703
- import { dirname as dirname28 } from "path";
21930
+ import { mkdirSync as mkdirSync33 } from "fs";
21931
+ import { dirname as dirname29 } from "path";
21704
21932
  function mirrorRepoOf(spec) {
21705
21933
  const fromOrigin = spec.originUrl ? parseRemote(spec.originUrl) : null;
21706
21934
  if (fromOrigin) return fromOrigin;
@@ -21718,9 +21946,9 @@ async function materializeMirrorCheckouts(specs, cacheFor = (s) => new SandboxCa
21718
21946
  continue;
21719
21947
  }
21720
21948
  try {
21721
- const parent = dirname28(spec.dir);
21722
- mkdirSync32(parent, { recursive: true });
21723
- mkdirSync32(spec.dir, { recursive: true });
21949
+ const parent = dirname29(spec.dir);
21950
+ mkdirSync33(parent, { recursive: true });
21951
+ mkdirSync33(spec.dir, { recursive: true });
21724
21952
  const r = await cacheFor(spec).materialize(repo, spec.base, parent, spec.name);
21725
21953
  out.push({ ...base, action: r.changed ? "updated" : "unchanged", detail: r.tip });
21726
21954
  } catch (e) {
@@ -21826,21 +22054,21 @@ var defaultSpawn2 = (cmd, args, env) => {
21826
22054
  return child;
21827
22055
  };
21828
22056
  function syncDaemonStatePath(stateDir, workspaceId) {
21829
- return join48(stateDir, "passengers", `${workspaceId}-sync.json`);
22057
+ return join50(stateDir, "passengers", `${workspaceId}-sync.json`);
21830
22058
  }
21831
22059
  function readPersistedSyncDaemon(stateDir, workspaceId) {
21832
22060
  const path = syncDaemonStatePath(stateDir, workspaceId);
21833
- if (!existsSync45(path)) return void 0;
22061
+ if (!existsSync46(path)) return void 0;
21834
22062
  try {
21835
- return JSON.parse(readFileSync41(path, "utf8"));
22063
+ return JSON.parse(readFileSync43(path, "utf8"));
21836
22064
  } catch {
21837
22065
  return void 0;
21838
22066
  }
21839
22067
  }
21840
22068
  function writePersistedSyncDaemon(stateDir, state) {
21841
22069
  const path = syncDaemonStatePath(stateDir, state.workspaceId);
21842
- mkdirSync33(join48(stateDir, "passengers"), { recursive: true });
21843
- writeFileSync34(path, `${JSON.stringify(state, null, 2)}
22070
+ mkdirSync34(join50(stateDir, "passengers"), { recursive: true });
22071
+ writeFileSync35(path, `${JSON.stringify(state, null, 2)}
21844
22072
  `);
21845
22073
  }
21846
22074
  function removePersistedSyncDaemon(stateDir, workspaceId) {
@@ -21860,8 +22088,8 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
21860
22088
  }
21861
22089
  }
21862
22090
  function readAllPersistedSyncWorkspaceIds(stateDir) {
21863
- const dir = join48(stateDir, "passengers");
21864
- if (!existsSync45(dir)) return [];
22091
+ const dir = join50(stateDir, "passengers");
22092
+ if (!existsSync46(dir)) return [];
21865
22093
  const suffix = "-sync.json";
21866
22094
  return readdirSync16(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
21867
22095
  }
@@ -21919,12 +22147,12 @@ function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
21919
22147
 
21920
22148
  // src/periscope-proxy.ts
21921
22149
  import { spawn as spawn10 } from "child_process";
21922
- import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync34 } from "fs";
22150
+ import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync35 } from "fs";
21923
22151
  import { createServer, request as httpRequest } from "http";
21924
22152
  import { request as httpsRequest } from "https";
21925
22153
  import { connect as netConnect } from "net";
21926
22154
  import { connect as tlsConnect } from "tls";
21927
- import { dirname as dirname29, isAbsolute as isAbsolute4 } from "path";
22155
+ import { dirname as dirname30, isAbsolute as isAbsolute4 } from "path";
21928
22156
  import { brotliDecompressSync, gunzipSync, inflateSync } from "zlib";
21929
22157
 
21930
22158
  // src/periscope-inspector.ts
@@ -22038,7 +22266,7 @@ function parsePointedBody(raw) {
22038
22266
  return entry;
22039
22267
  }
22040
22268
  function appendPointed(file, entry, now = /* @__PURE__ */ new Date()) {
22041
- mkdirSync34(dirname29(file), { recursive: true });
22269
+ mkdirSync35(dirname30(file), { recursive: true });
22042
22270
  appendFileSync3(file, `${JSON.stringify({ ...entry, at: now.toISOString() })}
22043
22271
  `);
22044
22272
  }
@@ -22299,6 +22527,33 @@ async function launchProxy(invocation, worktree, previewUrl, number, o) {
22299
22527
  throw new Error(`the proxy did not start listening on ${o.host}:${port}`);
22300
22528
  }
22301
22529
 
22530
+ // src/intel-worktree.ts
22531
+ async function startWorktreeIntel(d) {
22532
+ const file = attachFilePath(d.stateDir, d.route, d.number);
22533
+ const probe = d.probe ?? probeIntel;
22534
+ let outcome;
22535
+ try {
22536
+ const known = readAttachFile(file);
22537
+ outcome = known ? await probe({ runHook: async () => ({ code: 0, output: known }) }, {}, 3) : { state: "absent" };
22538
+ if (outcome.state !== "ready") {
22539
+ const prepared = await d.prepare(d.worktree);
22540
+ if (prepared && prepared.code !== 0) {
22541
+ outcome = { state: "failed", reason: `setup exited ${prepared.code}` };
22542
+ } else {
22543
+ const env = { CREW_TICKET: d.ticketTag, CREW_BRANCH: d.branch };
22544
+ const ports = await d.runHook("ports", d.worktree, env);
22545
+ if (ports) Object.assign(env, parseEnvLines(ports.output));
22546
+ outcome = await probe({ runHook: (e) => d.runHook("intel", d.worktree, e) }, env, d.startupSeconds);
22547
+ }
22548
+ }
22549
+ if (outcome.state === "ready") writeAttachFile(file, outcome.url);
22550
+ } catch (e) {
22551
+ outcome = { state: "failed", reason: e.message.slice(0, 200) };
22552
+ }
22553
+ const tail3 = outcome.state === "ready" ? " The session's `code` server attaches to it within a second; until it lists these tools it still shows only `code_status`." : "";
22554
+ return `${codeToolsParagraph(outcome)}${tail3}`;
22555
+ }
22556
+
22302
22557
  // src/outage.ts
22303
22558
  var TRANSIENT_BACKOFF_MS = [15e3, 3e4, 6e4];
22304
22559
  var OUTAGE_ESCALATE_AFTER = 5;
@@ -22331,8 +22586,8 @@ function describeCause(err3) {
22331
22586
  }
22332
22587
 
22333
22588
  // src/daemon.ts
22334
- import { existsSync as existsSync46, mkdirSync as mkdirSync35, readFileSync as readFileSync42, readdirSync as readdirSync17, rmSync as rmSync21, statSync as statSync14, writeFileSync as writeFileSync35 } from "fs";
22335
- import { join as join49 } from "path";
22589
+ import { existsSync as existsSync47, mkdirSync as mkdirSync36, readFileSync as readFileSync44, readdirSync as readdirSync17, rmSync as rmSync22, statSync as statSync14, writeFileSync as writeFileSync36 } from "fs";
22590
+ import { join as join51 } from "path";
22336
22591
  import { randomBytes as randomBytes2 } from "crypto";
22337
22592
  var DEFAULT_FLOOR_MS = 5e3;
22338
22593
  var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
@@ -22420,7 +22675,7 @@ function latestMtime(path) {
22420
22675
  if (!stat2.isDirectory()) return 0;
22421
22676
  let latest = stat2.mtimeMs;
22422
22677
  for (const entry of readdirSync17(path, { withFileTypes: true })) {
22423
- latest = Math.max(latest, latestMtime(join49(path, entry.name)));
22678
+ latest = Math.max(latest, latestMtime(join51(path, entry.name)));
22424
22679
  }
22425
22680
  return latest;
22426
22681
  }
@@ -22429,10 +22684,10 @@ function computeTreeSignature(paths) {
22429
22684
  }
22430
22685
  function makeStaleChecker(paths, opts = {}) {
22431
22686
  const initial = computeTreeSignature(paths);
22432
- return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync46(opts.reloadSentinel);
22687
+ return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync47(opts.reloadSentinel);
22433
22688
  }
22434
22689
  function reloadSentinelFor(stateDir) {
22435
- return join49(stateDir, "reload-requested");
22690
+ return join51(stateDir, "reload-requested");
22436
22691
  }
22437
22692
  function readReloadRequestedAt(stateDir) {
22438
22693
  try {
@@ -22442,17 +22697,17 @@ function readReloadRequestedAt(stateDir) {
22442
22697
  }
22443
22698
  }
22444
22699
  function daemonStampFor(stateDir) {
22445
- return join49(stateDir, "daemon.started");
22700
+ return join51(stateDir, "daemon.started");
22446
22701
  }
22447
22702
  function noteDaemonStarted(stateDir, now = Date.now()) {
22448
- mkdirSync35(stateDir, { recursive: true });
22449
- rmSync21(reloadSentinelFor(stateDir), { force: true });
22450
- writeFileSync35(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
22703
+ mkdirSync36(stateDir, { recursive: true });
22704
+ rmSync22(reloadSentinelFor(stateDir), { force: true });
22705
+ writeFileSync36(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
22451
22706
  `);
22452
22707
  }
22453
22708
  function readDaemonStarted(stateDir) {
22454
22709
  try {
22455
- const v = JSON.parse(readFileSync42(daemonStampFor(stateDir), "utf8")).startedAt;
22710
+ const v = JSON.parse(readFileSync44(daemonStampFor(stateDir), "utf8")).startedAt;
22456
22711
  return typeof v === "number" ? v : null;
22457
22712
  } catch {
22458
22713
  return null;
@@ -23018,18 +23273,18 @@ import { createHash as createHash10 } from "crypto";
23018
23273
  import {
23019
23274
  chmodSync as chmodSync4,
23020
23275
  closeSync as closeSync6,
23021
- existsSync as existsSync47,
23276
+ existsSync as existsSync48,
23022
23277
  fstatSync,
23023
23278
  openSync as openSync6,
23024
23279
  readSync as readSync3,
23025
23280
  readdirSync as readdirSync18,
23026
- rmSync as rmSync22,
23281
+ rmSync as rmSync23,
23027
23282
  statSync as statSync15,
23028
23283
  watch
23029
23284
  } from "fs";
23030
23285
  import { createConnection } from "net";
23031
23286
  import { createServer as createServer2 } from "http";
23032
- import { basename as basename14, dirname as dirname30, join as join50 } from "path";
23287
+ import { basename as basename14, dirname as dirname31, join as join52 } from "path";
23033
23288
  import { execFile as execFile3, spawn as spawn12 } from "child_process";
23034
23289
 
23035
23290
  // src/periscope-control.ts
@@ -23258,7 +23513,7 @@ function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
23258
23513
  // src/control-api.ts
23259
23514
  var PAUSABLE = ["dev", "design", "qa"];
23260
23515
  var CONTROL_SOCKET_NAME = "control.sock";
23261
- var controlSocketFor = (stateDir) => join50(stateDir, CONTROL_SOCKET_NAME);
23516
+ var controlSocketFor = (stateDir) => join52(stateDir, CONTROL_SOCKET_NAME);
23262
23517
  var MAX_SOCKET_PATH = 100;
23263
23518
  var MAX_TAIL_BYTES = 1024 * 1024;
23264
23519
  var MAX_WAIT_SECONDS = 30;
@@ -23345,7 +23600,7 @@ function candidates(live, rotated) {
23345
23600
  const out = [live];
23346
23601
  if (!rotated) return out;
23347
23602
  try {
23348
- for (const n of readdirSync18(dirname30(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join50(dirname30(live), n));
23603
+ for (const n of readdirSync18(dirname31(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join52(dirname31(live), n));
23349
23604
  } catch {
23350
23605
  }
23351
23606
  return out;
@@ -23389,7 +23644,7 @@ function lastLines2(path, n) {
23389
23644
  }
23390
23645
  function readTail(live, opts) {
23391
23646
  const max = opts.maxBytes ?? MAX_TAIL_BYTES;
23392
- if (!existsSync47(live)) {
23647
+ if (!existsSync48(live)) {
23393
23648
  return { lines: [], cursor: opts.after ?? encodeCursor({ dev: 0, ino: 0, off: 0 }), reset: false };
23394
23649
  }
23395
23650
  const cur = opts.after ? decodeCursor(opts.after) : null;
@@ -23505,7 +23760,7 @@ async function tailReply(path, q2, rotated, abort) {
23505
23760
  const deadline = Date.now() + Math.min(wait, MAX_WAIT_SECONDS) * 1e3;
23506
23761
  let r = readTail(path, { after, tail: tail3, rotated });
23507
23762
  while (after && !r.lines.length && !r.reset && Date.now() < deadline && !abort.aborted) {
23508
- await waitForChange(dirname30(path), deadline - Date.now(), abort);
23763
+ await waitForChange(dirname31(path), deadline - Date.now(), abort);
23509
23764
  r = readTail(path, { after: r.cursor, rotated });
23510
23765
  }
23511
23766
  return reply2(200, r);
@@ -23545,8 +23800,8 @@ async function route(deps, method, url, req, abort) {
23545
23800
  if (p.startsWith("/streams/") && get2) {
23546
23801
  const run3 = decodeURIComponent(p.slice("/streams/".length));
23547
23802
  if (!RUN_NAME_RE.test(run3) || run3.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run3)) return err2(400, "not a run name");
23548
- const file = join50(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
23549
- if (!existsSync47(file)) return err2(404, "no such run");
23803
+ const file = join52(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
23804
+ if (!existsSync48(file)) return err2(404, "no such run");
23550
23805
  return tailReply(file, url.searchParams, false, abort);
23551
23806
  }
23552
23807
  if ((p === "/pause" || p === "/resume") && post3) {
@@ -23590,7 +23845,7 @@ function send(res, r) {
23590
23845
  res.end(body);
23591
23846
  }
23592
23847
  async function socketIsStale(path) {
23593
- if (!existsSync47(path)) return false;
23848
+ if (!existsSync48(path)) return false;
23594
23849
  return new Promise((resolve8) => {
23595
23850
  const c = createConnection(path);
23596
23851
  c.once("connect", () => {
@@ -23608,8 +23863,8 @@ async function startControlServer(deps) {
23608
23863
  log(`control API not started: socket path is too long for a unix socket (${path})`);
23609
23864
  return null;
23610
23865
  }
23611
- if (await socketIsStale(path)) rmSync22(path, { force: true });
23612
- else if (existsSync47(path)) {
23866
+ if (await socketIsStale(path)) rmSync23(path, { force: true });
23867
+ else if (existsSync48(path)) {
23613
23868
  log(`control API not started: ${path} is in use by another process`);
23614
23869
  return null;
23615
23870
  }
@@ -23655,7 +23910,7 @@ async function startControlServer(deps) {
23655
23910
  path,
23656
23911
  close: () => new Promise((done) => {
23657
23912
  server.close(() => {
23658
- rmSync22(path, { force: true });
23913
+ rmSync23(path, { force: true });
23659
23914
  done();
23660
23915
  });
23661
23916
  })
@@ -23695,57 +23950,57 @@ function crewJson(args, timeoutMs = 6e4) {
23695
23950
 
23696
23951
  // src/periscope-mcp.ts
23697
23952
  import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
23698
- import { basename as basename15, dirname as dirname31, isAbsolute as isAbsolute5, join as join52, relative as relative2, resolve as resolve6, sep } from "path";
23953
+ import { basename as basename15, dirname as dirname32, isAbsolute as isAbsolute5, join as join54, relative as relative2, resolve as resolve6, sep } from "path";
23699
23954
  import { z } from "zod";
23700
23955
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
23701
23956
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
23702
23957
 
23703
23958
  // src/periscope-endpoints.ts
23704
23959
  import { randomBytes as randomBytes3 } from "crypto";
23705
- import { chmodSync as chmodSync5, existsSync as existsSync48, mkdirSync as mkdirSync36, readdirSync as readdirSync19, readFileSync as readFileSync43, rmSync as rmSync23, writeFileSync as writeFileSync36 } from "fs";
23706
- import { join as join51 } from "path";
23707
- var secretDir = (stateDir) => join51(stateDir, "periscope-mcp");
23708
- var secretFile = (stateDir, id) => join51(secretDir(stateDir), `${id}.json`);
23709
- var endpointDir = (stateDir) => join51(stateDir, "periscope-endpoints");
23710
- var endpointFile = (stateDir, id) => join51(endpointDir(stateDir), `${id}.json`);
23960
+ import { chmodSync as chmodSync5, existsSync as existsSync49, mkdirSync as mkdirSync37, readdirSync as readdirSync19, readFileSync as readFileSync45, rmSync as rmSync24, writeFileSync as writeFileSync37 } from "fs";
23961
+ import { join as join53 } from "path";
23962
+ var secretDir = (stateDir) => join53(stateDir, "periscope-mcp");
23963
+ var secretFile = (stateDir, id) => join53(secretDir(stateDir), `${id}.json`);
23964
+ var endpointDir = (stateDir) => join53(stateDir, "periscope-endpoints");
23965
+ var endpointFile = (stateDir, id) => join53(endpointDir(stateDir), `${id}.json`);
23711
23966
  function mintPeriscopeSecret(stateDir, ticketRecordId) {
23712
- mkdirSync36(secretDir(stateDir), { recursive: true, mode: 448 });
23967
+ mkdirSync37(secretDir(stateDir), { recursive: true, mode: 448 });
23713
23968
  const secret = randomBytes3(32).toString("base64url");
23714
23969
  const file = secretFile(stateDir, ticketRecordId);
23715
- writeFileSync36(file, `${JSON.stringify({ secret })}
23970
+ writeFileSync37(file, `${JSON.stringify({ secret })}
23716
23971
  `, { mode: 384 });
23717
23972
  chmodSync5(file, 384);
23718
23973
  return secret;
23719
23974
  }
23720
23975
  function readPeriscopeSecret(stateDir, ticketRecordId) {
23721
23976
  try {
23722
- const v = JSON.parse(readFileSync43(secretFile(stateDir, ticketRecordId), "utf8"));
23977
+ const v = JSON.parse(readFileSync45(secretFile(stateDir, ticketRecordId), "utf8"));
23723
23978
  return typeof v.secret === "string" && v.secret ? v.secret : void 0;
23724
23979
  } catch {
23725
23980
  return void 0;
23726
23981
  }
23727
23982
  }
23728
23983
  function removePeriscopeSecret(stateDir, ticketRecordId) {
23729
- rmSync23(secretFile(stateDir, ticketRecordId), { force: true });
23984
+ rmSync24(secretFile(stateDir, ticketRecordId), { force: true });
23730
23985
  }
23731
23986
  function readPeriscopeEndpoint(stateDir, ticketRecordId) {
23732
23987
  try {
23733
- return JSON.parse(readFileSync43(endpointFile(stateDir, ticketRecordId), "utf8"));
23988
+ return JSON.parse(readFileSync45(endpointFile(stateDir, ticketRecordId), "utf8"));
23734
23989
  } catch {
23735
23990
  return void 0;
23736
23991
  }
23737
23992
  }
23738
23993
  function writePeriscopeEndpoint(stateDir, rec) {
23739
- mkdirSync36(endpointDir(stateDir), { recursive: true });
23740
- writeFileSync36(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
23994
+ mkdirSync37(endpointDir(stateDir), { recursive: true });
23995
+ writeFileSync37(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
23741
23996
  `);
23742
23997
  }
23743
23998
  function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
23744
- rmSync23(endpointFile(stateDir, ticketRecordId), { force: true });
23999
+ rmSync24(endpointFile(stateDir, ticketRecordId), { force: true });
23745
24000
  }
23746
24001
  function listPeriscopeEndpoints(stateDir) {
23747
24002
  const dir = endpointDir(stateDir);
23748
- if (!existsSync48(dir)) return [];
24003
+ if (!existsSync49(dir)) return [];
23749
24004
  const out = [];
23750
24005
  for (const name of readdirSync19(dir)) {
23751
24006
  if (!name.endsWith(".json")) continue;
@@ -23833,10 +24088,10 @@ async function realAncestor(p) {
23833
24088
  }
23834
24089
  if (isLink) {
23835
24090
  if (++hops > 40) throw new PathRefusal(`too many symbolic links resolving ${p}`);
23836
- cur = resolve6(dirname31(cur), await readlink(cur));
24091
+ cur = resolve6(dirname32(cur), await readlink(cur));
23837
24092
  continue;
23838
24093
  }
23839
- const parent = dirname31(cur);
24094
+ const parent = dirname32(cur);
23840
24095
  if (parent === cur) throw new PathRefusal(`cannot resolve ${p}`);
23841
24096
  tail3.push(basename15(cur));
23842
24097
  cur = parent;
@@ -23848,13 +24103,13 @@ async function resolveInWorktree(root, requested) {
23848
24103
  let abs;
23849
24104
  if (isAbsolute5(requested)) {
23850
24105
  abs = resolve6(requested);
23851
- if (isInside(root, abs)) abs = join52(realRoot, relative2(root, abs));
24106
+ if (isInside(root, abs)) abs = join54(realRoot, relative2(root, abs));
23852
24107
  } else {
23853
24108
  abs = resolve6(realRoot, requested);
23854
24109
  }
23855
24110
  if (!isInside(realRoot, abs)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
23856
24111
  const { real, rest } = await realAncestor(abs);
23857
- const final = rest ? join52(real, rest) : real;
24112
+ const final = rest ? join54(real, rest) : real;
23858
24113
  if (!isInside(realRoot, final)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
23859
24114
  if (relative2(realRoot, final).split(sep).includes(".git")) throw new PathRefusal(".git is not reachable from a periscope");
23860
24115
  return final;
@@ -23922,7 +24177,7 @@ function createPeriscopeMcpServer(d, sc) {
23922
24177
  const entries = (await readdir(abs, { withFileTypes: true })).filter((e) => e.name !== ".git");
23923
24178
  const lines2 = await Promise.all(entries.map(async (e) => {
23924
24179
  if (e.isDirectory()) return `[DIR] ${e.name}`;
23925
- const size = (await lstat(join52(abs, e.name))).size;
24180
+ const size = (await lstat(join54(abs, e.name))).size;
23926
24181
  return `[FILE] ${e.name} (${size} bytes)`;
23927
24182
  }));
23928
24183
  return text(lines2.join("\n") || "(empty)");
@@ -23947,7 +24202,7 @@ function createPeriscopeMcpServer(d, sc) {
23947
24202
  if (Buffer.byteLength(content) > MAX_WRITE_BYTES) return text(`content exceeds ${MAX_WRITE_BYTES} bytes`, true);
23948
24203
  const abs = await inTree(path);
23949
24204
  if (abs === await realpath(root)) return text("path must name a file", true);
23950
- await mkdir(dirname31(abs), { recursive: true });
24205
+ await mkdir(dirname32(abs), { recursive: true });
23951
24206
  await inTree(path);
23952
24207
  await writeFile(abs, content);
23953
24208
  return text(`Wrote ${Buffer.byteLength(content)} bytes to ${await rel(abs)}`);
@@ -23987,8 +24242,8 @@ function createPeriscopeMcpServer(d, sc) {
23987
24242
  inputSchema: { path: z.string() },
23988
24243
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
23989
24244
  }, async ({ path }) => {
23990
- const parent = await inTree(dirname31(path) === "" ? "." : dirname31(path));
23991
- const abs = join52(parent, basename15(path));
24245
+ const parent = await inTree(dirname32(path) === "" ? "." : dirname32(path));
24246
+ const abs = join54(parent, basename15(path));
23992
24247
  if (basename15(path) === ".git" || abs === parent) return text("path must name a file", true);
23993
24248
  const st = await lstat(abs).catch(() => void 0);
23994
24249
  if (!st) return text(`${path} does not exist`, true);
@@ -24109,7 +24364,7 @@ function findPeriscope(sidecars, segment) {
24109
24364
  }
24110
24365
 
24111
24366
  // src/cli.ts
24112
- var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname32(process.execPath) : resolve7(dirname32(fileURLToPath(import.meta.url)), "..");
24367
+ var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname33(process.execPath) : resolve7(dirname33(fileURLToPath(import.meta.url)), "..");
24113
24368
  var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
24114
24369
  var CYCLE_SECONDS2 = 120;
24115
24370
  var STALE_CYCLES = 3;
@@ -24367,6 +24622,7 @@ if (!command) usage(argv.length ? `unknown option "${argv[0]}"` : void 0);
24367
24622
  var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
24368
24623
  "passenger-sync-daemon",
24369
24624
  "periscope-proxy",
24625
+ "intel",
24370
24626
  "agents",
24371
24627
  "connect",
24372
24628
  "credential",
@@ -24478,7 +24734,7 @@ if (command === "provision") {
24478
24734
  process.exit(outcome.code);
24479
24735
  }
24480
24736
  if (command === "pool-member") {
24481
- const pool = poolConfigFrom(argv, process.env, () => readFileSync44(0, "utf8"));
24737
+ const pool = poolConfigFrom(argv, process.env, () => readFileSync46(0, "utf8"));
24482
24738
  if (typeof pool === "string") {
24483
24739
  process.stderr.write(`crew pool-member: ${pool}
24484
24740
  `);
@@ -24512,6 +24768,14 @@ if (command === "passenger-sync-daemon") {
24512
24768
  process.exit(2);
24513
24769
  }
24514
24770
  runProxyFromEnv(dropFile);
24771
+ } else if (command === "intel") {
24772
+ const [, sub, bridgeRoute, bridgeTicket] = positional;
24773
+ const stateDir = value("state-dir");
24774
+ if (sub !== "bridge" || !bridgeRoute || !bridgeTicket || !stateDir) {
24775
+ process.stderr.write("usage: crew intel bridge <route> <ticket number> --state-dir DIR (registered by a lane run, not run by hand)\n");
24776
+ process.exit(2);
24777
+ }
24778
+ await runBridge(stateDir, bridgeRoute, bridgeTicket);
24515
24779
  } else {
24516
24780
  let requireArmed = function(what) {
24517
24781
  if (dryRun || route2.enabled) return;
@@ -24713,7 +24977,7 @@ if (command === "passenger-sync-daemon") {
24713
24977
  } else {
24714
24978
  const toleratesNoRoutes = command === "credential" || command === "forge" || command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "deprovision" || command === "connect" && flag("sign-out");
24715
24979
  const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset") || command === "deprovision";
24716
- if (configless && !existsSync49(findConfigFile(CREW_HOME))) {
24980
+ if (configless && !existsSync50(findConfigFile(CREW_HOME))) {
24717
24981
  cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
24718
24982
  } else {
24719
24983
  cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
@@ -24728,7 +24992,7 @@ if (command === "passenger-sync-daemon") {
24728
24992
  throw e;
24729
24993
  }
24730
24994
  if (command === "credential") {
24731
- const r = runCredential(positional[1], readFileSync44(0, "utf8"), cfg.ship.forges, cfg.ship.stateDir);
24995
+ const r = runCredential(positional[1], readFileSync46(0, "utf8"), cfg.ship.forges, cfg.ship.stateDir);
24732
24996
  process.stdout.write(r.out);
24733
24997
  process.stderr.write(r.err);
24734
24998
  process.exit(r.code);
@@ -24740,9 +25004,9 @@ if (command === "passenger-sync-daemon") {
24740
25004
  forges: cfg.ship.forges,
24741
25005
  stateDir: cfg.ship.stateDir,
24742
25006
  configFile: cfg.configFile,
24743
- readFile: (p) => readFileSync44(p, "utf8"),
24744
- writeFile: (p, t) => writeFileSync37(p, t),
24745
- stdin: () => readFileSync44(0, "utf8"),
25007
+ readFile: (p) => readFileSync46(p, "utf8"),
25008
+ writeFile: (p, t) => writeFileSync38(p, t),
25009
+ stdin: () => readFileSync46(0, "utf8"),
24746
25010
  env: process.env
24747
25011
  }
24748
25012
  );
@@ -25035,8 +25299,8 @@ if (command === "passenger-sync-daemon") {
25035
25299
  }
25036
25300
  if (host === "linux" && hasSystemd) {
25037
25301
  const label = labelFor(CREW_HOME, "run");
25038
- const staleTimer = join53(homedir11(), ".config", "systemd", "user", `${label}.timer`);
25039
- if (existsSync49(staleTimer)) {
25302
+ const staleTimer = join55(homedir11(), ".config", "systemd", "user", `${label}.timer`);
25303
+ if (existsSync50(staleTimer)) {
25040
25304
  process.stdout.write(
25041
25305
  `removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
25042
25306
  `
@@ -25063,7 +25327,7 @@ if (command === "passenger-sync-daemon") {
25063
25327
  const tracker = new Tracker(c, cfg.ship);
25064
25328
  problems.push(...validateContract(tracker.contract).map((p) => `contract: ${p}`));
25065
25329
  for (const r of await resolvedRepos(c)) {
25066
- if (!existsSync49(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
25330
+ if (!existsSync50(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
25067
25331
  if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
25068
25332
  readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
25069
25333
  }
@@ -25090,7 +25354,7 @@ if (command === "passenger-sync-daemon") {
25090
25354
  }
25091
25355
  async function doctorFix(o) {
25092
25356
  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))));
25093
- const unitExists = (p) => existsSync49(p);
25357
+ const unitExists = (p) => existsSync50(p);
25094
25358
  const crontab = o.host === "linux" ? spawnSync11("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
25095
25359
  const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run", installContext()), unitExists, crontab) : true;
25096
25360
  const plan = planFix({ routes, schedulerAbsent, host: o.host });
@@ -25107,7 +25371,7 @@ if (command === "passenger-sync-daemon") {
25107
25371
  for (const a of plan.actions) {
25108
25372
  if (a.kind === "enable") {
25109
25373
  if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
25110
- writeFileSync37(cfg.configFile, planEnable(readFileSync44(cfg.configFile, "utf8"), a.route));
25374
+ writeFileSync38(cfg.configFile, planEnable(readFileSync46(cfg.configFile, "utf8"), a.route));
25111
25375
  enabledSound.add(a.route);
25112
25376
  changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
25113
25377
  } else {
@@ -26246,7 +26510,7 @@ ${cu.tail.trimEnd()}
26246
26510
  process.exit(1);
26247
26511
  }
26248
26512
  requireArmed("run the daemon loop");
26249
- const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join53(CREW_HOME, "src"), join53(CREW_HOME, "dist"), join53(CREW_HOME, "bin")];
26513
+ const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join55(CREW_HOME, "src"), join55(CREW_HOME, "dist"), join55(CREW_HOME, "bin")];
26250
26514
  const configPaths = daemonConfigPaths(cfg);
26251
26515
  watchPaths.push(...configPaths);
26252
26516
  noteDaemonStarted(cfg.ship.stateDir);
@@ -26551,10 +26815,10 @@ ${cu.tail.trimEnd()}
26551
26815
  `);
26552
26816
  if (!dryRun) {
26553
26817
  const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
26554
- const raw = existsSync49(resolvedPath) ? JSON.parse(readFileSync44(resolvedPath, "utf8")) : {};
26818
+ const raw = existsSync50(resolvedPath) ? JSON.parse(readFileSync46(resolvedPath, "utf8")) : {};
26555
26819
  raw.agentSkills = result.agentSkills;
26556
- mkdirSync37(dirOf2(resolvedPath), { recursive: true });
26557
- writeFileSync37(resolvedPath, `${JSON.stringify(raw, null, 2)}
26820
+ mkdirSync38(dirOf2(resolvedPath), { recursive: true });
26821
+ writeFileSync38(resolvedPath, `${JSON.stringify(raw, null, 2)}
26558
26822
  `);
26559
26823
  }
26560
26824
  if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
@@ -26577,7 +26841,7 @@ ${cu.tail.trimEnd()}
26577
26841
  throw e;
26578
26842
  }
26579
26843
  const oldName = cfg.ship.name;
26580
- writeFileSync37(cfg.configFile, setShipNameInYaml(readFileSync44(cfg.configFile, "utf8"), newName));
26844
+ writeFileSync38(cfg.configFile, setShipNameInYaml(readFileSync46(cfg.configFile, "utf8"), newName));
26581
26845
  cfg.ship.name = newName;
26582
26846
  let renameMachineId;
26583
26847
  try {
@@ -26624,14 +26888,14 @@ ${cu.tail.trimEnd()}
26624
26888
  process.exit(2);
26625
26889
  };
26626
26890
  if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks" && sub !== "check") usage2();
26627
- const configText = readFileSync44(cfg.configFile, "utf8");
26891
+ const configText = readFileSync46(cfg.configFile, "utf8");
26628
26892
  if (sub === "check") {
26629
26893
  try {
26630
26894
  const routeArg = value("route") ?? positional[2];
26631
26895
  const found = cfg.routes.find((r) => r.route === routeArg);
26632
26896
  if (!found) throw new ReposError(`no route "${routeArg}" in the config (have: ${cfg.routes.map((r) => r.route).join(", ") || "none"})`);
26633
26897
  const named2 = value("route") ? void 0 : positional[3];
26634
- const dir = named2 ? repoDirOf(configText, dirname32(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
26898
+ const dir = named2 ? repoDirOf(configText, dirname33(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
26635
26899
  const target = reposOf(found).find((t) => resolvePath(t.dir) === dir || t.name === named2);
26636
26900
  const o = target ? found.repoOverrides[target.name] : void 0;
26637
26901
  runReposCheck(dir, {
@@ -26639,7 +26903,8 @@ ${cu.tail.trimEnd()}
26639
26903
  labels: { ...found.labels, ...o?.labels },
26640
26904
  release: mergeRouteRelease(found.release, o?.release),
26641
26905
  branch: { ...found.branch, ...o?.branch },
26642
- worktrees: { prefix: shipWorktreePrefixFor(found), nameOverride: o?.worktreeName }
26906
+ worktrees: { prefix: shipWorktreePrefixFor(found), nameOverride: o?.worktreeName },
26907
+ mapTokenBudget: cfg.ship.mapTokenBudget
26643
26908
  });
26644
26909
  } catch (e) {
26645
26910
  if (e instanceof ReposError) {
@@ -26654,9 +26919,9 @@ ${cu.tail.trimEnd()}
26654
26919
  if (sub === "hooks") {
26655
26920
  try {
26656
26921
  const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
26657
- const dir = repoDirOf(configText, dirname32(cfg.configFile), a.route, a.name);
26658
- if (!existsSync49(dir)) throw new ReposError(`${dir} does not exist on disk`);
26659
- const files = { exists: (r) => existsSync49(resolvePath(dir, r)), list: (r) => {
26922
+ const dir = repoDirOf(configText, dirname33(cfg.configFile), a.route, a.name);
26923
+ if (!existsSync50(dir)) throw new ReposError(`${dir} does not exist on disk`);
26924
+ const files = { exists: (r) => existsSync50(resolvePath(dir, r)), list: (r) => {
26660
26925
  try {
26661
26926
  return readdirSync20(resolvePath(dir, r));
26662
26927
  } catch {
@@ -26664,7 +26929,7 @@ ${cu.tail.trimEnd()}
26664
26929
  }
26665
26930
  }, read: (r) => {
26666
26931
  try {
26667
- return readFileSync44(resolvePath(dir, r), "utf8");
26932
+ return readFileSync46(resolvePath(dir, r), "utf8");
26668
26933
  } catch {
26669
26934
  return void 0;
26670
26935
  }
@@ -26706,19 +26971,19 @@ ${cu.tail.trimEnd()}
26706
26971
  let cleared;
26707
26972
  if (clear2) {
26708
26973
  const plan = clearRouteHooks(configText, a.route, a.name);
26709
- if (plan.removed.length > 0) writeFileSync37(cfg.configFile, plan.text);
26974
+ if (plan.removed.length > 0) writeFileSync38(cfg.configFile, plan.text);
26710
26975
  cleared = { file: cfg.configFile, removed: plan.removed };
26711
26976
  }
26712
26977
  if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
26713
26978
  if (write === "route") {
26714
26979
  const plan = planRouteHooks(configText, a.route, a.name, picked.hooks, { replace: flag("replace") });
26715
- writeFileSync37(cfg.configFile, plan.text);
26980
+ writeFileSync38(cfg.configFile, plan.text);
26716
26981
  written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept, replaced: plan.replaced };
26717
26982
  } else if (write === "repo") {
26718
26983
  const have = existingRepoConfig(files);
26719
26984
  if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
26720
26985
  const file = resolvePath(dir, ".crew.yaml");
26721
- writeFileSync37(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
26986
+ writeFileSync38(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
26722
26987
  written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [], replaced: [] };
26723
26988
  }
26724
26989
  if (flag("json")) {
@@ -26774,7 +27039,7 @@ ${cu.tail.trimEnd()}
26774
27039
  if (sub === "list" && !routeName) usage2();
26775
27040
  if (sub === "add") {
26776
27041
  try {
26777
- const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync49(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
27042
+ const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync50(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
26778
27043
  routeName = a.route;
26779
27044
  addPath = a.path;
26780
27045
  addName = a.name;
@@ -26804,7 +27069,7 @@ ${cu.tail.trimEnd()}
26804
27069
  const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
26805
27070
  if (sub === "list" || sub === "add") {
26806
27071
  const liveRoute = cfg.routes.find((r) => r.route === routeName);
26807
- if (liveRoute?.resolved?.reposModelId && existsSync49(resolvedFile) && !dryRun) {
27072
+ if (liveRoute?.resolved?.reposModelId && existsSync50(resolvedFile) && !dryRun) {
26808
27073
  try {
26809
27074
  await hydrateApiKeys([liveRoute]);
26810
27075
  persistRepoRows(cfg.ship.stateDir, liveRoute.route, await new Tracker(liveRoute, cfg.ship).repoRowsForMap());
@@ -26813,16 +27078,16 @@ ${cu.tail.trimEnd()}
26813
27078
  }
26814
27079
  }
26815
27080
  let resolvedRemotes;
26816
- if (existsSync49(resolvedFile)) {
27081
+ if (existsSync50(resolvedFile)) {
26817
27082
  try {
26818
- resolvedRemotes = JSON.parse(readFileSync44(resolvedFile, "utf8"));
27083
+ resolvedRemotes = JSON.parse(readFileSync46(resolvedFile, "utf8"));
26819
27084
  } catch {
26820
27085
  }
26821
27086
  }
26822
27087
  const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
26823
27088
  const text2 = configText;
26824
- const base = dirname32(cfg.configFile);
26825
- const isCheckout = (d) => existsSync49(`${d}/.git`);
27089
+ const base = dirname33(cfg.configFile);
27090
+ const isCheckout = (d) => existsSync50(`${d}/.git`);
26826
27091
  try {
26827
27092
  if (sub === "list") {
26828
27093
  if (flag("json")) {
@@ -26832,11 +27097,11 @@ ${cu.tail.trimEnd()}
26832
27097
  base,
26833
27098
  resolved: resolvedRemotes,
26834
27099
  originOf,
26835
- exists: (d) => existsSync49(d),
27100
+ exists: (d) => existsSync50(d),
26836
27101
  isCheckout,
26837
27102
  repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
26838
27103
  try {
26839
- return Object.keys(parseRepoConfig(readFileSync44(resolvePath(d, f), "utf8"), f).hooks).length > 0;
27104
+ return Object.keys(parseRepoConfig(readFileSync46(resolvePath(d, f), "utf8"), f).hooks).length > 0;
26840
27105
  } catch {
26841
27106
  return false;
26842
27107
  }
@@ -26853,7 +27118,7 @@ ${cu.tail.trimEnd()}
26853
27118
  for (const r of repos) {
26854
27119
  const origin2 = originOf(r.dir);
26855
27120
  const row = matchRepoName(origin2, resolvedRemotes);
26856
- const note = !existsSync49(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
27121
+ const note = !existsSync50(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
26857
27122
  process.stdout.write(`${r.name} ${r.dir} ${note}
26858
27123
  `);
26859
27124
  }
@@ -26865,9 +27130,9 @@ ${cu.tail.trimEnd()}
26865
27130
  ` : `${human}
26866
27131
  `);
26867
27132
  const plan2 = planRepoClone({ text: text2, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
26868
- if (existsSync49(plan2.target) && (existsSync49(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
27133
+ if (existsSync50(plan2.target) && (existsSync50(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
26869
27134
  throw new ReposError(
26870
- existsSync49(`${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`
27135
+ existsSync50(`${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`
26871
27136
  );
26872
27137
  }
26873
27138
  if (dryRun) {
@@ -26885,14 +27150,14 @@ ${cu.tail.trimEnd()}
26885
27150
  throw new ReposError(`${e.message}${authHint}
26886
27151
  crew.yaml was not changed`);
26887
27152
  }
26888
- writeFileSync37(cfg.configFile, plan2.text);
27153
+ writeFileSync38(cfg.configFile, plan2.text);
26889
27154
  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:
26890
27155
  `);
26891
27156
  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}`);
26892
27157
  break;
26893
27158
  }
26894
27159
  const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir11()));
26895
- const top = existsSync49(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
27160
+ const top = existsSync50(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
26896
27161
  if (!top) throw new ReposError(`${target} is not a git checkout`);
26897
27162
  const origin = originOf(top);
26898
27163
  const plan = planRepoAdd({
@@ -26944,15 +27209,15 @@ ${cu.tail.trimEnd()}
26944
27209
  const created = await rowTracker.createRepoRow(rowPayload);
26945
27210
  process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
26946
27211
  `);
26947
- if (existsSync49(resolvedFile)) {
26948
- const raw = JSON.parse(readFileSync44(resolvedFile, "utf8"));
27212
+ if (existsSync50(resolvedFile)) {
27213
+ const raw = JSON.parse(readFileSync46(resolvedFile, "utf8"));
26949
27214
  raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
26950
27215
  raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
26951
- writeFileSync37(resolvedFile, `${JSON.stringify(raw, null, 2)}
27216
+ writeFileSync38(resolvedFile, `${JSON.stringify(raw, null, 2)}
26952
27217
  `);
26953
27218
  }
26954
27219
  }
26955
- writeFileSync37(cfg.configFile, plan.text);
27220
+ writeFileSync38(cfg.configFile, plan.text);
26956
27221
  process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
26957
27222
  `);
26958
27223
  } catch (e) {
@@ -27323,7 +27588,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
27323
27588
  process.exit(2);
27324
27589
  }
27325
27590
  if (found.shipNameAdopted && !dryRun) {
27326
- writeFileSync37(cfg.configFile, setShipNameInYaml(readFileSync44(cfg.configFile, "utf8"), found.shipNameAdopted));
27591
+ writeFileSync38(cfg.configFile, setShipNameInYaml(readFileSync46(cfg.configFile, "utf8"), found.shipNameAdopted));
27327
27592
  cfg.ship.name = found.shipNameAdopted;
27328
27593
  }
27329
27594
  if (found.shipRow) {
@@ -27366,10 +27631,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
27366
27631
  }
27367
27632
  }
27368
27633
  let previous;
27369
- const hadPreviousFile = existsSync49(resolvedPath);
27634
+ const hadPreviousFile = existsSync50(resolvedPath);
27370
27635
  if (hadPreviousFile) {
27371
27636
  try {
27372
- previous = JSON.parse(readFileSync44(resolvedPath, "utf8"));
27637
+ previous = JSON.parse(readFileSync46(resolvedPath, "utf8"));
27373
27638
  } catch {
27374
27639
  }
27375
27640
  }
@@ -27499,10 +27764,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
27499
27764
  process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
27500
27765
  `);
27501
27766
  } else {
27502
- mkdirSync37(dirname32(resolvedPath), { recursive: true });
27767
+ mkdirSync38(dirname33(resolvedPath), { recursive: true });
27503
27768
  if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
27504
27769
  const tmpPath = `${resolvedPath}.tmp${process.pid}`;
27505
- writeFileSync37(tmpPath, `${JSON.stringify(resolved, null, 2)}
27770
+ writeFileSync38(tmpPath, `${JSON.stringify(resolved, null, 2)}
27506
27771
  `);
27507
27772
  renameSync6(tmpPath, resolvedPath);
27508
27773
  process.stderr.write(`Resolved ids written to ${resolvedPath}
@@ -27548,8 +27813,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
27548
27813
  process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
27549
27814
  `);
27550
27815
  } else {
27551
- mkdirSync37(dirname32(apiKeyFile), { recursive: true });
27552
- writeFileSync37(apiKeyFile, `${apiKeyVar}=${apiKey}
27816
+ mkdirSync38(dirname33(apiKeyFile), { recursive: true });
27817
+ writeFileSync38(apiKeyFile, `${apiKeyVar}=${apiKey}
27553
27818
  `, { mode: 384 });
27554
27819
  process.stderr.write(`API key written to ${apiKeyFile}
27555
27820
  `);
@@ -28231,6 +28496,33 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
28231
28496
  }
28232
28497
  const pathWt = applyRunWorktree(repoWt.dir, repoWt.config, planWt);
28233
28498
  emit.emit(describeRunWorktreePlan(planWt));
28499
+ if (pathWt && planIsUsable(planWt) && repoWt.config.hooks.intel) {
28500
+ const shellWt = resolveShell(repoWt.config.shell ?? cfg.ship.shell);
28501
+ const paragraph = await startWorktreeIntel({
28502
+ worktree: pathWt,
28503
+ route: routeWt.route,
28504
+ number: String(nWt),
28505
+ stateDir: cfg.ship.stateDir,
28506
+ ticketTag: String(ticketWt.issue_tag ?? ticketWt.issue_id ?? `ISSUE-${nWt}`),
28507
+ branch: planWt.branch ?? "",
28508
+ startupSeconds: cfg.ship.intelStartupSeconds,
28509
+ prepare: async (path) => {
28510
+ for (const f of repoWt.config.worktrees.copy) {
28511
+ const src = join55(repoWt.dir, f);
28512
+ if (!existsSync50(src) || existsSync50(join55(path, f))) continue;
28513
+ mkdirSync38(dirOf2(join55(path, f)), { recursive: true });
28514
+ copyFileSync2(src, join55(path, f));
28515
+ }
28516
+ const setup = repoWt.config.hooks.setup;
28517
+ return setup ? runScript(setup, { cwd: path, shell: shellWt }) : null;
28518
+ },
28519
+ runHook: async (name, cwd, env) => {
28520
+ const script = repoWt.config.hooks[name];
28521
+ return script ? runScript(script, { cwd, env, shell: shellWt, timeoutMs: name === "intel" ? 6e4 : void 0 }) : null;
28522
+ }
28523
+ });
28524
+ emit.emit(paragraph);
28525
+ }
28234
28526
  if (pathWt) process.stdout.write(`${pathWt}
28235
28527
  `);
28236
28528
  if (!planIsUsable(planWt)) process.exitCode = 1;
@@ -28430,7 +28722,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
28430
28722
  const shellR = hit && resolveShell(hit.repo.config.shell ?? cfg.ship.shell);
28431
28723
  const resumed = await runPeriscopeResume({
28432
28724
  find: async () => hit && { row: hit.row, tag: hit.tag },
28433
- worktreeExists: (p) => existsSync49(p),
28725
+ worktreeExists: (p) => existsSync50(p),
28434
28726
  prepareDropFile,
28435
28727
  startProxy: (wt, url, n) => launchProxy(crewInvocation(CREW_HOME), wt, url, Number(n), { host: hit.row.host || "localhost", inject: hit.repo.config.periscope?.inspector !== "repo" }),
28436
28728
  caps: periscopeCaps(cfg.ship),
@@ -28526,10 +28818,10 @@ ${where.join("\n")}`);
28526
28818
  },
28527
28819
  prepare: async (path) => {
28528
28820
  for (const f of repoP.config.worktrees.copy) {
28529
- const src = join53(repoP.dir, f);
28530
- if (!existsSync49(src)) continue;
28531
- mkdirSync37(dirOf2(join53(path, f)), { recursive: true });
28532
- copyFileSync2(src, join53(path, f));
28821
+ const src = join55(repoP.dir, f);
28822
+ if (!existsSync50(src)) continue;
28823
+ mkdirSync38(dirOf2(join55(path, f)), { recursive: true });
28824
+ copyFileSync2(src, join55(path, f));
28533
28825
  }
28534
28826
  const setup = repoP.config.hooks.setup;
28535
28827
  return setup ? runScript(setup, { cwd: path, shell: shellP, onLine: (l) => {
@@ -28839,7 +29131,7 @@ ${where.join("\n")}`);
28839
29131
  };
28840
29132
  const droppedRoutes = [];
28841
29133
  try {
28842
- const rawCfg = parseYaml2(readFileSync44(cfg.configFile, "utf8"));
29134
+ const rawCfg = parseYaml2(readFileSync46(cfg.configFile, "utf8"));
28843
29135
  for (const r of rawCfg?.routes ?? []) {
28844
29136
  if (typeof r?.route !== "string" || cfg.routes.some((x) => x.route === r.route)) continue;
28845
29137
  try {
@@ -28892,8 +29184,8 @@ ${where.join("\n")}`);
28892
29184
  process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
28893
29185
  break;
28894
29186
  }
28895
- mkdirSync37(cfg.ship.stateDir, { recursive: true });
28896
- writeFileSync37(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
29187
+ mkdirSync38(cfg.ship.stateDir, { recursive: true });
29188
+ writeFileSync38(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
28897
29189
  `);
28898
29190
  process.stdout.write(
28899
29191
  "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"
@@ -28958,7 +29250,7 @@ ${where.join("\n")}`);
28958
29250
  }
28959
29251
  case "log": {
28960
29252
  try {
28961
- const text2 = readFileSync44(cfg.ship.logFile, "utf8").split("\n");
29253
+ const text2 = readFileSync46(cfg.ship.logFile, "utf8").split("\n");
28962
29254
  process.stdout.write(`${text2.slice(-(Number(value("tail")) || 40)).join("\n")}
28963
29255
  `);
28964
29256
  } catch {
@@ -28981,8 +29273,8 @@ ${where.join("\n")}`);
28981
29273
  };
28982
29274
  if (!route2.resolved) fail2(`route ${route2.route} is not connected \u2014 run \`crew connect\` first`);
28983
29275
  const tracker = new Tracker(route2, cfg.ship);
28984
- const configText = readFileSync44(cfg.configFile, "utf8");
28985
- const cfgBase = dirname32(cfg.configFile);
29276
+ const configText = readFileSync46(cfg.configFile, "utf8");
29277
+ const cfgBase = dirname33(cfg.configFile);
28986
29278
  const configured2 = configuredRepos(configText, route2.route, cfgBase);
28987
29279
  const dir = configured2.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured2.map((r) => r.dir), route2.reposBasePath, route2.route.split("/")[0]);
28988
29280
  const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route2.route);
@@ -29010,7 +29302,7 @@ ${where.join("\n")}`);
29010
29302
  }
29011
29303
  if (configured2.some((r) => r.dir === dir)) {
29012
29304
  try {
29013
- writeFileSync37(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
29305
+ writeFileSync38(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
29014
29306
  } catch (e) {
29015
29307
  if (e instanceof ReposError) fail2(e.message);
29016
29308
  throw e;
@@ -29018,29 +29310,29 @@ ${where.join("\n")}`);
29018
29310
  }
29019
29311
  if (row) {
29020
29312
  await tracker.deleteRepoRow(row.id);
29021
- if (existsSync49(resolvedFile)) {
29022
- const raw = JSON.parse(readFileSync44(resolvedFile, "utf8"));
29313
+ if (existsSync50(resolvedFile)) {
29314
+ const raw = JSON.parse(readFileSync46(resolvedFile, "utf8"));
29023
29315
  if (raw.repoNames) delete raw.repoNames[row.id];
29024
29316
  if (raw.repoRemotes) delete raw.repoRemotes[row.id];
29025
- writeFileSync37(resolvedFile, `${JSON.stringify(raw, null, 2)}
29317
+ writeFileSync38(resolvedFile, `${JSON.stringify(raw, null, 2)}
29026
29318
  `);
29027
29319
  }
29028
29320
  }
29029
- if (existsSync49(join53(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync24(dir, { recursive: true, force: true });
29321
+ if (existsSync50(join55(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync25(dir, { recursive: true, force: true });
29030
29322
  say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
29031
29323
  break;
29032
29324
  }
29033
29325
  const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
29034
- const fresh = !existsSync49(join53(dir, ".git"));
29035
- if (existsSync49(dir) && fresh && readdirSync20(dir).length > 0) fail2(`${dir} exists and is not a git checkout \u2014 move it aside`);
29326
+ const fresh = !existsSync50(join55(dir, ".git"));
29327
+ if (existsSync50(dir) && fresh && readdirSync20(dir).length > 0) fail2(`${dir} exists and is not a git checkout \u2014 move it aside`);
29036
29328
  if (dryRun) {
29037
29329
  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`);
29038
29330
  break;
29039
29331
  }
29040
29332
  if (fresh) {
29041
- mkdirSync37(dir, { recursive: true });
29042
- writeFileSync37(join53(dir, "README.md"), SAMPLE_README);
29043
- writeFileSync37(join53(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
29333
+ mkdirSync38(dir, { recursive: true });
29334
+ writeFileSync38(join55(dir, "README.md"), SAMPLE_README);
29335
+ writeFileSync38(join55(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
29044
29336
  const g = (...a) => {
29045
29337
  const r = spawnSync11("git", a, { cwd: dir, encoding: "utf8" });
29046
29338
  if (r.status !== 0) fail2(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
@@ -29058,17 +29350,17 @@ ${where.join("\n")}`);
29058
29350
  spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
29059
29351
  });
29060
29352
  repoId = (await tracker.createRepoRow(payload)).id;
29061
- if (existsSync49(resolvedFile)) {
29062
- const raw = JSON.parse(readFileSync44(resolvedFile, "utf8"));
29353
+ if (existsSync50(resolvedFile)) {
29354
+ const raw = JSON.parse(readFileSync46(resolvedFile, "utf8"));
29063
29355
  raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
29064
29356
  raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
29065
- writeFileSync37(resolvedFile, `${JSON.stringify(raw, null, 2)}
29357
+ writeFileSync38(resolvedFile, `${JSON.stringify(raw, null, 2)}
29066
29358
  `);
29067
29359
  }
29068
29360
  }
29069
29361
  if (!configured2.some((r) => r.dir === dir)) {
29070
29362
  try {
29071
- writeFileSync37(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
29363
+ writeFileSync38(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
29072
29364
  } catch (e) {
29073
29365
  if (e instanceof ReposError) fail2(e.message);
29074
29366
  throw e;
@@ -29414,7 +29706,7 @@ watermark: ${state.watermark()}
29414
29706
  }
29415
29707
  for (const r of repos) {
29416
29708
  const remote = r.config.branch.remote;
29417
- if (existsSync49(r.dir)) {
29709
+ if (existsSync50(r.dir)) {
29418
29710
  pushAuth.push({
29419
29711
  route: c.route,
29420
29712
  repo: r.name,
@@ -29453,6 +29745,7 @@ watermark: ${state.watermark()}
29453
29745
  base: doctorBaseReport(r, r.config.branch.base, baseHealth),
29454
29746
  hooks: r.config.hooks,
29455
29747
  onPath: (cmd) => !!findOnPath(cmd, doctorSchedulerPath),
29748
+ mapFile: mapFilePath(r.config.hooks.map) ? inspectMapFile(r.dir, mapFilePath(r.config.hooks.map)) : void 0,
29456
29749
  intelSyntax: r.config.hooks.intel && !intelFlavor(r.config.hooks.intel) ? checkHookSyntax(r.config.hooks.intel, r.config.shell) : void 0,
29457
29750
  intelFlavor: intelFlavor(r.config.hooks.intel) ? { name: intelFlavor(r.config.hooks.intel), missing: [
29458
29751
  ...missingPrerequisites(intelFlavor(r.config.hooks.intel), (bin) => !!findOnPath(bin, doctorSchedulerPath)),
@@ -29509,7 +29802,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
29509
29802
  }
29510
29803
  try {
29511
29804
  const evFile = eventFileFor(cfg.ship.stateDir);
29512
- const evSize = existsSync49(evFile) ? statSync16(evFile).size : 0;
29805
+ const evSize = existsSync50(evFile) ? statSync16(evFile).size : 0;
29513
29806
  process.stdout.write(
29514
29807
  evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
29515
29808
  ` : "events log: ok\n"
@@ -29520,7 +29813,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
29520
29813
  const unitPlan = planInstall(cfg.ship, CREW_HOME, host, detectSystemd(), "run", installContext());
29521
29814
  const abandons = unitAbandonsAgents(unitPlan, (p) => {
29522
29815
  try {
29523
- return readFileSync44(p, "utf8");
29816
+ return readFileSync46(p, "utf8");
29524
29817
  } catch {
29525
29818
  return null;
29526
29819
  }