@tablation/crew 0.47.0 → 0.47.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.
Files changed (2) hide show
  1. package/dist/cli.js +122 -74
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { resolve as resolve4, dirname as dirname12, join as join23, basename as basename9 } from "path";
4
+ import { resolve as resolve4, dirname as dirname12, join as join24, basename as basename9 } from "path";
5
5
  import { fileURLToPath } from "url";
6
6
  import { hostname as hostname2, homedir as homedir8 } from "os";
7
7
  import { spawnSync as spawnSync5 } from "child_process";
@@ -3482,7 +3482,7 @@ function describeRelease(o, route) {
3482
3482
  level: "fail",
3483
3483
  headline: `${route}: deploy FAILED`,
3484
3484
  detail: withHookTail(
3485
- "the version is merged to the base branch but not live \u2014 run `crew deploy` to retry; do not re-release",
3485
+ "the version is merged to the base branch but not live \u2014 if the deploy target was the problem, run `crew deploy` to retry (do not re-release); if the merged code itself failed to build, fix forward on a ticket branch, and merging it clears the not-retrying guard",
3486
3486
  o.hookFailure
3487
3487
  )
3488
3488
  };
@@ -4030,9 +4030,11 @@ import {
4030
4030
  statSync as statSync4,
4031
4031
  unlinkSync as unlinkSync2,
4032
4032
  readFileSync as readFileSync9,
4033
- writeFileSync as writeFileSync6
4033
+ writeFileSync as writeFileSync6,
4034
+ readdirSync as readdirSync3,
4035
+ rmSync as rmSync6
4034
4036
  } from "fs";
4035
- import { dirname as dirname5, basename as basename4 } from "path";
4037
+ import { dirname as dirname5, basename as basename4, join as join10 } from "path";
4036
4038
  var GitError = class extends Error {
4037
4039
  };
4038
4040
  function streamText(x) {
@@ -4367,12 +4369,36 @@ function recoverStrandedReleaseCheckouts(cwd) {
4367
4369
  if (line.startsWith("worktree ")) path = line.slice("worktree ".length);
4368
4370
  else if (line.startsWith("branch refs/heads/crew/release-") && path) {
4369
4371
  gitOk(cwd, ["worktree", "remove", "--force", path]);
4372
+ const root = dirname5(path);
4373
+ if (basename4(root).startsWith("crew-release-")) rmSync6(root, { recursive: true, force: true });
4370
4374
  }
4371
4375
  }
4372
4376
  const stranded = (gitOk(cwd, ["for-each-ref", "--format=%(refname:short)", "refs/heads/crew/release-*"]) ?? "").split("\n").filter(Boolean);
4373
4377
  for (const b of stranded) gitOk(cwd, ["branch", "-q", "-D", b]);
4374
4378
  return stranded;
4375
4379
  }
4380
+ function sweepStaleReleaseRoots(tmp, now = Date.now(), maxAgeMs = 60 * 60 * 1e3) {
4381
+ const removed = [];
4382
+ let names;
4383
+ try {
4384
+ names = readdirSync3(tmp);
4385
+ } catch {
4386
+ return removed;
4387
+ }
4388
+ for (const name of names) {
4389
+ if (!name.startsWith("crew-release-")) continue;
4390
+ const root = join10(tmp, name);
4391
+ try {
4392
+ const st = statSync4(root);
4393
+ if (!st.isDirectory() || now - st.mtimeMs < maxAgeMs) continue;
4394
+ if (existsSync9(join10(root, "checkout"))) continue;
4395
+ rmSync6(root, { recursive: true, force: true });
4396
+ removed.push(name);
4397
+ } catch {
4398
+ }
4399
+ }
4400
+ return removed;
4401
+ }
4376
4402
  function pushBranch(cwd, remote, branch, dst = branch) {
4377
4403
  git(cwd, ["push", remote, `${branch}:${dst}`]);
4378
4404
  }
@@ -6268,7 +6294,7 @@ function materializeBranchForTicket(dir, cfg, t, role) {
6268
6294
 
6269
6295
  // src/poll.ts
6270
6296
  import { writeFileSync as writeFileSync7 } from "fs";
6271
- import { join as join10 } from "path";
6297
+ import { join as join11 } from "path";
6272
6298
 
6273
6299
  // src/stalled.ts
6274
6300
  var STALLED_AFTER_CYCLES = 30;
@@ -6545,7 +6571,7 @@ async function decideCycle(o) {
6545
6571
  }
6546
6572
  return decision;
6547
6573
  }
