@tablation/crew 0.126.0 → 0.126.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -794,7 +794,7 @@ function shellInitScript(shell) {
794
794
 
795
795
  // src/config.ts
796
796
  import { readFileSync as readFileSync15, existsSync as existsSync15 } from "fs";
797
- import { resolve as resolve2, dirname as dirname7, isAbsolute as isAbsolute2, join as join17, basename as basename4 } from "path";
797
+ import { resolve as resolve2, dirname as dirname8, isAbsolute as isAbsolute2, join as join17, basename as basename4 } from "path";
798
798
  import { homedir as homedir6, tmpdir } from "os";
799
799
  import { parse as parse2 } from "yaml";
800
800
 
@@ -2661,7 +2661,7 @@ function explain(ship, required) {
2661
2661
 
2662
2662
  // src/repo-config.ts
2663
2663
  import { readFileSync as readFileSync14, existsSync as existsSync14 } from "fs";
2664
- import { join as join16, basename as basename3 } from "path";
2664
+ import { join as join16, basename as basename3, dirname as dirname7 } from "path";
2665
2665
  import { parse } from "yaml";
2666
2666
 
2667
2667
  // src/sandbox-policy.ts
@@ -2863,6 +2863,19 @@ var SPEC_VERSION = 1;
2863
2863
  var REPO_CONFIG_FILENAMES = [".crew.yaml", ".crew.yml", ".crew.json"];
2864
2864
  var RepoConfigError = class extends Error {
2865
2865
  };
2866
+ function versionFileProblem(name, text2) {
2867
+ const remedy = `a version file that is not a JSON object with a string "version" needs hooks.bump instead of release.versionFiles (docs/REPO_SPEC.md)`;
2868
+ let doc;
2869
+ try {
2870
+ doc = JSON.parse(text2);
2871
+ } catch {
2872
+ return `release.versionFiles entry "${name}" is not a JSON document \u2014 ${remedy}`;
2873
+ }
2874
+ if (!doc || typeof doc !== "object" || Array.isArray(doc) || typeof doc.version !== "string") {
2875
+ return `release.versionFiles entry "${name}" has no string "version" field \u2014 ${remedy}`;
2876
+ }
2877
+ return null;
2878
+ }
2866
2879
  var HOOK_NAMES = [
2867
2880
  "test",
2868
2881
  "build",
@@ -3197,6 +3210,22 @@ function parseRepoConfig(text2, file) {
3197
3210
  if (versionFiles.length === 0) {
3198
3211
  throw new RepoConfigError(`${file}: release.versionFiles cannot be empty`);
3199
3212
  }
3213
+ if (versionFilesGiven) {
3214
+ for (const name of versionFiles) {
3215
+ const path = join16(dirname7(file), name);
3216
+ if (!existsSync14(path)) continue;
3217
+ let text3;
3218
+ try {
3219
+ text3 = readFileSync14(path, "utf8");
3220
+ } catch (e) {
3221
+ throw new RepoConfigError(
3222
+ `${file}: release.versionFiles entry "${name}" cannot be read (${e.code ?? "read error"})`
3223
+ );
3224
+ }
3225
+ const problem = versionFileProblem(name, text3);
3226
+ if (problem) throw new RepoConfigError(`${file}: ${problem}`);
3227
+ }
3228
+ }
3200
3229
  const inspectorRaw = raw.periscope?.inspector;
3201
3230
  if (inspectorRaw !== void 0 && inspectorRaw !== "proxy" && inspectorRaw !== "repo") {
3202
3231
  throw new RepoConfigError(`${file}: periscope.inspector must be "proxy" or "repo"`);
@@ -3898,7 +3927,7 @@ function loadConfig(crewHome, configFile, opts = {}) {
3898
3927
  ` + configSearchPath(crewHome).map((c) => ` - ${c}`).join("\n") + `
3899
3928
 
3900
3929
  Create the first of those:
3901
- mkdir -p ${dirname7(defaultConfigPath())} && cp ${join17(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
3930
+ mkdir -p ${dirname8(defaultConfigPath())} && cp ${join17(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
3902
3931
  );
3903
3932
  }
3904
3933
  let raw;
@@ -3908,7 +3937,7 @@ Create the first of those:
3908
3937
  throw new ConfigError(`${file}: not valid YAML \u2014 ${e.message}`);
3909
3938
  }
3910
3939
  if (!raw || typeof raw !== "object") throw new ConfigError(`${file} is empty or not a mapping`);
3911
- const base = dirname7(file);
3940
+ const base = dirname8(file);
3912
3941
  const missing = new Missing();
3913
3942
  rejectUnknownKeys(raw, /* @__PURE__ */ new Set(["ship", "routes"]), "top-level", file);
3914
3943
  const shipRaw = raw.ship ?? {};
@@ -4275,7 +4304,7 @@ function locateDir(cfg, dir, commonDirOf) {
4275
4304
  const direct = find(target);
4276
4305
  if (direct) return direct;
4277
4306
  const common = commonDirOf?.(target);
4278
- return common ? find(dirname7(resolve2(target, common))) : void 0;
4307
+ return common ? find(dirname8(resolve2(target, common))) : void 0;
4279
4308
  }
4280
4309
  function findRoute(cfg, route2) {
4281
4310
  if (!route2) {
@@ -4717,7 +4746,7 @@ function recoveredFailureHeadlines(o, route2) {
4717
4746
  }
4718
4747
 
4719
4748
  // src/install.ts
4720
- import { dirname as dirname8, join as join18, extname } from "path";
4749
+ import { dirname as dirname9, join as join18, extname } from "path";
4721
4750
  import { homedir as homedir7, userInfo } from "os";
4722
4751
  import { accessSync as accessSync2, constants as constants2, existsSync as existsSync16, mkdirSync as mkdirSync11, readdirSync as readdirSync3, readFileSync as readFileSync16, statSync as statSync5, writeFileSync as writeFileSync12, unlinkSync, realpathSync } from "fs";
4723
4752
  import { spawnSync as spawnSync5 } from "child_process";
@@ -4762,7 +4791,7 @@ function schedulerLogFor(crewLog, job) {
4762
4791
  return `${stem}.${suffix}${ext || ".log"}`;
4763
4792
  }
4764
4793
  function pathFor(ship) {
4765
- const nodeBinDir = dirname8(process.execPath);
4794
+ const nodeBinDir = dirname9(process.execPath);
4766
4795
  const parts = [nodeBinDir];
4767
4796
  if (ship.extraPath) parts.push(ship.extraPath);
4768
4797
  parts.push("/usr/bin", "/bin", "/usr/sbin", "/sbin");
@@ -4784,7 +4813,7 @@ function dockerPathProblem(ship, shellPath = process.env.PATH ?? "") {
4784
4813
  const schedulerPath = pathFor(ship);
4785
4814
  if (findOnPath("docker", schedulerPath)) return void 0;
4786
4815
  const inShell = findOnPath("docker", shellPath);
4787
- return inShell ? `\`docker\` is not on the scheduler PATH (${schedulerPath}) but is at ${inShell} in your shell; add ${dirname8(inShell)} to ship.extraPath in crew.yaml and run \`crew install\`` : `\`docker\` is not on the scheduler PATH (${schedulerPath}) or your shell PATH; install Docker, then add its directory to ship.extraPath in crew.yaml and run \`crew install\``;
4816
+ return inShell ? `\`docker\` is not on the scheduler PATH (${schedulerPath}) but is at ${inShell} in your shell; add ${dirname9(inShell)} to ship.extraPath in crew.yaml and run \`crew install\`` : `\`docker\` is not on the scheduler PATH (${schedulerPath}) or your shell PATH; install Docker, then add its directory to ship.extraPath in crew.yaml and run \`crew install\``;
4788
4817
  }
4789
4818
  var USER_INSTALL = { root: false, user: "" };
4790
4819
  function installContext(env = process.env) {
@@ -5079,7 +5108,7 @@ async function applyInstall(plan, crewHome, dryRun, log) {
5079
5108
  }
5080
5109
  const before = existsSync16(path) ? readFileSync16(path, "utf8") : null;
5081
5110
  if (before !== plan.unitContent[path]) changed2 = true;
5082
- mkdirSync11(dirname8(path), { recursive: true });
5111
+ mkdirSync11(dirname9(path), { recursive: true });
5083
5112
  writeFileSync12(path, plan.unitContent[path]);
5084
5113
  log.emit(`wrote ${path}`);
5085
5114
  }
@@ -5308,7 +5337,7 @@ import {
5308
5337
  readdirSync as readdirSync4,
5309
5338
  rmSync as rmSync9
5310
5339
  } from "fs";
5311
- import { dirname as dirname9, basename as basename5, join as join19 } from "path";
5340
+ import { dirname as dirname10, basename as basename5, join as join19 } from "path";
5312
5341
  var GitError = class extends Error {
5313
5342
  };
5314
5343
  function streamText(x) {
@@ -5688,7 +5717,7 @@ function recoverStrandedReleaseCheckouts(cwd) {
5688
5717
  if (line.startsWith("worktree ")) path = line.slice("worktree ".length);
5689
5718
  else if (line.startsWith("branch refs/heads/crew/release-") && path) {
5690
5719
  gitOk(cwd, ["worktree", "remove", "--force", path]);
5691
- const root = dirname9(path);
5720
+ const root = dirname10(path);
5692
5721
  if (basename5(root).startsWith("crew-release-")) rmSync9(root, { recursive: true, force: true });
5693
5722
  }
5694
5723
  }
@@ -5768,7 +5797,7 @@ function ensureRepoCheckout(dir, remote, opts = {}) {
5768
5797
  `${dir} does not exist and this repo has no discovered remote to clone from \u2014 re-run \`crew connect\`, or check out the repository yourself and point \`repos:\` at it`
5769
5798
  );
5770
5799
  }
5771
- mkdirSync12(dirname9(dir), { recursive: true });
5800
+ mkdirSync12(dirname10(dir), { recursive: true });
5772
5801
  const url = cloneUrlFor(remote, opts);
5773
5802
  try {
5774
5803
  execFileSync5("git", ["clone", url, dir], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
@@ -5793,7 +5822,7 @@ function sleepSync(ms3) {
5793
5822
  }
5794
5823
  function acquireCheckoutLock(dir, timeoutMs = 3e4) {
5795
5824
  const lockPath = checkoutLockPath(dir);
5796
- mkdirSync12(dirname9(lockPath), { recursive: true });
5825
+ mkdirSync12(dirname10(lockPath), { recursive: true });
5797
5826
  const deadline = Date.now() + timeoutMs;
5798
5827
  for (; ; ) {
5799
5828
  try {
@@ -6305,7 +6334,7 @@ import { createHash as createHash6 } from "crypto";
6305
6334
  import { spawn as spawn5 } from "child_process";
6306
6335
  import { execFileSync as execFileSync8 } from "child_process";
6307
6336
  import { existsSync as existsSync30, mkdirSync as mkdirSync19, readFileSync as readFileSync27, renameSync as renameSync3, writeFileSync as writeFileSync20, rmSync as rmSync15 } from "fs";
6308
- import { dirname as dirname14, join as join32 } from "path";
6337
+ import { dirname as dirname15, join as join32 } from "path";
6309
6338
  import { fileURLToPath } from "url";
6310
6339
  import { tmpdir as tmpdir3 } from "os";
6311
6340
 
@@ -6330,7 +6359,7 @@ import { spawnSync as spawnSync6 } from "child_process";
6330
6359
  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";
6331
6360
  import { StringDecoder } from "string_decoder";
6332
6361
  import { finished } from "stream/promises";
6333
- import { join as join29, dirname as dirname13, basename as basename9 } from "path";
6362
+ import { join as join29, dirname as dirname14, basename as basename9 } from "path";
6334
6363
  import { createHash as createHash4 } from "crypto";
6335
6364
 
6336
6365
  // src/state.ts
@@ -7235,7 +7264,7 @@ function renderEnvironment(i) {
7235
7264
 
7236
7265
  // src/intel-bridge.ts
7237
7266
  import { existsSync as existsSync20, mkdirSync as mkdirSync14, readFileSync as readFileSync20, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "fs";
7238
- import { dirname as dirname10, join as join22 } from "path";
7267
+ import { dirname as dirname11, join as join22 } from "path";
7239
7268
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
7240
7269
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
7241
7270
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -7255,7 +7284,7 @@ function intelDir(stateDir, route2, ticket) {
7255
7284
  }
7256
7285
  var attachFilePath = (stateDir, route2, ticket) => join22(intelDir(stateDir, route2, ticket), "url");
7257
7286
  function writeAttachFile(file, url) {
7258
- mkdirSync14(dirname10(file), { recursive: true });
7287
+ mkdirSync14(dirname11(file), { recursive: true });
7259
7288
  writeFileSync15(file, `${url}
7260
7289
  `);
7261
7290
  }
@@ -9300,7 +9329,7 @@ function holdWarnings(tickets, ships, contract, humanHolds, now = Date.now()) {
9300
9329
 
9301
9330
  // src/run-worktree.ts
9302
9331
  import { existsSync as existsSync22 } from "fs";
9303
- import { dirname as dirname11, join as join24 } from "path";
9332
+ import { dirname as dirname12, join as join24 } from "path";
9304
9333
  var describeRunWorktreePlan = (p) => {
9305
9334
  switch (p.action) {
9306
9335
  case "cut-remote":
@@ -9357,7 +9386,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
9357
9386
  if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
9358
9387
  return { action: "recut", path: existing.path, branch, base: baseRef };
9359
9388
  }
9360
- const path = join24(dirname11(dir), worktreeForTicket(dir, cfg, t, role));
9389
+ const path = join24(dirname12(dir), worktreeForTicket(dir, cfg, t, role));
9361
9390
  if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
9362
9391
  return { action: "cut-remote", path, branch: located.branch };
9363
9392
  }
@@ -10631,7 +10660,7 @@ function skillMarkerWarnings(promptsDir, personaPrompts) {
10631
10660
  import { spawn as spawn3, execFile, execFileSync as execFileSync6 } from "child_process";
10632
10661
  import { mkdtempSync, mkdirSync as mkdirSync17, writeFileSync as writeFileSync18, rmSync as rmSync14 } from "fs";
10633
10662
  import { tmpdir as tmpdir2 } from "os";
10634
- import { basename as basename8, dirname as dirname12, join as join28 } from "path";
10663
+ import { basename as basename8, dirname as dirname13, join as join28 } from "path";
10635
10664
  import { promisify } from "util";
10636
10665
 
10637
10666
  // src/run-limit.ts
@@ -10760,7 +10789,7 @@ var SandboxRunner = class {
10760
10789
  throw new Error(`cannot generate the sandbox policy: ${e.message}`);
10761
10790
  }
10762
10791
  policyFile = policyPathFor(plan.streamPath);
10763
- mkdirSync17(dirname12(policyFile), { recursive: true });
10792
+ mkdirSync17(dirname13(policyFile), { recursive: true });
10764
10793
  writeFileSync18(policyFile, generated.yaml, { mode: 384 });
10765
10794
  }
10766
10795
  const create2 = ["sandbox", "create", "--name", name, "--detach"];
@@ -11022,7 +11051,7 @@ function codeBridgeFor(o, harness) {
11022
11051
  "--clone",
11023
11052
  o.ship.sandbox?.workDir ?? DEFAULT_SANDBOX_WORKDIR,
11024
11053
  "--state-dir",
11025
- dirname13(SANDBOX_INTEL_DIR),
11054
+ dirname14(SANDBOX_INTEL_DIR),
11026
11055
  "--ticket",
11027
11056
  number,
11028
11057
  ...seed ? ["--seed-cache", seed] : []
@@ -11036,7 +11065,7 @@ function codeBridgeFor(o, harness) {
11036
11065
  bridge.files = [{ path: bridge.settingsFile, content: text2 }];
11037
11066
  } else {
11038
11067
  const file = join29(intelDir(o.stateDir, o.route.route, number), "gemini-settings.json");
11039
- mkdirSync18(dirname13(file), { recursive: true });
11068
+ mkdirSync18(dirname14(file), { recursive: true });
11040
11069
  writeFileSync19(file, text2);
11041
11070
  bridge.settingsFile = file;
11042
11071
  }
@@ -11259,7 +11288,7 @@ async function killForDeadline(kill, pid, emit, role, ticket, spawnedAt, limitMi
11259
11288
  var withoutKey = ({ apiKey: _apiKey, ...rest }) => rest;
11260
11289
  function openSink(path) {
11261
11290
  try {
11262
- mkdirSync18(dirname13(path), { recursive: true });
11291
+ mkdirSync18(dirname14(path), { recursive: true });
11263
11292
  return createWriteStream(path, { flags: "a" });
11264
11293
  } catch {
11265
11294
  return void 0;
@@ -11611,7 +11640,7 @@ function spawnAgent(plan, emit, hintMoved) {
11611
11640
  const id = identityOf(plan, adapter.harness);
11612
11641
  let streamFd;
11613
11642
  try {
11614
- mkdirSync18(dirname13(plan.streamPath), { recursive: true });
11643
+ mkdirSync18(dirname14(plan.streamPath), { recursive: true });
11615
11644
  streamFd = openSync4(plan.streamPath, "a");
11616
11645
  } catch (e) {
11617
11646
  void endSink(eventsSink);
@@ -12830,7 +12859,7 @@ var SANDBOX_UID = 1e3;
12830
12859
  var CREW_PREFIX = "/opt/crew";
12831
12860
  var SANDBOX_BRIDGE_PATH = `${CREW_PREFIX}/bin/intel-bridge`;
12832
12861
  function readBridgeBundle() {
12833
- const here = dirname14(fileURLToPath(import.meta.url));
12862
+ const here = dirname15(fileURLToPath(import.meta.url));
12834
12863
  for (const p of [join32(here, "sandbox-intel-bridge.cjs"), join32(here, "..", "dist", "sandbox-intel-bridge.cjs")]) {
12835
12864
  if (existsSync30(p)) return readFileSync27(p, "utf8");
12836
12865
  }
@@ -13055,7 +13084,7 @@ async function buildSandboxImage(plan, deps) {
13055
13084
  try {
13056
13085
  writeFileSync20(join32(ctx, "Dockerfile"), layer.dockerfile);
13057
13086
  for (const [name, content] of Object.entries(layer.files)) {
13058
- mkdirSync19(dirname14(join32(ctx, name)), { recursive: true });
13087
+ mkdirSync19(dirname15(join32(ctx, name)), { recursive: true });
13059
13088
  writeFileSync20(join32(ctx, name), content);
13060
13089
  }
13061
13090
  const code = await deps.run(["build", "--tag", layer.tag, "--file", join32(ctx, "Dockerfile"), ctx], logPath);
@@ -13468,7 +13497,7 @@ function doctorSelftestLines(stored, toolchains) {
13468
13497
  }
13469
13498
 
13470
13499
  // src/cli.ts
13471
- import { resolve as resolve7, dirname as dirname33, join as join55, basename as basename16 } from "path";
13500
+ import { resolve as resolve7, dirname as dirname34, join as join55, basename as basename16 } from "path";
13472
13501
  import { fileURLToPath as fileURLToPath2 } from "url";
13473
13502
  import { hostname as hostname4, homedir as homedir11 } from "os";
13474
13503
  import { spawnSync as spawnSync11 } from "child_process";
@@ -13667,7 +13696,7 @@ function rosterMarkdown(roster, meId) {
13667
13696
 
13668
13697
  // src/connect-resume.ts
13669
13698
  import { mkdirSync as mkdirSync21, readFileSync as readFileSync29, rmSync as rmSync17, writeFileSync as writeFileSync22 } from "fs";
13670
- import { dirname as dirname15, join as join34 } from "path";
13699
+ import { dirname as dirname16, join as join34 } from "path";
13671
13700
  function lastSessionFile(stateDir) {
13672
13701
  return join34(stateDir, "connect-session.json");
13673
13702
  }
@@ -13687,7 +13716,7 @@ function readPointers(stateDir) {
13687
13716
  }
13688
13717
  function writePointers(stateDir, pointers) {
13689
13718
  const file = lastSessionFile(stateDir);
13690
- mkdirSync21(dirname15(file), { recursive: true });
13719
+ mkdirSync21(dirname16(file), { recursive: true });
13691
13720
  writeFileSync22(file, `${JSON.stringify(pointers, null, 2)}
13692
13721
  `, { mode: 384 });
13693
13722
  }
@@ -13774,7 +13803,7 @@ async function signOutSessions(o) {
13774
13803
 
13775
13804
  // src/reset.ts
13776
13805
  import { existsSync as existsSync32, mkdirSync as mkdirSync22, readFileSync as readFileSync30, readdirSync as readdirSync12, rmSync as rmSync18, statSync as statSync10, writeFileSync as writeFileSync23 } from "fs";
13777
- import { dirname as dirname16, join as join35 } from "path";
13806
+ import { dirname as dirname17, join as join35 } from "path";
13778
13807
  import { hostFromUrl as hostFromUrl3 } from "@tablation/client";
13779
13808
  function clonedCheckoutsFile(stateDir) {
13780
13809
  return join35(stateDir, "cloned-checkouts.json");
@@ -13791,7 +13820,7 @@ function recordClonedCheckout(stateDir, dir) {
13791
13820
  const known = readClonedCheckouts(stateDir);
13792
13821
  if (known.includes(dir)) return;
13793
13822
  const file = clonedCheckoutsFile(stateDir);
13794
- mkdirSync22(dirname16(file), { recursive: true });
13823
+ mkdirSync22(dirname17(file), { recursive: true });
13795
13824
  writeFileSync23(file, `${JSON.stringify([...known, dir], null, 2)}
13796
13825
  `);
13797
13826
  }
@@ -13974,7 +14003,7 @@ async function runDeprovision(o, fx, deps) {
13974
14003
  // src/pool-member.ts
13975
14004
  import { mkdirSync as mkdirSync23, readFileSync as readFileSync31, renameSync as renameSync4, writeFileSync as writeFileSync24 } from "fs";
13976
14005
  import { homedir as homedir8 } from "os";
13977
- import { dirname as dirname17, join as join36 } from "path";
14006
+ import { dirname as dirname18, join as join36 } from "path";
13978
14007
  var POOL_EXIT = { ok: 0, failed: 1, credential: 2 };
13979
14008
  function backoffMs(attempt) {
13980
14009
  return Math.min(6e4, 5e3 * 2 ** Math.max(0, attempt));
@@ -14218,7 +14247,7 @@ function fileMemberStore(dir) {
14218
14247
  }
14219
14248
  },
14220
14249
  write(f) {
14221
- mkdirSync23(dirname17(file), { recursive: true, mode: 448 });
14250
+ mkdirSync23(dirname18(file), { recursive: true, mode: 448 });
14222
14251
  const tmp = `${file}.tmp`;
14223
14252
  writeFileSync24(tmp, `${JSON.stringify(f)}
14224
14253
  `, { mode: 384 });
@@ -14279,7 +14308,7 @@ import { hostname as hostname3 } from "os";
14279
14308
  import { execFileSync as execFileSync10 } from "child_process";
14280
14309
  import { createHash as createHash7 } from "crypto";
14281
14310
  import { existsSync as existsSync33, mkdirSync as mkdirSync24, readFileSync as readFileSync32, writeFileSync as writeFileSync25 } from "fs";
14282
- import { dirname as dirname18, join as join37 } from "path";
14311
+ import { dirname as dirname19, join as join37 } from "path";
14283
14312
  function machineIdPathFor(stateDir) {
14284
14313
  return join37(stateDir, "machine-id");
14285
14314
  }
@@ -14313,7 +14342,7 @@ function ensureMachineId(stateDir, opts = {}) {
14313
14342
  const raw = (opts.read ?? readRawMachineId)();
14314
14343
  if (!raw) return void 0;
14315
14344
  const hashed = sha256(raw);
14316
- mkdirSync24(dirname18(path), { recursive: true });
14345
+ mkdirSync24(dirname19(path), { recursive: true });
14317
14346
  writeFileSync25(path, `${hashed}
14318
14347
  `, { mode: 384 });
14319
14348
  return hashed;
@@ -16594,7 +16623,7 @@ async function resolveOne(tracker, t, seat, approvedStatus, buildingStatus, affi
16594
16623
 
16595
16624
  // src/repo-refresh.ts
16596
16625
  import { existsSync as existsSync37, mkdirSync as mkdirSync26, readFileSync as readFileSync35, writeFileSync as writeFileSync29 } from "fs";
16597
- import { dirname as dirname20 } from "path";
16626
+ import { dirname as dirname21 } from "path";
16598
16627
  var REFRESH_COOLDOWN_MS = 2 * 6e4;
16599
16628
  var UNSERVABLE_PASSES_BEFORE_ATTENTION = 3;
16600
16629
  var lastAsked = /* @__PURE__ */ new Map();
@@ -16626,7 +16655,7 @@ function persistRepoRows(stateDir, routeName, rows) {
16626
16655
  if (!existsSync37(path)) return;
16627
16656
  const raw = JSON.parse(readFileSync35(path, "utf8"));
16628
16657
  applyRepoRows(raw, rows);
16629
- mkdirSync26(dirname20(path), { recursive: true });
16658
+ mkdirSync26(dirname21(path), { recursive: true });
16630
16659
  writeFileSync29(path, `${JSON.stringify(raw, null, 2)}
16631
16660
  `);
16632
16661
  }
@@ -17077,7 +17106,7 @@ Ship ${shipName2} found: ${problem}. The release refuses to squash a ref nobody
17077
17106
  var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "local_ref_skip" && (e.message ?? "").includes(problem));
17078
17107
 
17079
17108
  // src/connect-wizard.ts
17080
- import { delimiter, dirname as dirname21, join as join41 } from "path";
17109
+ import { delimiter, dirname as dirname22, join as join41 } from "path";
17081
17110
  import { accessSync as accessSync3, constants as constants3, existsSync as existsSync38, mkdirSync as mkdirSync27, writeFileSync as writeFileSync30 } from "fs";
17082
17111
  import { stringify as stringify3 } from "yaml";
17083
17112
  var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
@@ -17126,9 +17155,9 @@ async function runWizard(p, env) {
17126
17155
  if (entered && isExec(entered)) agentBin = entered;
17127
17156
  else p.say(entered ? `${entered} is not an executable file.` : "A path is required.");
17128
17157
  }
17129
- const nodeDir = dirname21(env.nodePath);
17158
+ const nodeDir = dirname22(env.nodePath);
17130
17159
  const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
17131
- const baseDirs = [nodeDir, pnpmPath ? dirname21(pnpmPath) : void 0];
17160
+ const baseDirs = [nodeDir, pnpmPath ? dirname22(pnpmPath) : void 0];
17132
17161
  p.say(
17133
17162
  `
17134
17163
  Host Passengers lets this machine run the agent sessions that other people's
@@ -17139,7 +17168,7 @@ Tablation workspaces ask for, in a container. More: ${HOST_PASSENGERS_DOCS_URL}`
17139
17168
  p.say("Docker is not available here, so nothing will be hosted until it is. Your answer is recorded anyway.");
17140
17169
  }
17141
17170
  const dockerPath = hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
17142
- const extraPath = deriveExtraPath([...baseDirs, dockerPath ? dirname21(dockerPath) : void 0]);
17171
+ const extraPath = deriveExtraPath([...baseDirs, dockerPath ? dirname22(dockerPath) : void 0]);
17143
17172
  if (extraPath) {
17144
17173
  p.say(`Hooks will get ${extraPath} on their PATH (where node, pnpm${dockerPath ? " and docker" : ""} live).`);
17145
17174
  }
@@ -17161,7 +17190,7 @@ routes:
17161
17190
  ${routeBlock}`;
17162
17191
  }
17163
17192
  function writeNewConfig(path, text2) {
17164
- mkdirSync27(dirname21(path), { recursive: true });
17193
+ mkdirSync27(dirname22(path), { recursive: true });
17165
17194
  try {
17166
17195
  writeFileSync30(path, text2, { mode: 384, flag: "wx" });
17167
17196
  return true;
@@ -17197,7 +17226,7 @@ Wrote ${o.configPath}.
17197
17226
  // src/connect-json.ts
17198
17227
  import { accessSync as accessSync4, constants as constants4 } from "fs";
17199
17228
  import { homedir as homedir10 } from "os";
17200
- import { dirname as dirname22, join as join42 } from "path";
17229
+ import { dirname as dirname23, join as join42 } from "path";
17201
17230
  import { delimiter as delimiter2 } from "path";
17202
17231
  var CHOICE_EXIT_CODE = 3;
17203
17232
  function createEmitter(json2, write) {
@@ -17263,7 +17292,7 @@ function flagWizardAnswers(f, env) {
17263
17292
  const agentBin = f.agentBin ?? found?.bin;
17264
17293
  const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
17265
17294
  const dockerPath = f.hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
17266
- const extraPath = f.extraPath ?? deriveExtraPath([dirname22(env.nodePath), pnpmPath ? dirname22(pnpmPath) : void 0, dockerPath ? dirname22(dockerPath) : void 0]);
17295
+ const extraPath = f.extraPath ?? deriveExtraPath([dirname23(env.nodePath), pnpmPath ? dirname23(pnpmPath) : void 0, dockerPath ? dirname23(dockerPath) : void 0]);
17267
17296
  return {
17268
17297
  answers: {
17269
17298
  name: f.shipName?.trim() || env.hostname,
@@ -17278,7 +17307,7 @@ function flagWizardAnswers(f, env) {
17278
17307
 
17279
17308
  // src/release-run.ts
17280
17309
  import { readFileSync as readFileSync36, writeFileSync as writeFileSync31, existsSync as existsSync39, copyFileSync, mkdirSync as mkdirSync28, mkdtempSync as mkdtempSync3, rmSync as rmSync19 } from "fs";
17281
- import { dirname as dirname23, join as join43 } from "path";
17310
+ import { dirname as dirname24, join as join43 } from "path";
17282
17311
  import { tmpdir as tmpdir5 } from "os";
17283
17312
 
17284
17313
  // src/release.ts
@@ -18025,7 +18054,13 @@ async function bump(o, size) {
18025
18054
  o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
18026
18055
  return null;
18027
18056
  }
18028
- const current = JSON.parse(readFileSync36(first, "utf8")).version;
18057
+ const firstText = readFileSync36(first, "utf8");
18058
+ const problem = versionFileProblem(o.repo.release.versionFiles[0], firstText);
18059
+ if (problem) {
18060
+ o.emit.error(problem);
18061
+ return null;
18062
+ }
18063
+ const current = JSON.parse(firstText).version;
18029
18064
  if (!current) {
18030
18065
  o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
18031
18066
  return null;
@@ -18164,7 +18199,7 @@ function openReleaseCheckout(o) {
18164
18199
  for (const f of o.repo.worktrees.copy) {
18165
18200
  const src = join43(o.cwd, f);
18166
18201
  if (!existsSync39(src)) continue;
18167
- mkdirSync28(dirname23(join43(dir, f)), { recursive: true });
18202
+ mkdirSync28(dirname24(join43(dir, f)), { recursive: true });
18168
18203
  copyFileSync(src, join43(dir, f));
18169
18204
  }
18170
18205
  return { dir, branch, close };
@@ -18493,6 +18528,20 @@ async function mergeAndRelease(o, decision, fresh, releaseBranch, isolated = fal
18493
18528
  }
18494
18529
  merged.push(c);
18495
18530
  }
18531
+ if (applied > 0 && !o.dryRun && !o.mergeOnly && o.reloadRepo) {
18532
+ const changed2 = gitOk(o.cwd, ["diff", "--name-only", preMerge, "HEAD", "--", ...REPO_CONFIG_FILENAMES]);
18533
+ if (changed2) {
18534
+ try {
18535
+ const next = o.reloadRepo(o.cwd);
18536
+ if (next) {
18537
+ o = { ...o, repo: next };
18538
+ o.emit.emit(`${changed2.split("\n")[0]} changed in this release \u2014 continuing under the merged contract`);
18539
+ }
18540
+ } catch (e) {
18541
+ o.emit.warn(`could not re-read the merged repo config: ${e.message}; releasing under the earlier one`);
18542
+ }
18543
+ }
18544
+ }
18496
18545
  if (o.mergeOnly) {
18497
18546
  o.emit.emit(`merge only: ${merged.length} branch(es) merged, not releasing`);
18498
18547
  return { merged, conflicts, unbuildable, deployed: false, stopped: "merge only", decision };
@@ -18517,7 +18566,8 @@ async function mergeAndRelease(o, decision, fresh, releaseBranch, isolated = fal
18517
18566
  o.emit.emit("nothing to release");
18518
18567
  return { merged, conflicts, unbuildable, deployed: false, stopped: "nothing to release", decision };
18519
18568
  }
18520
- if (merged.length === 0 && decision.unseeded && !o.force) {
18569
+ const waiting = decision.merges.some((c) => c.skipReason === "already-merged" && c.mergedSha);
18570
+ if (merged.length === 0 && decision.unseeded && !waiting && !o.force) {
18521
18571
  o.emit.emit(
18522
18572
  `nothing merged, and ${o.repo.branch.base} carries no release tag to compare against \u2014 nothing to release`
18523
18573
  );
@@ -19489,7 +19539,7 @@ function nextRoles(decision, ran) {
19489
19539
 
19490
19540
  // src/ship-name.ts
19491
19541
  import { existsSync as existsSync41, mkdirSync as mkdirSync29, readFileSync as readFileSync37, renameSync as renameSync5, writeFileSync as writeFileSync32 } from "fs";
19492
- import { dirname as dirname24 } from "path";
19542
+ import { dirname as dirname25 } from "path";
19493
19543
  import { parseDocument as parseDocument5 } from "yaml";
19494
19544
  var ShipNameError = class extends Error {
19495
19545
  };
@@ -19568,7 +19618,7 @@ function writeSyncedName(resolvedFile, name) {
19568
19618
  }
19569
19619
  if (raw.shipName === name) return;
19570
19620
  raw.shipName = name;
19571
- mkdirSync29(dirname24(resolvedFile), { recursive: true });
19621
+ mkdirSync29(dirname25(resolvedFile), { recursive: true });
19572
19622
  const tmp = `${resolvedFile}.tmp${process.pid}`;
19573
19623
  writeFileSync32(tmp, `${JSON.stringify(raw, null, 2)}
19574
19624
  `);
@@ -20262,7 +20312,7 @@ function describeCapability(i) {
20262
20312
 
20263
20313
  // src/agents-sync-run.ts
20264
20314
  import { existsSync as existsSync42, mkdirSync as mkdirSync30, readFileSync as readFileSync38, writeFileSync as writeFileSync33 } from "fs";
20265
- import { dirname as dirname25 } from "path";
20315
+ import { dirname as dirname26 } from "path";
20266
20316
  async function runAgentsSync(target, o) {
20267
20317
  const dryRun = o.dryRun ?? false;
20268
20318
  const force = o.force ?? false;
@@ -20299,7 +20349,7 @@ async function runAgentsSync(target, o) {
20299
20349
  raw.agentPersonas = personaResult.agentPersonas;
20300
20350
  if (skillResult) raw.agentSkills = skillResult.agentSkills;
20301
20351
  if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
20302
- mkdirSync30(dirname25(resolvedPath), { recursive: true });
20352
+ mkdirSync30(dirname26(resolvedPath), { recursive: true });
20303
20353
  writeFileSync33(resolvedPath, `${JSON.stringify(raw, null, 2)}
20304
20354
  `);
20305
20355
  if (target.resolved) {
@@ -20922,7 +20972,7 @@ ${run3.err}` });
20922
20972
  }
20923
20973
 
20924
20974
  // src/sample-ticket.ts
20925
- import { basename as basename12, dirname as dirname26, join as join44 } from "path";
20975
+ import { basename as basename12, dirname as dirname27, join as join44 } from "path";
20926
20976
  var SAMPLE_REPO = "crew-sample";
20927
20977
  var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
20928
20978
  var SAMPLE_MARKER = "sample: true";
@@ -20945,7 +20995,7 @@ has something safe to work on: its test and build hooks always pass, it has no
20945
20995
  remote, and \`crew sample-ticket --remove\` deletes it.
20946
20996
  `;
20947
20997
  function sampleDir(existing, reposBase, workspace) {
20948
- if (existing.length > 0) return join44(dirname26(existing[0]), SAMPLE_REPO);
20998
+ if (existing.length > 0) return join44(dirname27(existing[0]), SAMPLE_REPO);
20949
20999
  return join44(reposBase, workspace, SAMPLE_REPO);
20950
21000
  }
20951
21001
  var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
@@ -20991,7 +21041,7 @@ function planSampleTicket(i) {
20991
21041
 
20992
21042
  // src/worktree-sweep.ts
20993
21043
  import { existsSync as existsSync43, readFileSync as readFileSync40, readdirSync as readdirSync14 } from "fs";
20994
- import { basename as basename13, dirname as dirname27, join as join45, resolve as resolve5 } from "path";
21044
+ import { basename as basename13, dirname as dirname28, join as join45, resolve as resolve5 } from "path";
20995
21045
  function planWorktreeSweep(target, tickets, contract) {
20996
21046
  const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
20997
21047
  const verified = contract.statuses.verified;
@@ -21055,7 +21105,7 @@ function worktreeTicketNumbers(target) {
21055
21105
  return [...nums];
21056
21106
  }
21057
21107
  function siblingWorktreeDirs(target) {
21058
- const parent = dirname27(target.dir.replace(/[/\\]+$/, ""));
21108
+ const parent = dirname28(target.dir.replace(/[/\\]+$/, ""));
21059
21109
  const stem = `${basename13(target.dir.replace(/[/\\]+$/, ""))}-`;
21060
21110
  const registered = new Set(worktrees(target.dir).map((w) => resolve5(w.path)));
21061
21111
  let names;
@@ -21907,7 +21957,7 @@ import { dirname as dirOf2, resolve as resolvePath } from "path";
21907
21957
  // src/ssh-keys.ts
21908
21958
  import { execFileSync as execFileSync13 } from "child_process";
21909
21959
  import { existsSync as existsSync46, mkdirSync as mkdirSync32, readFileSync as readFileSync42, chmodSync as chmodSync3 } from "fs";
21910
- import { dirname as dirname28, join as join49 } from "path";
21960
+ import { dirname as dirname29, join as join49 } from "path";
21911
21961
  var SshKeyError = class extends Error {
21912
21962
  };
21913
21963
  function shipSshKeyPathFor(stateDir) {
@@ -21917,7 +21967,7 @@ function ensureShipSshKeypair(stateDir) {
21917
21967
  const privateKeyPath = shipSshKeyPathFor(stateDir);
21918
21968
  const publicKeyPath = `${privateKeyPath}.pub`;
21919
21969
  if (!existsSync46(privateKeyPath)) {
21920
- mkdirSync32(dirname28(privateKeyPath), { recursive: true });
21970
+ mkdirSync32(dirname29(privateKeyPath), { recursive: true });
21921
21971
  try {
21922
21972
  execFileSync13("ssh-keygen", [
21923
21973
  "-t",
@@ -22109,7 +22159,7 @@ import { join as join50 } from "path";
22109
22159
 
22110
22160
  // src/passenger-mirror.ts
22111
22161
  import { mkdirSync as mkdirSync33 } from "fs";
22112
- import { dirname as dirname29 } from "path";
22162
+ import { dirname as dirname30 } from "path";
22113
22163
  function mirrorRepoOf(spec) {
22114
22164
  const fromOrigin = spec.originUrl ? parseRemote(spec.originUrl) : null;
22115
22165
  if (fromOrigin) return fromOrigin;
@@ -22127,7 +22177,7 @@ async function materializeMirrorCheckouts(specs, cacheFor = (s) => new SandboxCa
22127
22177
  continue;
22128
22178
  }
22129
22179
  try {
22130
- const parent = dirname29(spec.dir);
22180
+ const parent = dirname30(spec.dir);
22131
22181
  mkdirSync33(parent, { recursive: true });
22132
22182
  mkdirSync33(spec.dir, { recursive: true });
22133
22183
  const r = await cacheFor(spec).materialize(repo, spec.base, parent, spec.name);
@@ -22333,7 +22383,7 @@ import { createServer, request as httpRequest } from "http";
22333
22383
  import { request as httpsRequest } from "https";
22334
22384
  import { connect as netConnect } from "net";
22335
22385
  import { connect as tlsConnect } from "tls";
22336
- import { dirname as dirname30, isAbsolute as isAbsolute4 } from "path";
22386
+ import { dirname as dirname31, isAbsolute as isAbsolute4 } from "path";
22337
22387
  import { brotliDecompressSync, gunzipSync, inflateSync } from "zlib";
22338
22388
 
22339
22389
  // src/periscope-inspector.ts
@@ -22447,7 +22497,7 @@ function parsePointedBody(raw) {
22447
22497
  return entry;
22448
22498
  }
22449
22499
  function appendPointed(file, entry, now = /* @__PURE__ */ new Date()) {
22450
- mkdirSync35(dirname30(file), { recursive: true });
22500
+ mkdirSync35(dirname31(file), { recursive: true });
22451
22501
  appendFileSync3(file, `${JSON.stringify({ ...entry, at: now.toISOString() })}
22452
22502
  `);
22453
22503
  }
@@ -23465,7 +23515,7 @@ import {
23465
23515
  } from "fs";
23466
23516
  import { createConnection } from "net";
23467
23517
  import { createServer as createServer2 } from "http";
23468
- import { basename as basename14, dirname as dirname31, join as join52 } from "path";
23518
+ import { basename as basename14, dirname as dirname32, join as join52 } from "path";
23469
23519
  import { execFile as execFile3, spawn as spawn12 } from "child_process";
23470
23520
 
23471
23521
  // src/periscope-control.ts
@@ -23781,7 +23831,7 @@ function candidates(live, rotated) {
23781
23831
  const out = [live];
23782
23832
  if (!rotated) return out;
23783
23833
  try {
23784
- for (const n of readdirSync18(dirname31(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join52(dirname31(live), n));
23834
+ for (const n of readdirSync18(dirname32(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join52(dirname32(live), n));
23785
23835
  } catch {
23786
23836
  }
23787
23837
  return out;
@@ -23941,7 +23991,7 @@ async function tailReply(path, q2, rotated, abort) {
23941
23991
  const deadline = Date.now() + Math.min(wait, MAX_WAIT_SECONDS) * 1e3;
23942
23992
  let r = readTail(path, { after, tail: tail3, rotated });
23943
23993
  while (after && !r.lines.length && !r.reset && Date.now() < deadline && !abort.aborted) {
23944
- await waitForChange(dirname31(path), deadline - Date.now(), abort);
23994
+ await waitForChange(dirname32(path), deadline - Date.now(), abort);
23945
23995
  r = readTail(path, { after: r.cursor, rotated });
23946
23996
  }
23947
23997
  return reply2(200, r);
@@ -24131,7 +24181,7 @@ function crewJson(args, timeoutMs = 6e4) {
24131
24181
 
24132
24182
  // src/periscope-mcp.ts
24133
24183
  import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
24134
- import { basename as basename15, dirname as dirname32, isAbsolute as isAbsolute5, join as join54, relative as relative2, resolve as resolve6, sep } from "path";
24184
+ import { basename as basename15, dirname as dirname33, isAbsolute as isAbsolute5, join as join54, relative as relative2, resolve as resolve6, sep } from "path";
24135
24185
  import { z } from "zod";
24136
24186
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
24137
24187
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
@@ -24269,10 +24319,10 @@ async function realAncestor(p) {
24269
24319
  }
24270
24320
  if (isLink) {
24271
24321
  if (++hops > 40) throw new PathRefusal(`too many symbolic links resolving ${p}`);
24272
- cur = resolve6(dirname32(cur), await readlink(cur));
24322
+ cur = resolve6(dirname33(cur), await readlink(cur));
24273
24323
  continue;
24274
24324
  }
24275
- const parent = dirname32(cur);
24325
+ const parent = dirname33(cur);
24276
24326
  if (parent === cur) throw new PathRefusal(`cannot resolve ${p}`);
24277
24327
  tail3.push(basename15(cur));
24278
24328
  cur = parent;
@@ -24383,7 +24433,7 @@ function createPeriscopeMcpServer(d, sc) {
24383
24433
  if (Buffer.byteLength(content) > MAX_WRITE_BYTES) return text(`content exceeds ${MAX_WRITE_BYTES} bytes`, true);
24384
24434
  const abs = await inTree(path);
24385
24435
  if (abs === await realpath(root)) return text("path must name a file", true);
24386
- await mkdir(dirname32(abs), { recursive: true });
24436
+ await mkdir(dirname33(abs), { recursive: true });
24387
24437
  await inTree(path);
24388
24438
  await writeFile(abs, content);
24389
24439
  return text(`Wrote ${Buffer.byteLength(content)} bytes to ${await rel(abs)}`);
@@ -24423,7 +24473,7 @@ function createPeriscopeMcpServer(d, sc) {
24423
24473
  inputSchema: { path: z.string() },
24424
24474
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
24425
24475
  }, async ({ path }) => {
24426
- const parent = await inTree(dirname32(path) === "" ? "." : dirname32(path));
24476
+ const parent = await inTree(dirname33(path) === "" ? "." : dirname33(path));
24427
24477
  const abs = join54(parent, basename15(path));
24428
24478
  if (basename15(path) === ".git" || abs === parent) return text("path must name a file", true);
24429
24479
  const st = await lstat(abs).catch(() => void 0);
@@ -24545,7 +24595,7 @@ function findPeriscope(sidecars, segment) {
24545
24595
  }
24546
24596
 
24547
24597
  // src/cli.ts
24548
- var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname33(process.execPath) : resolve7(dirname33(fileURLToPath2(import.meta.url)), "..");
24598
+ var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname34(process.execPath) : resolve7(dirname34(fileURLToPath2(import.meta.url)), "..");
24549
24599
  var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
24550
24600
  var CYCLE_SECONDS2 = 120;
24551
24601
  var STALE_CYCLES = 3;
@@ -25828,6 +25878,13 @@ ${changed2.map((c) => ` - ${c}
25828
25878
  const outcome = await runRelease({
25829
25879
  cwd: target.dir,
25830
25880
  repo,
25881
+ reloadRepo: (dir) => resolveRepoConfig(loadRepoConfig(dir), {
25882
+ hooks: { ...c.hooks, ...o?.hooks },
25883
+ labels: { ...c.labels, ...o?.labels },
25884
+ release: mergeRouteRelease(c.release, o?.release),
25885
+ branch: { ...c.branch, ...o?.branch },
25886
+ worktrees: { prefix: shipWorktreePrefixFor(c), nameOverride: o?.worktreeName }
25887
+ }, dir),
25831
25888
  contract: tracker.contract,
25832
25889
  tickets,
25833
25890
  blockerInfo,
@@ -25872,7 +25929,12 @@ ${changed2.map((c) => ` - ${c}
25872
25929
  tracker.contract,
25873
25930
  outcome.stampFrom ?? outcome.decision.lastReleased,
25874
25931
  outcome.decision.head,
25875
- new Map(outcome.merged.map((m) => [m.ticket.issue_id, m.sha]))
25932
+ new Map([
25933
+ // Squashed onto an untagged base by an earlier cycle: no release range
25934
+ // names it, so the first release carries it out here (CREW-2925).
25935
+ ...outcome.decision.merges.filter((m) => m.skipReason === "already-merged" && m.mergedSha).map((m) => [m.ticket.issue_id, m.mergedSha]),
25936
+ ...outcome.merged.map((m) => [m.ticket.issue_id, m.sha])
25937
+ ])
25876
25938
  );
25877
25939
  if (plan.length) await applyStamp(tracker, plan, outcome.version, tracker.contract, remit, dryRun, target.dir);
25878
25940
  } else if (outcome.stopped) {
@@ -27082,7 +27144,7 @@ ${cu.tail.trimEnd()}
27082
27144
  const found = cfg.routes.find((r) => r.route === routeArg);
27083
27145
  if (!found) throw new ReposError(`no route "${routeArg}" in the config (have: ${cfg.routes.map((r) => r.route).join(", ") || "none"})`);
27084
27146
  const named2 = value("route") ? void 0 : positional[3];
27085
- const dir = named2 ? repoDirOf(configText, dirname33(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
27147
+ const dir = named2 ? repoDirOf(configText, dirname34(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
27086
27148
  const target = reposOf(found).find((t) => resolvePath(t.dir) === dir || t.name === named2);
27087
27149
  const o = target ? found.repoOverrides[target.name] : void 0;
27088
27150
  runReposCheck(dir, {
@@ -27106,7 +27168,7 @@ ${cu.tail.trimEnd()}
27106
27168
  if (sub === "hooks") {
27107
27169
  try {
27108
27170
  const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
27109
- const dir = repoDirOf(configText, dirname33(cfg.configFile), a.route, a.name);
27171
+ const dir = repoDirOf(configText, dirname34(cfg.configFile), a.route, a.name);
27110
27172
  if (!existsSync51(dir)) throw new ReposError(`${dir} does not exist on disk`);
27111
27173
  const files = { exists: (r) => existsSync51(resolvePath(dir, r)), list: (r) => {
27112
27174
  try {
@@ -27273,7 +27335,7 @@ ${cu.tail.trimEnd()}
27273
27335
  }
27274
27336
  const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
27275
27337
  const text2 = configText;
27276
- const base = dirname33(cfg.configFile);
27338
+ const base = dirname34(cfg.configFile);
27277
27339
  const isCheckout = (d) => existsSync51(`${d}/.git`);
27278
27340
  try {
27279
27341
  if (sub === "list") {
@@ -27288,7 +27350,8 @@ ${cu.tail.trimEnd()}
27288
27350
  isCheckout,
27289
27351
  repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
27290
27352
  try {
27291
- return Object.keys(parseRepoConfig(readFileSync46(resolvePath(d, f), "utf8"), f).hooks).length > 0;
27353
+ const path = resolvePath(d, f);
27354
+ return Object.keys(parseRepoConfig(readFileSync46(path, "utf8"), path).hooks).length > 0;
27292
27355
  } catch {
27293
27356
  return false;
27294
27357
  }
@@ -27951,7 +28014,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
27951
28014
  process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
27952
28015
  `);
27953
28016
  } else {
27954
- mkdirSync38(dirname33(resolvedPath), { recursive: true });
28017
+ mkdirSync38(dirname34(resolvedPath), { recursive: true });
27955
28018
  if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
27956
28019
  const tmpPath = `${resolvedPath}.tmp${process.pid}`;
27957
28020
  writeFileSync38(tmpPath, `${JSON.stringify(resolved, null, 2)}
@@ -28000,7 +28063,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
28000
28063
  process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
28001
28064
  `);
28002
28065
  } else {
28003
- mkdirSync38(dirname33(apiKeyFile), { recursive: true });
28066
+ mkdirSync38(dirname34(apiKeyFile), { recursive: true });
28004
28067
  writeFileSync38(apiKeyFile, `${apiKeyVar}=${apiKey}
28005
28068
  `, { mode: 384 });
28006
28069
  process.stderr.write(`API key written to ${apiKeyFile}
@@ -29473,7 +29536,7 @@ ${where.join("\n")}`);
29473
29536
  if (!route2.resolved) fail2(`route ${route2.route} is not connected \u2014 run \`crew connect\` first`);
29474
29537
  const tracker = new Tracker(route2, cfg.ship);
29475
29538
  const configText = readFileSync46(cfg.configFile, "utf8");
29476
- const cfgBase = dirname33(cfg.configFile);
29539
+ const cfgBase = dirname34(cfg.configFile);
29477
29540
  const configured2 = configuredRepos(configText, route2.route, cfgBase);
29478
29541
  const dir = configured2.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured2.map((r) => r.dir), route2.reposBasePath, route2.route.split("/")[0]);
29479
29542
  const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route2.route);
@@ -24682,6 +24682,19 @@ var SPEC_VERSION = 1;
24682
24682
  var REPO_CONFIG_FILENAMES = [".crew.yaml", ".crew.yml", ".crew.json"];
24683
24683
  var RepoConfigError = class extends Error {
24684
24684
  };
24685
+ function versionFileProblem(name, text) {
24686
+ const remedy = `a version file that is not a JSON object with a string "version" needs hooks.bump instead of release.versionFiles (docs/REPO_SPEC.md)`;
24687
+ let doc;
24688
+ try {
24689
+ doc = JSON.parse(text);
24690
+ } catch {
24691
+ return `release.versionFiles entry "${name}" is not a JSON document \u2014 ${remedy}`;
24692
+ }
24693
+ if (!doc || typeof doc !== "object" || Array.isArray(doc) || typeof doc.version !== "string") {
24694
+ return `release.versionFiles entry "${name}" has no string "version" field \u2014 ${remedy}`;
24695
+ }
24696
+ return null;
24697
+ }
24685
24698
  var HOOK_NAMES = [
24686
24699
  "test",
24687
24700
  "build",
@@ -25015,6 +25028,22 @@ function parseRepoConfig(text, file) {
25015
25028
  if (versionFiles.length === 0) {
25016
25029
  throw new RepoConfigError(`${file}: release.versionFiles cannot be empty`);
25017
25030
  }
25031
+ if (versionFilesGiven) {
25032
+ for (const name of versionFiles) {
25033
+ const path = (0, import_node_path6.join)((0, import_node_path6.dirname)(file), name);
25034
+ if (!(0, import_node_fs5.existsSync)(path)) continue;
25035
+ let text2;
25036
+ try {
25037
+ text2 = (0, import_node_fs5.readFileSync)(path, "utf8");
25038
+ } catch (e) {
25039
+ throw new RepoConfigError(
25040
+ `${file}: release.versionFiles entry "${name}" cannot be read (${e.code ?? "read error"})`
25041
+ );
25042
+ }
25043
+ const problem = versionFileProblem(name, text2);
25044
+ if (problem) throw new RepoConfigError(`${file}: ${problem}`);
25045
+ }
25046
+ }
25018
25047
  const inspectorRaw = raw.periscope?.inspector;
25019
25048
  if (inspectorRaw !== void 0 && inspectorRaw !== "proxy" && inspectorRaw !== "repo") {
25020
25049
  throw new RepoConfigError(`${file}: periscope.inspector must be "proxy" or "repo"`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tablation/crew",
3
- "version": "0.126.0",
3
+ "version": "0.126.2",
4
4
  "description": "A standing team of headless agents that picks work off a Tablation board, does it on a machine you control, and reports back.",
5
5
  "type": "module",
6
6
  "license": "MIT",