@tablation/crew 0.126.2 → 0.126.3

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 +135 -43
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -3263,6 +3263,15 @@ function loadRepoConfig(dir) {
3263
3263
  if (!file) return null;
3264
3264
  return parseRepoConfig(readFileSync14(file, "utf8"), file);
3265
3265
  }
3266
+ function loadRepoConfigTolerant(dir, refused) {
3267
+ try {
3268
+ return loadRepoConfig(dir);
3269
+ } catch (e) {
3270
+ if (!(e instanceof RepoConfigError)) throw e;
3271
+ refused?.(e);
3272
+ return null;
3273
+ }
3274
+ }
3266
3275
  function hookLabel(cfg, hook2) {
3267
3276
  return cfg.labels[hook2] ?? cfg.hooks[hook2] ?? hook2;
3268
3277
  }
@@ -6016,6 +6025,15 @@ function seatKeyUnreadableItem(route2, seat, storeError, nowIso = (/* @__PURE__
6016
6025
  message: `${route2}: the ${seat} seat's agent key cannot be read (${storeError}) \u2014 its runs are refused until the key store is readable. Resumes automatically once it is; \`crew doctor\` names the store in use`
6017
6026
  };
6018
6027
  }
6028
+ var repoContractRefusedKey = (repo) => `repo_contract_refused:${repo}`;
6029
+ function repoContractRefusedItem(repo, detail, nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
6030
+ return {
6031
+ kind: "repo_contract_refused",
6032
+ key: repoContractRefusedKey(repo),
6033
+ since: nowIso,
6034
+ message: `${repo}: its repo contract is refused and the repository is skipped \u2014 ${detail}. Resumes automatically once the file is valid on its base; \`crew doctor\` names the problem`
6035
+ };
6036
+ }
6019
6037
  var agentLogRefusedKey = (route2) => `agent_log_refused:${route2}`;
6020
6038
  function agentLogRefusedItem(route2, role, status2, nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
6021
6039
  return {
@@ -6129,7 +6147,7 @@ function recheckBaseStops(state, route2) {
6129
6147
  for (const item of open) {
6130
6148
  const target = reposOf(route2).find((t) => baseUnsafeKey(t.name) === item.key);
6131
6149
  if (!target) continue;
6132
- const cfg = resolveRepoConfig(loadRepoConfig(target.dir), void 0, target.dir);
6150
+ const cfg = resolveRepoConfig(loadRepoConfigTolerant(target.dir), void 0, target.dir);
6133
6151
  const outcome = refreshBaseBranch(target.dir, cfg.branch.remote, cfg.branch.base, true);
6134
6152
  if (outcome.action === "level" || outcome.action === "would-fast-forward") {
6135
6153
  clearShipAttention(state, item.key);
@@ -12805,7 +12823,7 @@ function syncPassengerContainers(cfg, tablationApiBaseUrl, exec = realExec2, syn
12805
12823
  }
12806
12824
  return { started, recreated, stopped };
12807
12825
  }
12808
- function passengerCheckoutSyncSpecs(cfg, workspaceId) {
12826
+ function passengerCheckoutSyncSpecs(cfg, workspaceId, onRefused) {
12809
12827
  const specs = [];
12810
12828
  for (const route2 of cfg.routes) {
12811
12829
  if (!route2.enabled || !route2.hostPassengers || !route2.resolved?.workspaceId) continue;
@@ -12817,8 +12835,14 @@ function passengerCheckoutSyncSpecs(cfg, workspaceId) {
12817
12835
  for (const target of passengerCheckoutTargets(route2, mirrorArea)) {
12818
12836
  const ordinary = ordinaryByName.get(target.name);
12819
12837
  const override = route2.repoOverrides[target.name];
12838
+ let refused = false;
12839
+ const declared = ordinary ? loadRepoConfigTolerant(ordinary.dir, (e) => {
12840
+ refused = true;
12841
+ onRefused?.(target.name, e);
12842
+ }) : null;
12843
+ if (refused) continue;
12820
12844
  const effective = resolveRepoConfig(
12821
- ordinary ? loadRepoConfig(ordinary.dir) : null,
12845
+ declared,
12822
12846
  { branch: { ...route2.branch, ...override?.branch } },
12823
12847
  ordinary?.dir ?? target.dir
12824
12848
  );
@@ -12838,8 +12862,8 @@ function passengerCheckoutSyncSpecs(cfg, workspaceId) {
12838
12862
  }
12839
12863
  return specs;
12840
12864
  }
12841
- function syncAllPassengerCheckouts(cfg) {
12842
- return passengerCheckoutSyncSpecs(cfg).filter((spec) => !spec.mirror).map((spec) => ({
12865
+ function syncAllPassengerCheckouts(cfg, onRefused) {
12866
+ return passengerCheckoutSyncSpecs(cfg, void 0, onRefused).filter((spec) => !spec.mirror).map((spec) => ({
12843
12867
  name: spec.name,
12844
12868
  workspaceId: spec.workspaceId,
12845
12869
  outcome: syncPassengerCheckout(spec.dir, spec.remote, spec.gitRemoteName, spec.base, { originUrl: spec.originUrl, scheme: spec.cloneScheme, host: spec.forgeHost })
@@ -13497,7 +13521,7 @@ function doctorSelftestLines(stored, toolchains) {
13497
13521
  }
13498
13522
 
13499
13523
  // src/cli.ts
13500
- import { resolve as resolve7, dirname as dirname34, join as join55, basename as basename16 } from "path";
13524
+ import { resolve as resolve7, dirname as dirname34, join as join56, basename as basename16 } from "path";
13501
13525
  import { fileURLToPath as fileURLToPath2 } from "url";
13502
13526
  import { hostname as hostname4, homedir as homedir11 } from "os";
13503
13527
  import { spawnSync as spawnSync11 } from "child_process";
@@ -16454,7 +16478,7 @@ function writeDigest(o, d, role, stateDir, runId, own) {
16454
16478
  if (hit) return hit;
16455
16479
  const target = reposOf(o.route).find((r) => r.dir === dir);
16456
16480
  const nameOverride = target ? o.route.repoOverrides[target.name]?.worktreeName : void 0;
16457
- const cfg = resolveRepoConfig(loadRepoConfig(dir), nameOverride ? { worktrees: { nameOverride } } : void 0, dir);
16481
+ const cfg = resolveRepoConfig(loadRepoConfigTolerant(dir), nameOverride ? { worktrees: { nameOverride } } : void 0, dir);
16458
16482
  repoCache.set(dir, cfg);
16459
16483
  return cfg;
16460
16484
  };
@@ -18538,8 +18562,11 @@ async function mergeAndRelease(o, decision, fresh, releaseBranch, isolated = fal
18538
18562
  o.emit.emit(`${changed2.split("\n")[0]} changed in this release \u2014 continuing under the merged contract`);
18539
18563
  }
18540
18564
  } catch (e) {
18565
+ if (o.contractRefusal) throw new Error(`${o.contractRefusal}; the merged contract is also refused: ${e.message}`);
18541
18566
  o.emit.warn(`could not re-read the merged repo config: ${e.message}; releasing under the earlier one`);
18542
18567
  }
18568
+ } else if (o.contractRefusal) {
18569
+ throw new Error(`${o.contractRefusal}; nothing merged replaces it`);
18543
18570
  }
18544
18571
  }
18545
18572
  if (o.mergeOnly) {
@@ -22816,9 +22843,32 @@ function describeCause(err3) {
22816
22843
  return code && !first.includes(code) ? `${first} (${code})` : first;
22817
22844
  }
22818
22845
 
22846
+ // src/contract-refusal.ts
22847
+ import { join as join51 } from "path";
22848
+ function contractFixers(dir, baseRef, tickets, locate) {
22849
+ const out = [];
22850
+ for (const t of tickets) {
22851
+ const branch = locate(t);
22852
+ if (!branch) continue;
22853
+ const changed2 = gitOk(dir, ["diff", "--name-only", `${baseRef}...${branch}`, "--", ...REPO_CONFIG_FILENAMES]);
22854
+ const file = changed2?.split("\n").find((f) => f.trim() !== "");
22855
+ if (!file) continue;
22856
+ const text2 = gitOk(dir, ["show", `${branch}:${file}`]);
22857
+ if (text2 === null) continue;
22858
+ try {
22859
+ parseRepoConfig(text2, join51(dir, file));
22860
+ } catch (e) {
22861
+ if (e instanceof RepoConfigError) continue;
22862
+ throw e;
22863
+ }
22864
+ out.push({ issueId: t.issue_id, branch, file });
22865
+ }
22866
+ return out;
22867
+ }
22868
+
22819
22869
  // src/daemon.ts
22820
22870
  import { existsSync as existsSync48, mkdirSync as mkdirSync36, readFileSync as readFileSync44, readdirSync as readdirSync17, rmSync as rmSync22, statSync as statSync14, writeFileSync as writeFileSync36 } from "fs";
22821
- import { join as join51 } from "path";
22871
+ import { join as join52 } from "path";
22822
22872
  import { randomBytes as randomBytes2 } from "crypto";
22823
22873
  var DEFAULT_FLOOR_MS = 5e3;
22824
22874
  var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
@@ -22906,7 +22956,7 @@ function latestMtime(path) {
22906
22956
  if (!stat2.isDirectory()) return 0;
22907
22957
  let latest = stat2.mtimeMs;
22908
22958
  for (const entry of readdirSync17(path, { withFileTypes: true })) {
22909
- latest = Math.max(latest, latestMtime(join51(path, entry.name)));
22959
+ latest = Math.max(latest, latestMtime(join52(path, entry.name)));
22910
22960
  }
22911
22961
  return latest;
22912
22962
  }
@@ -22918,7 +22968,7 @@ function makeStaleChecker(paths, opts = {}) {
22918
22968
  return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync48(opts.reloadSentinel);
22919
22969
  }
22920
22970
  function reloadSentinelFor(stateDir) {
22921
- return join51(stateDir, "reload-requested");
22971
+ return join52(stateDir, "reload-requested");
22922
22972
  }
22923
22973
  function readReloadRequestedAt(stateDir) {
22924
22974
  try {
@@ -22928,7 +22978,7 @@ function readReloadRequestedAt(stateDir) {
22928
22978
  }
22929
22979
  }
22930
22980
  function daemonStampFor(stateDir) {
22931
- return join51(stateDir, "daemon.started");
22981
+ return join52(stateDir, "daemon.started");
22932
22982
  }
22933
22983
  function noteDaemonStarted(stateDir, now = Date.now()) {
22934
22984
  mkdirSync36(stateDir, { recursive: true });
@@ -23486,7 +23536,7 @@ async function buildEnvironment(route2, ship, ticket) {
23486
23536
  name: t.name,
23487
23537
  dir: t.dir,
23488
23538
  id,
23489
- config: resolveRepoConfig(loadRepoConfig(t.dir), {
23539
+ config: resolveRepoConfig(loadRepoConfigTolerant(t.dir), {
23490
23540
  hooks: { ...route2.hooks, ...o?.hooks },
23491
23541
  labels: { ...route2.labels, ...o?.labels },
23492
23542
  branch: { ...route2.branch, ...o?.branch },
@@ -23515,7 +23565,7 @@ import {
23515
23565
  } from "fs";
23516
23566
  import { createConnection } from "net";
23517
23567
  import { createServer as createServer2 } from "http";
23518
- import { basename as basename14, dirname as dirname32, join as join52 } from "path";
23568
+ import { basename as basename14, dirname as dirname32, join as join53 } from "path";
23519
23569
  import { execFile as execFile3, spawn as spawn12 } from "child_process";
23520
23570
 
23521
23571
  // src/periscope-control.ts
@@ -23744,7 +23794,7 @@ function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
23744
23794
  // src/control-api.ts
23745
23795
  var PAUSABLE = ["dev", "design", "qa"];
23746
23796
  var CONTROL_SOCKET_NAME = "control.sock";
23747
- var controlSocketFor = (stateDir) => join52(stateDir, CONTROL_SOCKET_NAME);
23797
+ var controlSocketFor = (stateDir) => join53(stateDir, CONTROL_SOCKET_NAME);
23748
23798
  var MAX_SOCKET_PATH = 100;
23749
23799
  var MAX_TAIL_BYTES = 1024 * 1024;
23750
23800
  var MAX_WAIT_SECONDS = 30;
@@ -23831,7 +23881,7 @@ function candidates(live, rotated) {
23831
23881
  const out = [live];
23832
23882
  if (!rotated) return out;
23833
23883
  try {
23834
- for (const n of readdirSync18(dirname32(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join52(dirname32(live), n));
23884
+ for (const n of readdirSync18(dirname32(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join53(dirname32(live), n));
23835
23885
  } catch {
23836
23886
  }
23837
23887
  return out;
@@ -24031,7 +24081,7 @@ async function route(deps, method, url, req, abort) {
24031
24081
  if (p.startsWith("/streams/") && get2) {
24032
24082
  const run3 = decodeURIComponent(p.slice("/streams/".length));
24033
24083
  if (!RUN_NAME_RE.test(run3) || run3.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run3)) return err2(400, "not a run name");
24034
- const file = join52(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
24084
+ const file = join53(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
24035
24085
  if (!existsSync49(file)) return err2(404, "no such run");
24036
24086
  return tailReply(file, url.searchParams, false, abort);
24037
24087
  }
@@ -24181,7 +24231,7 @@ function crewJson(args, timeoutMs = 6e4) {
24181
24231
 
24182
24232
  // src/periscope-mcp.ts
24183
24233
  import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
24184
- import { basename as basename15, dirname as dirname33, isAbsolute as isAbsolute5, join as join54, relative as relative2, resolve as resolve6, sep } from "path";
24234
+ import { basename as basename15, dirname as dirname33, isAbsolute as isAbsolute5, join as join55, relative as relative2, resolve as resolve6, sep } from "path";
24185
24235
  import { z } from "zod";
24186
24236
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
24187
24237
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
@@ -24189,11 +24239,11 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
24189
24239
  // src/periscope-endpoints.ts
24190
24240
  import { randomBytes as randomBytes3 } from "crypto";
24191
24241
  import { chmodSync as chmodSync5, existsSync as existsSync50, mkdirSync as mkdirSync37, readdirSync as readdirSync19, readFileSync as readFileSync45, rmSync as rmSync24, writeFileSync as writeFileSync37 } from "fs";
24192
- import { join as join53 } from "path";
24193
- var secretDir = (stateDir) => join53(stateDir, "periscope-mcp");
24194
- var secretFile = (stateDir, id) => join53(secretDir(stateDir), `${id}.json`);
24195
- var endpointDir = (stateDir) => join53(stateDir, "periscope-endpoints");
24196
- var endpointFile = (stateDir, id) => join53(endpointDir(stateDir), `${id}.json`);
24242
+ import { join as join54 } from "path";
24243
+ var secretDir = (stateDir) => join54(stateDir, "periscope-mcp");
24244
+ var secretFile = (stateDir, id) => join54(secretDir(stateDir), `${id}.json`);
24245
+ var endpointDir = (stateDir) => join54(stateDir, "periscope-endpoints");
24246
+ var endpointFile = (stateDir, id) => join54(endpointDir(stateDir), `${id}.json`);
24197
24247
  function mintPeriscopeSecret(stateDir, ticketRecordId) {
24198
24248
  mkdirSync37(secretDir(stateDir), { recursive: true, mode: 448 });
24199
24249
  const secret = randomBytes3(32).toString("base64url");
@@ -24334,13 +24384,13 @@ async function resolveInWorktree(root, requested) {
24334
24384
  let abs;
24335
24385
  if (isAbsolute5(requested)) {
24336
24386
  abs = resolve6(requested);
24337
- if (isInside(root, abs)) abs = join54(realRoot, relative2(root, abs));
24387
+ if (isInside(root, abs)) abs = join55(realRoot, relative2(root, abs));
24338
24388
  } else {
24339
24389
  abs = resolve6(realRoot, requested);
24340
24390
  }
24341
24391
  if (!isInside(realRoot, abs)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
24342
24392
  const { real, rest } = await realAncestor(abs);
24343
- const final = rest ? join54(real, rest) : real;
24393
+ const final = rest ? join55(real, rest) : real;
24344
24394
  if (!isInside(realRoot, final)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
24345
24395
  if (relative2(realRoot, final).split(sep).includes(".git")) throw new PathRefusal(".git is not reachable from a periscope");
24346
24396
  return final;
@@ -24408,7 +24458,7 @@ function createPeriscopeMcpServer(d, sc) {
24408
24458
  const entries = (await readdir(abs, { withFileTypes: true })).filter((e) => e.name !== ".git");
24409
24459
  const lines2 = await Promise.all(entries.map(async (e) => {
24410
24460
  if (e.isDirectory()) return `[DIR] ${e.name}`;
24411
- const size = (await lstat(join54(abs, e.name))).size;
24461
+ const size = (await lstat(join55(abs, e.name))).size;
24412
24462
  return `[FILE] ${e.name} (${size} bytes)`;
24413
24463
  }));
24414
24464
  return text(lines2.join("\n") || "(empty)");
@@ -24474,7 +24524,7 @@ function createPeriscopeMcpServer(d, sc) {
24474
24524
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
24475
24525
  }, async ({ path }) => {
24476
24526
  const parent = await inTree(dirname33(path) === "" ? "." : dirname33(path));
24477
- const abs = join54(parent, basename15(path));
24527
+ const abs = join55(parent, basename15(path));
24478
24528
  if (basename15(path) === ".git" || abs === parent) return text("path must name a file", true);
24479
24529
  const st = await lstat(abs).catch(() => void 0);
24480
24530
  if (!st) return text(`${path} does not exist`, true);
@@ -25313,7 +25363,19 @@ if (command === "passenger-sync-daemon") {
25313
25363
  return;
25314
25364
  }
25315
25365
  try {
25316
- for (const r of syncAllPassengerCheckouts(cfg)) {
25366
+ const refusedRepos = /* @__PURE__ */ new Map();
25367
+ const checkouts = syncAllPassengerCheckouts(cfg, (repo, e) => refusedRepos.set(repo, e.message));
25368
+ for (const [repo, detail] of refusedRepos) {
25369
+ if (await raiseShipAttention({ state, emit }, repoContractRefusedItem(repo, detail))) {
25370
+ emit.warn(`passenger sync skips ${repo}: ${detail}`, { step: "passengers", data: { repo } });
25371
+ }
25372
+ }
25373
+ for (const item of state.shipAttention().previous()) {
25374
+ if (item.kind === "repo_contract_refused" && !refusedRepos.has(item.key.slice(repoContractRefusedKey("").length))) {
25375
+ clearShipAttention(state, item.key);
25376
+ }
25377
+ }
25378
+ for (const r of checkouts) {
25317
25379
  if (r.outcome.action === "clone-failed") {
25318
25380
  if (!r.outcome.repeat) {
25319
25381
  emit.warn(
@@ -25536,7 +25598,7 @@ if (command === "passenger-sync-daemon") {
25536
25598
  }
25537
25599
  if (host === "linux" && hasSystemd) {
25538
25600
  const label = labelFor(CREW_HOME, "run");
25539
- const staleTimer = join55(homedir11(), ".config", "systemd", "user", `${label}.timer`);
25601
+ const staleTimer = join56(homedir11(), ".config", "systemd", "user", `${label}.timer`);
25540
25602
  if (existsSync51(staleTimer)) {
25541
25603
  process.stdout.write(
25542
25604
  `removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
@@ -25563,7 +25625,9 @@ if (command === "passenger-sync-daemon") {
25563
25625
  }
25564
25626
  const tracker = new Tracker(c, cfg.ship);
25565
25627
  problems.push(...validateContract(tracker.contract).map((p) => `contract: ${p}`));
25566
- for (const r of await resolvedRepos(c)) {
25628
+ const { settled: doctorRepos, errors: refusedRepos } = await settleRepos(reposOf(c), (t) => t.name, (t) => resolveOneRepo(c, t));
25629
+ for (const e of refusedRepos) problems.push(`repo ${e.repo}: ${e.detail}`);
25630
+ for (const r of doctorRepos) {
25567
25631
  if (!existsSync51(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
25568
25632
  if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
25569
25633
  readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
@@ -25625,7 +25689,16 @@ ${changed2.map((c) => ` - ${c}
25625
25689
  `).join("")}` : "fix: nothing changed\n");
25626
25690
  }
25627
25691
  async function resolvedRepos(c) {
25628
- return Promise.all(reposOf(c).map((t) => resolveOneRepo(c, t)));
25692
+ const { settled, errors } = await settleRepos(reposOf(c), (t) => t.name, (t) => resolveOneRepo(c, t));
25693
+ for (const e of errors) {
25694
+ try {
25695
+ await raiseShipAttention({ state, emit }, repoContractRefusedItem(e.repo, e.detail));
25696
+ } catch {
25697
+ }
25698
+ }
25699
+ const refused = new Set(errors.map((e) => e.repo));
25700
+ for (const t of reposOf(c)) if (!refused.has(t.name)) clearShipAttention(state, repoContractRefusedKey(t.name));
25701
+ return settled;
25629
25702
  }
25630
25703
  async function sandboxReposOf(routes) {
25631
25704
  const out = /* @__PURE__ */ new Map();
@@ -25744,7 +25817,10 @@ ${changed2.map((c) => ` - ${c}
25744
25817
  }
25745
25818
  let boardLock;
25746
25819
  try {
25747
- const repoFile = loadRepoConfig(target.dir);
25820
+ let contractRefusal = null;
25821
+ const repoFile = loadRepoConfigTolerant(target.dir, (e) => {
25822
+ contractRefusal = e;
25823
+ });
25748
25824
  const o = c.repoOverrides[target.name];
25749
25825
  const repo = resolveRepoConfig(repoFile, {
25750
25826
  hooks: { ...c.hooks, ...o?.hooks },
@@ -25859,6 +25935,21 @@ ${changed2.map((c) => ` - ${c}
25859
25935
  releaseModes
25860
25936
  );
25861
25937
  reportUnplaceable(c.route, unplaceable, tracker.contract.statuses.verified, remit);
25938
+ if (contractRefusal) {
25939
+ const refusal = contractRefusal;
25940
+ const baseRef = remoteBranchExists(target.dir, repo.branch.remote, repo.branch.base) ? `${repo.branch.remote}/${repo.branch.base}` : repo.branch.base;
25941
+ const fixers = contractFixers(
25942
+ target.dir,
25943
+ baseRef,
25944
+ tickets.filter((t) => t.status === tracker.contract.statuses.verified),
25945
+ (t) => locateBranchForTicket(target.dir, repo, t)?.branch ?? null
25946
+ );
25947
+ if (fixers.length === 0) throw refusal;
25948
+ remit.emit(
25949
+ `${target.name}: the base contract is refused (${refusal.message}); ${fixers.map((f) => f.issueId).join(", ")} replace${fixers.length === 1 ? "s" : ""} ${fixers[0].file} \u2014 merging first`,
25950
+ { step: "release" }
25951
+ );
25952
+ }
25862
25953
  const blockerInfo = blockerInfoMap(all, await tracker.ticketsByIds(missingBlockerIds(all)));
25863
25954
  const verifiedStatus = tracker.contract.statuses.verified;
25864
25955
  const verifiedTickets = tickets.filter((t) => t.status === verifiedStatus);
@@ -25891,6 +25982,7 @@ ${changed2.map((c) => ` - ${c}
25891
25982
  emit: remit,
25892
25983
  scope,
25893
25984
  verifiedShas,
25985
+ ...contractRefusal ? { contractRefusal: contractRefusal.message } : {},
25894
25986
  state: state.release(scope),
25895
25987
  reviewColumns: repo.release.mode === "external" ? await tracker.reviewColumns() : void 0,
25896
25988
  dryRun,
@@ -26759,7 +26851,7 @@ ${cu.tail.trimEnd()}
26759
26851
  process.exit(1);
26760
26852
  }
26761
26853
  requireArmed("run the daemon loop");
26762
- const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join55(CREW_HOME, "src"), join55(CREW_HOME, "dist"), join55(CREW_HOME, "bin")];
26854
+ const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join56(CREW_HOME, "src"), join56(CREW_HOME, "dist"), join56(CREW_HOME, "bin")];
26763
26855
  const configPaths = daemonConfigPaths(cfg);
26764
26856
  watchPaths.push(...configPaths);
26765
26857
  noteDaemonStarted(cfg.ship.stateDir);
@@ -28760,10 +28852,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
28760
28852
  startupSeconds: cfg.ship.intelStartupSeconds,
28761
28853
  prepare: async (path) => {
28762
28854
  for (const f of repoWt.config.worktrees.copy) {
28763
- const src = join55(repoWt.dir, f);
28764
- if (!existsSync51(src) || existsSync51(join55(path, f))) continue;
28765
- mkdirSync38(dirOf2(join55(path, f)), { recursive: true });
28766
- copyFileSync2(src, join55(path, f));
28855
+ const src = join56(repoWt.dir, f);
28856
+ if (!existsSync51(src) || existsSync51(join56(path, f))) continue;
28857
+ mkdirSync38(dirOf2(join56(path, f)), { recursive: true });
28858
+ copyFileSync2(src, join56(path, f));
28767
28859
  }
28768
28860
  const setup = repoWt.config.hooks.setup;
28769
28861
  return setup ? runScript(setup, { cwd: path, shell: shellWt }) : null;
@@ -29079,10 +29171,10 @@ ${where.join("\n")}`);
29079
29171
  },
29080
29172
  prepare: async (path) => {
29081
29173
  for (const f of repoP.config.worktrees.copy) {
29082
- const src = join55(repoP.dir, f);
29174
+ const src = join56(repoP.dir, f);
29083
29175
  if (!existsSync51(src)) continue;
29084
- mkdirSync38(dirOf2(join55(path, f)), { recursive: true });
29085
- copyFileSync2(src, join55(path, f));
29176
+ mkdirSync38(dirOf2(join56(path, f)), { recursive: true });
29177
+ copyFileSync2(src, join56(path, f));
29086
29178
  }
29087
29179
  const setup = repoP.config.hooks.setup;
29088
29180
  return setup ? runScript(setup, { cwd: path, shell: shellP, onLine: (l) => {
@@ -29580,12 +29672,12 @@ ${where.join("\n")}`);
29580
29672
  `);
29581
29673
  }
29582
29674
  }
29583
- if (existsSync51(join55(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync25(dir, { recursive: true, force: true });
29675
+ if (existsSync51(join56(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync25(dir, { recursive: true, force: true });
29584
29676
  say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
29585
29677
  break;
29586
29678
  }
29587
29679
  const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
29588
- const fresh = !existsSync51(join55(dir, ".git"));
29680
+ const fresh = !existsSync51(join56(dir, ".git"));
29589
29681
  if (existsSync51(dir) && fresh && readdirSync20(dir).length > 0) fail2(`${dir} exists and is not a git checkout \u2014 move it aside`);
29590
29682
  if (dryRun) {
29591
29683
  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`);
@@ -29593,8 +29685,8 @@ ${where.join("\n")}`);
29593
29685
  }
29594
29686
  if (fresh) {
29595
29687
  mkdirSync38(dir, { recursive: true });
29596
- writeFileSync38(join55(dir, "README.md"), SAMPLE_README);
29597
- writeFileSync38(join55(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
29688
+ writeFileSync38(join56(dir, "README.md"), SAMPLE_README);
29689
+ writeFileSync38(join56(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
29598
29690
  const g = (...a) => {
29599
29691
  const r = spawnSync11("git", a, { cwd: dir, encoding: "utf8" });
29600
29692
  if (r.status !== 0) fail2(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tablation/crew",
3
- "version": "0.126.2",
3
+ "version": "0.126.3",
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",