6548
- var digestPath = (stateDir, route, role) => join10(stateDir, `digest-${route}-${role}.md`);
6574
+ var digestPath = (stateDir, route, role) => join11(stateDir, `digest-${route}-${role}.md`);
6549
6575
  function writeDigest(o, d, role, stateDir) {
6550
6576
  const me = o.route.resolved?.seats[role];
6551
6577
  if (!me) return false;
@@ -6993,7 +7019,7 @@ var alreadyReported = (ticket, g, events) => events.some((e) => e.record_id ===
6993
7019
  import { spawn as spawn2 } from "child_process";
6994
7020
  import { readFileSync as readFileSync12, existsSync as existsSync12, statSync as statSync5, mkdirSync as mkdirSync6, writeFileSync as writeFileSync8, createWriteStream } from "fs";
6995
7021
  import { finished } from "stream/promises";
6996
- import { join as join13, dirname as dirname6 } from "path";
7022
+ import { join as join14, dirname as dirname6 } from "path";
6997
7023
  import { createHash as createHash2 } from "crypto";
6998
7024
 
6999
7025
  // src/environment.ts
@@ -7408,14 +7434,14 @@ function expandSkillMarkers(text, lookup, opts = {}) {
7408
7434
  }
7409
7435
 
7410
7436
  // src/skills.ts
7411
- import { readFileSync as readFileSync11, readdirSync as readdirSync3, existsSync as existsSync11 } from "fs";
7412
- import { join as join12 } from "path";
7437
+ import { readFileSync as readFileSync11, readdirSync as readdirSync4, existsSync as existsSync11 } from "fs";
7438
+ import { join as join13 } from "path";
7413
7439
  import { parse as parseYaml } from "yaml";
7414
7440
  import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError8, TablationApiError as TablationApiError2 } from "@tablation/client";
7415
7441
 
7416
7442
  // src/agents.ts
7417
7443
  import { readFileSync as readFileSync10, existsSync as existsSync10 } from "fs";
7418
- import { join as join11 } from "path";
7444
+ import { join as join12 } from "path";
7419
7445
  import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError7, TablationApiError } from "@tablation/client";
7420
7446
  var PERSONA_NAME = {
7421
7447
  dev: "Developer",
@@ -7429,10 +7455,10 @@ var CREW_AGENT_COLUMN = "agent_id";
7429
7455
  var AgentsSyncError = class extends Error {
7430
7456
  };
7431
7457
  function personaDefaultPrompt(promptsDir, role) {
7432
- const briefPath = join11(promptsDir, "personas", `lane-${role}.md`);
7458
+ const briefPath = join12(promptsDir, "personas", `lane-${role}.md`);
7433
7459
  if (!existsSync10(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
7434
7460
  if (SELF_CONTAINED_ROLES.has(role)) return readFileSync10(briefPath, "utf8");
7435
- const commonPath = join11(promptsDir, "personas", "common.md");
7461
+ const commonPath = join12(promptsDir, "personas", "common.md");
7436
7462
  if (!existsSync10(commonPath)) return readFileSync10(briefPath, "utf8");
7437
7463
  return `${readFileSync10(commonPath, "utf8")}${readFileSync10(briefPath, "utf8")}`;
7438
7464
  }
@@ -7812,9 +7838,9 @@ function parseSkillFile(path) {
7812
7838
  return { name: meta.name, description: meta.description, prompt: body, file: path };
7813
7839
  }
7814
7840
  function listLocalSkills(promptsDir) {
7815
- const dir = join12(promptsDir, "skills");
7841
+ const dir = join13(promptsDir, "skills");
7816
7842
  if (!existsSync11(dir)) return [];
7817
- return readdirSync3(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join12(dir, f)));
7843
+ return readdirSync4(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join13(dir, f)));
7818
7844
  }
7819
7845
  async function syncSkills(route, opts) {
7820
7846
  if (!route.resolved) {
@@ -8047,15 +8073,15 @@ var AgentError = class extends Error {
8047
8073
  var AgentSpawnError = class extends AgentError {
8048
8074
  };
8049
8075
  function planAgentRun(o) {
8050
- const briefPath = join13(o.route.promptsDir, "personas", `lane-${o.role}.md`);
8076
+ const briefPath = join14(o.route.promptsDir, "personas", `lane-${o.role}.md`);
8051
8077
  if (!existsSync12(briefPath)) {
8052
8078
  throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
8053
8079
  }
8054
- const commonPath = join13(o.route.promptsDir, "personas", "common.md");
8080
+ const commonPath = join14(o.route.promptsDir, "personas", "common.md");
8055
8081
  const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync12(commonPath);
8056
8082
  const now = o.now ?? (() => Date.now());
8057
8083
  const routeLabel = routeSlug(o.route.route);
8058
- const digestPath2 = join13(o.stateDir, `digest-${routeLabel}-${o.role}.md`);
8084
+ const digestPath2 = join14(o.stateDir, `digest-${routeLabel}-${o.role}.md`);
8059
8085
  let digest;
8060
8086
  let ageSeconds;
8061
8087
  if (existsSync12(digestPath2)) {
@@ -8085,7 +8111,7 @@ function planAgentRun(o) {
8085
8111
  digest
8086
8112
  });
8087
8113
  const promptSha = createHash2("sha256").update(expanded.text).digest("hex");
8088
- const streamsDir = join13(o.stateDir, "streams");
8114
+ const streamsDir = join14(o.stateDir, "streams");
8089
8115
  const base = `${routeLabel}-${o.role}-${o.cycle}`;
8090
8116
  const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
8091
8117
  const { toolchain, model } = seat;
@@ -8141,8 +8167,8 @@ function planAgentRun(o) {
8141
8167
  digestAttached: digest !== void 0,
8142
8168
  digestAgeSeconds: ageSeconds,
8143
8169
  model,
8144
- streamPath: join13(streamsDir, `${base}.jsonl`),
8145
- eventsPath: join13(streamsDir, `${base}.events.jsonl`),
8170
+ streamPath: join14(streamsDir, `${base}.jsonl`),
8171
+ eventsPath: join14(streamsDir, `${base}.events.jsonl`),
8146
8172
  ticket: o.ticket,
8147
8173
  agentLog,
8148
8174
  promptVersion: o.route.resolved?.agentPersonas?.[o.role]?.historyId,
@@ -8349,12 +8375,12 @@ function spawnAgent(plan, emit) {
8349
8375
 
8350
8376
  // src/run-worktree.ts
8351
8377
  import { existsSync as existsSync14 } from "fs";
8352
- import { dirname as dirname7, join as join15 } from "path";
8378
+ import { dirname as dirname7, join as join16 } from "path";
8353
8379
 
8354
8380
  // src/ports.ts
8355
8381
  import { execFileSync as execFileSync2 } from "child_process";
8356
8382
  import { existsSync as existsSync13 } from "fs";
8357
- import { join as join14 } from "path";
8383
+ import { join as join15 } from "path";
8358
8384
  var run = (cmd, args) => {
8359
8385
  try {
8360
8386
  return execFileSync2(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
@@ -8399,7 +8425,7 @@ function ticketForPort(port, s = DEFAULT_PORTS) {
8399
8425
  }
8400
8426
  function worktreeExists(parent, prefix, n) {
8401
8427
  for (const name of [`${prefix}${n}`, `${prefix}${String(n).padStart(3, "0")}`]) {
8402
- if (existsSync13(join14(parent, name, ".git"))) return true;
8428
+ if (existsSync13(join15(parent, name, ".git"))) return true;
8403
8429
  }
8404
8430
  return false;
8405
8431
  }
@@ -8487,7 +8513,7 @@ function planRunWorktree(dir, cfg, t, role) {
8487
8513
  if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
8488
8514
  return { action: "recut", path: existing.path, branch, base: baseRef };
8489
8515
  }
8490
- const path = join15(dirname7(dir), worktreeForTicket(dir, cfg, t, role));
8516
+ const path = join16(dirname7(dir), worktreeForTicket(dir, cfg, t, role));
8491
8517
  if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
8492
8518
  return { action: "cut-remote", path, branch: located.branch };
8493
8519
  }
@@ -8695,7 +8721,7 @@ async function retireRunWorktree(tracker, dir, cfg, t, role, seatId, shipName2,
8695
8721
  }
8696
8722
 
8697
8723
  // src/connect-wizard.ts
8698
- import { delimiter, dirname as dirname8, join as join16 } from "path";
8724
+ import { delimiter, dirname as dirname8, join as join17 } from "path";
8699
8725
  import { accessSync as accessSync2, constants as constants2, existsSync as existsSync15, mkdirSync as mkdirSync7, writeFileSync as writeFileSync9 } from "fs";
8700
8726
  import { stringify } from "yaml";
8701
8727
  var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
@@ -8718,7 +8744,7 @@ function defaultIsExecutable(p) {
8718
8744
  function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
8719
8745
  for (const dir of pathEnv.split(delimiter)) {
8720
8746
  if (!dir) continue;
8721
- const candidate = join16(dir, name);
8747
+ const candidate = join17(dir, name);
8722
8748
  if (isExecutable(candidate)) return candidate;
8723
8749
  }
8724
8750
  return void 0;
@@ -8813,8 +8839,8 @@ Wrote ${o.configPath}.
8813
8839
  }
8814
8840
 
8815
8841
  // src/release-run.ts
8816
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync10, existsSync as existsSync16, copyFileSync, mkdirSync as mkdirSync8, mkdtempSync, rmSync as rmSync6 } from "fs";
8817
- import { dirname as dirname9, join as join17 } from "path";
8842
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync10, existsSync as existsSync16, copyFileSync, mkdirSync as mkdirSync8, mkdtempSync, rmSync as rmSync7 } from "fs";
8843
+ import { dirname as dirname9, join as join18 } from "path";
8818
8844
  import { tmpdir as tmpdir2 } from "os";
8819
8845
 
8820
8846
  // src/release.ts
@@ -9472,7 +9498,7 @@ async function bump(o, size) {
9472
9498
  }
9473
9499
  return printed;
9474
9500
  }
9475
- const files = o.repo.release.versionFiles.map((f) => join17(o.cwd, f));
9501
+ const files = o.repo.release.versionFiles.map((f) => join18(o.cwd, f));
9476
9502
  const first = files[0];
9477
9503
  if (!existsSync16(first)) {
9478
9504
  o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
@@ -9504,7 +9530,7 @@ async function bump(o, size) {
9504
9530
  function writeChangelog(o, version, entries) {
9505
9531
  const file = o.repo.release.changelog;
9506
9532
  if (!file || entries.length === 0) return;
9507
- const path = join17(o.cwd, file);
9533
+ const path = join18(o.cwd, file);
9508
9534
  const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
9509
9535
  if (o.dryRun) {
9510
9536
  o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
@@ -9589,17 +9615,25 @@ async function confirmLastRelease(o, decision) {
9589
9615
  if (!confirmed) o.emit.warn(`${decision.lastTag} is still not confirmed live \u2014 tickets stay verified`);
9590
9616
  return { confirmed, version };
9591
9617
  }
9618
+ function closeReleaseCheckout(cwd, root, dir, branch, run2 = gitOk) {
9619
+ try {
9620
+ run2(cwd, ["worktree", "remove", "--force", dir]);
9621
+ } finally {
9622
+ try {
9623
+ rmSync7(root, { recursive: true, force: true });
9624
+ } finally {
9625
+ run2(cwd, ["worktree", "prune"]);
9626
+ run2(cwd, ["branch", "-D", branch]);
9627
+ }
9628
+ }
9629
+ }
9592
9630
  function openReleaseCheckout(o) {
9593
9631
  const { remote, base } = o.repo.branch;
9594
9632
  const branch = `crew/release-${Date.now()}`;
9595
- const root = mkdtempSync(join17(tmpdir2(), "crew-release-"));
9596
- const dir = join17(root, "checkout");
9597
- const close = () => {
9598
- gitOk(o.cwd, ["worktree", "remove", "--force", dir]);
9599
- rmSync6(root, { recursive: true, force: true });
9600
- gitOk(o.cwd, ["worktree", "prune"]);
9601
- gitOk(o.cwd, ["branch", "-D", branch]);
9602
- };
9633
+ const root = mkdtempSync(join18(tmpdir2(), "crew-release-"));
9634
+ const dir = join18(root, "checkout");
9635
+ o.emit.emit(`release checkout: ${dir}`, { step: "release" });
9636
+ const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
9603
9637
  try {
9604
9638
  git(o.cwd, ["worktree", "add", "-q", "-b", branch, dir, `${remote}/${base}`]);
9605
9639
  } catch (e) {
@@ -9607,10 +9641,10 @@ function openReleaseCheckout(o) {
9607
9641
  throw e;
9608
9642
  }
9609
9643
  for (const f of o.repo.worktrees.copy) {
9610
- const src = join17(o.cwd, f);
9644
+ const src = join18(o.cwd, f);
9611
9645
  if (!existsSync16(src)) continue;
9612
- mkdirSync8(dirname9(join17(dir, f)), { recursive: true });
9613
- copyFileSync(src, join17(dir, f));
9646
+ mkdirSync8(dirname9(join18(dir, f)), { recursive: true });
9647
+ copyFileSync(src, join18(dir, f));
9614
9648
  }
9615
9649
  return { dir, branch, close };
9616
9650
  }
@@ -9689,6 +9723,8 @@ async function runRelease(o) {
9689
9723
  let checkout;
9690
9724
  const stranded = recoverStrandedReleaseCheckouts(o.cwd);
9691
9725
  if (stranded.length > 0) o.emit.warn(scoped(`dropped release checkout(s) stranded by a crash: ${stranded.join(", ")}`));
9726
+ const swept = sweepStaleReleaseRoots(tmpdir2());
9727
+ if (swept.length > 0) o.emit.emit(scoped(`removed ${swept.length} stale crew-release-* temp dir(s)`), { step: "release" });
9692
9728
  try {
9693
9729
  checkout = openReleaseCheckout(o);
9694
9730
  } catch (e) {
@@ -10049,7 +10085,7 @@ ${n.detail ?? n.headline}
10049
10085
 
10050
10086
  Filed automatically \u2014 no \`notify\` hook is configured for route \`${route}\`, so this is the crew's built-in fallback rather than a silent log line. Configure \`hooks.notify\` on this route to replace it.`;
10051
10087
  }
10052
- async function applyFailureAlert(writer, n, route, openTickets, memberId, log, dryRun) {
10088
+ async function applyFailureAlert(writer, n, route, openTickets, memberId, log, dryRun, placement = {}) {
10053
10089
  const existing = findOpenFailureAlert(openTickets, n.headline);
10054
10090
  if (existing) {
10055
10091
  const label = displayKey(existing);
@@ -10076,7 +10112,9 @@ Still failing: ${n.detail ?? n.headline}`, memberId, "failure_alert");
10076
10112
  description: ticketBody(n, route),
10077
10113
  report_type: "bug",
10078
10114
  severity: "s2",
10079
- reporter_name: "Crew release"
10115
+ reporter_name: "Crew release",
10116
+ ...placement.repo_id ? { repo_id: placement.repo_id } : {},
10117
+ ...placement.project_id ? { project_id: placement.project_id } : {}
10080
10118
  }, memberId);
10081
10119
  const label = created.issue_id ? displayKey(created) : created.id;
10082
10120
  log.warn(`release failure \u2014 filed ${label}`, { step: "release" });
@@ -11449,14 +11487,14 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
11449
11487
  }
11450
11488
 
11451
11489
  // src/stream-sweep.ts
11452
- import { existsSync as existsSync19, readdirSync as readdirSync4, rmSync as rmSync7, statSync as statSync7 } from "fs";
11453
- import { join as join18 } from "path";
11490
+ import { existsSync as existsSync19, readdirSync as readdirSync5, rmSync as rmSync8, statSync as statSync7 } from "fs";
11491
+ import { join as join19 } from "path";
11454
11492
  var DAY_MS2 = 24 * 60 * 60 * 1e3;
11455
11493
  function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
11456
- const dir = join18(stateDir, "streams");
11494
+ const dir = join19(stateDir, "streams");
11457
11495
  if (!existsSync19(dir)) return [];
11458
11496
  const cutoff = now - retentionDays * DAY_MS2;
11459
- return readdirSync4(dir).map((name) => join18(dir, name)).filter((path) => {
11497
+ return readdirSync5(dir).map((name) => join19(dir, name)).filter((path) => {
11460
11498
  try {
11461
11499
  return statSync7(path).mtimeMs < cutoff;
11462
11500
  } catch {
@@ -11472,7 +11510,7 @@ function applyStreamSweep(paths, dryRun, log) {
11472
11510
  continue;
11473
11511
  }
11474
11512
  try {
11475
- rmSync7(path, { force: true });
11513
+ rmSync8(path, { force: true });
11476
11514
  removed++;
11477
11515
  log.emit(`removed stream artifact ${path}`, { step: "worktree" });
11478
11516
  } catch (e) {
@@ -11484,7 +11522,7 @@ function applyStreamSweep(paths, dryRun, log) {
11484
11522
  function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
11485
11523
  if (!existsSync19(stateDir)) return [];
11486
11524
  const cutoff = now - retentionDays * DAY_MS2;
11487
- return readdirSync4(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join18(stateDir, name)).filter((path) => {
11525
+ return readdirSync5(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join19(stateDir, name)).filter((path) => {
11488
11526
  try {
11489
11527
  return statSync7(path).mtimeMs < cutoff;
11490
11528
  } catch {
@@ -11501,11 +11539,11 @@ import { dirname as dirOf, resolve as resolvePath } from "path";
11501
11539
  // src/ssh-keys.ts
11502
11540
  import { execFileSync as execFileSync3 } from "child_process";
11503
11541
  import { existsSync as existsSync20, mkdirSync as mkdirSync9, readFileSync as readFileSync14, chmodSync } from "fs";
11504
- import { dirname as dirname11, join as join19 } from "path";
11542
+ import { dirname as dirname11, join as join20 } from "path";
11505
11543
  var SshKeyError = class extends Error {
11506
11544
  };
11507
11545
  function shipSshKeyPathFor(stateDir) {
11508
- return join19(stateDir, "ssh", "id_ed25519");
11546
+ return join20(stateDir, "ssh", "id_ed25519");
11509
11547
  }
11510
11548
  function ensureShipSshKeypair(stateDir) {
11511
11549
  const privateKeyPath = shipSshKeyPathFor(stateDir);
@@ -11750,9 +11788,9 @@ function syncAllPassengerCheckouts(cfg) {
11750
11788
 
11751
11789
  // src/tunnel.ts
11752
11790
  import { spawn as spawn3 } from "child_process";
11753
- import { existsSync as existsSync21, mkdirSync as mkdirSync10, readFileSync as readFileSync15, writeFileSync as writeFileSync11, unlinkSync as unlinkSync3, readdirSync as readdirSync5 } from "fs";
11791
+ import { existsSync as existsSync21, mkdirSync as mkdirSync10, readFileSync as readFileSync15, writeFileSync as writeFileSync11, unlinkSync as unlinkSync3, readdirSync as readdirSync6 } from "fs";
11754
11792
  import { randomInt } from "crypto";
11755
- import { join as join20 } from "path";
11793
+ import { join as join21 } from "path";
11756
11794
  var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
11757
11795
  var defaultSpawn = (cmd, args) => spawn3(cmd, args, DEFAULT_SPAWN_OPTIONS);
11758
11796
  var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
@@ -11819,7 +11857,7 @@ function startTunnel(opts) {
11819
11857
  };
11820
11858
  }
11821
11859
  function tunnelStatePath(stateDir, workspaceId) {
11822
- return join20(stateDir, "passengers", `${workspaceId}.json`);
11860
+ return join21(stateDir, "passengers", `${workspaceId}.json`);
11823
11861
  }
11824
11862
  function readPersistedTunnel(stateDir, workspaceId) {
11825
11863
  const path = tunnelStatePath(stateDir, workspaceId);
@@ -11832,7 +11870,7 @@ function readPersistedTunnel(stateDir, workspaceId) {
11832
11870
  }
11833
11871
  function writePersistedTunnel(stateDir, state) {
11834
11872
  const path = tunnelStatePath(stateDir, state.workspaceId);
11835
- mkdirSync10(join20(stateDir, "passengers"), { recursive: true });
11873
+ mkdirSync10(join21(stateDir, "passengers"), { recursive: true });
11836
11874
  writeFileSync11(path, `${JSON.stringify(state, null, 2)}
11837
11875
  `);
11838
11876
  }
@@ -11930,9 +11968,9 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
11930
11968
  }
11931
11969
  }
11932
11970
  function readAllPersistedWorkspaceIds(stateDir) {
11933
- const dir = join20(stateDir, "passengers");
11971
+ const dir = join21(stateDir, "passengers");
11934
11972
  if (!existsSync21(dir)) return [];
11935
- return readdirSync5(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
11973
+ return readdirSync6(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
11936
11974
  }
11937
11975
 
11938
11976
  // src/mcp-endpoint-registry.ts
@@ -12052,8 +12090,8 @@ async function unregisterEndpoint(workspaceId, info, d) {
12052
12090
  import { createServer as createHttpServer } from "http";
12053
12091
  import { spawn as spawn4 } from "child_process";
12054
12092
  import { randomBytes } from "crypto";
12055
- import { existsSync as existsSync22, mkdirSync as mkdirSync11, readFileSync as readFileSync16, writeFileSync as writeFileSync12, unlinkSync as unlinkSync4, readdirSync as readdirSync6 } from "fs";
12056
- import { join as join21 } from "path";
12093
+ import { existsSync as existsSync22, mkdirSync as mkdirSync11, readFileSync as readFileSync16, writeFileSync as writeFileSync12, unlinkSync as unlinkSync4, readdirSync as readdirSync7 } from "fs";
12094
+ import { join as join22 } from "path";
12057
12095
 
12058
12096
  // src/secret-compare.ts
12059
12097
  import { timingSafeEqual } from "crypto";
@@ -12142,7 +12180,7 @@ var defaultSpawn2 = (cmd, args, env) => {
12142
12180
  return child;
12143
12181
  };
12144
12182
  function syncDaemonStatePath(stateDir, workspaceId) {
12145
- return join21(stateDir, "passengers", `${workspaceId}-sync.json`);
12183
+ return join22(stateDir, "passengers", `${workspaceId}-sync.json`);
12146
12184
  }
12147
12185
  function readPersistedSyncDaemon(stateDir, workspaceId) {
12148
12186
  const path = syncDaemonStatePath(stateDir, workspaceId);
@@ -12155,7 +12193,7 @@ function readPersistedSyncDaemon(stateDir, workspaceId) {
12155
12193
  }
12156
12194
  function writePersistedSyncDaemon(stateDir, state) {
12157
12195
  const path = syncDaemonStatePath(stateDir, state.workspaceId);
12158
- mkdirSync11(join21(stateDir, "passengers"), { recursive: true });
12196
+ mkdirSync11(join22(stateDir, "passengers"), { recursive: true });
12159
12197
  writeFileSync12(path, `${JSON.stringify(state, null, 2)}
12160
12198
  `);
12161
12199
  }
@@ -12176,10 +12214,10 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
12176
12214
  }
12177
12215
  }
12178
12216
  function readAllPersistedSyncWorkspaceIds(stateDir) {
12179
- const dir = join21(stateDir, "passengers");
12217
+ const dir = join22(stateDir, "passengers");
12180
12218
  if (!existsSync22(dir)) return [];
12181
12219
  const suffix = "-sync.json";
12182
- return readdirSync6(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
12220
+ return readdirSync7(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
12183
12221
  }
12184
12222
  function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
12185
12223
  const alive = deps.isPidAlive ?? isPidAlive;
@@ -12234,8 +12272,8 @@ function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
12234
12272
  }
12235
12273
 
12236
12274
  // src/daemon.ts
12237
- import { existsSync as existsSync23, mkdirSync as mkdirSync12, readFileSync as readFileSync17, readdirSync as readdirSync7, rmSync as rmSync8, statSync as statSync8, writeFileSync as writeFileSync13 } from "fs";
12238
- import { join as join22 } from "path";
12275
+ import { existsSync as existsSync23, mkdirSync as mkdirSync12, readFileSync as readFileSync17, readdirSync as readdirSync8, rmSync as rmSync9, statSync as statSync8, writeFileSync as writeFileSync13 } from "fs";
12276
+ import { join as join23 } from "path";
12239
12277
  var DEFAULT_FLOOR_MS = 5e3;
12240
12278
  var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
12241
12279
  var defaultSleep = (ms2) => new Promise((resolve5) => setTimeout(resolve5, ms2));
@@ -12273,8 +12311,8 @@ function latestMtime(path) {
12273
12311
  if (stat.isFile()) return stat.mtimeMs;
12274
12312
  if (!stat.isDirectory()) return 0;
12275
12313
  let latest = stat.mtimeMs;
12276
- for (const entry of readdirSync7(path, { withFileTypes: true })) {
12277
- latest = Math.max(latest, latestMtime(join22(path, entry.name)));
12314
+ for (const entry of readdirSync8(path, { withFileTypes: true })) {
12315
+ latest = Math.max(latest, latestMtime(join23(path, entry.name)));
12278
12316
  }
12279
12317
  return latest;
12280
12318
  }
@@ -12286,14 +12324,14 @@ function makeStaleChecker(paths, opts = {}) {
12286
12324
  return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync23(opts.reloadSentinel);
12287
12325
  }
12288
12326
  function reloadSentinelFor(stateDir) {
12289
- return join22(stateDir, "reload-requested");
12327
+ return join23(stateDir, "reload-requested");
12290
12328
  }
12291
12329
  function daemonStampFor(stateDir) {
12292
- return join22(stateDir, "daemon.started");
12330
+ return join23(stateDir, "daemon.started");
12293
12331
  }
12294
12332
  function noteDaemonStarted(stateDir, now = Date.now()) {
12295
12333
  mkdirSync12(stateDir, { recursive: true });
12296
- rmSync8(reloadSentinelFor(stateDir), { force: true });
12334
+ rmSync9(reloadSentinelFor(stateDir), { force: true });
12297
12335
  writeFileSync13(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
12298
12336
  `);
12299
12337
  }
@@ -13113,7 +13151,7 @@ if (command === "passenger-sync-daemon") {
13113
13151
  }
13114
13152
  if (host === "linux" && hasSystemd) {
13115
13153
  const label = labelFor(CREW_HOME, "run");
13116
- const staleTimer = join23(homedir8(), ".config", "systemd", "user", `${label}.timer`);
13154
+ const staleTimer = join24(homedir8(), ".config", "systemd", "user", `${label}.timer`);
13117
13155
  if (existsSync24(staleTimer)) {
13118
13156
  process.stdout.write(
13119
13157
  `removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
@@ -13489,7 +13527,17 @@ ${changed2.map((c) => ` - ${c}
13489
13527
  const notified = await notify(c, cfg.ship, news, remit, dryRun);
13490
13528
  if (!notified && news.level === "fail") {
13491
13529
  const memberId = releaseSeat(c);
13492
- await applyFailureAlert(tracker, news, scope, all, memberId, remit, dryRun).catch((e) => {
13530
+ const placement = {};
13531
+ try {
13532
+ const repoId = repoIdForName(c, target.name);
13533
+ if (repoId) {
13534
+ placement.repo_id = repoId;
13535
+ const projectId = (await tracker.repoRow(repoId))?.project_id;
13536
+ if (projectId) placement.project_id = projectId;
13537
+ }
13538
+ } catch {
13539
+ }
13540
+ await applyFailureAlert(tracker, news, scope, all, memberId, remit, dryRun, placement).catch((e) => {
13493
13541
  remit.warn(`could not file/update a failure ticket: ${e.message}`, { step: "release" });
13494
13542
  });
13495
13543
  }
@@ -14111,7 +14159,7 @@ ${changed2.map((c) => ` - ${c}
14111
14159
  process.exit(1);
14112
14160
  }
14113
14161
  requireArmed("run the daemon loop");
14114
- const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join23(CREW_HOME, "src"), join23(CREW_HOME, "dist"), join23(CREW_HOME, "bin")];
14162
+ const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join24(CREW_HOME, "src"), join24(CREW_HOME, "dist"), join24(CREW_HOME, "bin")];
14115
14163
  const configPaths = daemonConfigPaths(cfg);
14116
14164
  watchPaths.push(...configPaths);
14117
14165
  noteDaemonStarted(cfg.ship.stateDir);
@@ -14791,7 +14839,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
14791
14839
  const keyPersisted = keychainBacked || apiKeyFile !== void 0;
14792
14840
  const wizard = firstRun;
14793
14841
  const writeFirstRun = wizard !== void 0 && !dryRun && keyPersisted;
14794
- const routeDir = value("dir") ?? (writeFirstRun ? join23(DEFAULT_REPOS_BASE_PATH, found.workspaceSlug) : void 0);
14842
+ const routeDir = value("dir") ?? (writeFirstRun ? join24(DEFAULT_REPOS_BASE_PATH, found.workspaceSlug) : void 0);
14795
14843
  const routeBlock = renderConnection(
14796
14844
  found,
14797
14845
  route2,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tablation/crew",
3
- "version": "0.47.0",
3
+ "version": "0.47.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",