@tablation/crew 0.52.0 → 0.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +36 -24
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1435,8 +1435,8 @@ var TOP_LEVEL = /* @__PURE__ */ new Set([
1435
1435
  "labels",
1436
1436
  "release"
1437
1437
  ]);
1438
- var BRANCH_KEYS = /* @__PURE__ */ new Set(["base", "name", "push", "remote"]);
1439
- var PLACEHOLDER = /\{(key|number|slug|role|prefix|tag)\}/g;
1438
+ var BRANCH_KEYS = /* @__PURE__ */ new Set(["base", "name", "push", "remote", "lowercase"]);
1439
+ var PLACEHOLDER = /\{(key|number|slug|role|prefix|tag|issue)\}/g;
1440
1440
  var WORKTREE_PLACEHOLDERS = ["dir", "issue", "number", "key", "slug", "role", "prefix", "tag"];
1441
1441
  function worktreeNameProblem(template) {
1442
1442
  const valid = `Valid placeholders: ${WORKTREE_PLACEHOLDERS.map((p) => `{${p}}`).join(" ")}.`;
@@ -1528,7 +1528,7 @@ function parseRepoConfig(text, file) {
1528
1528
  if (!PLACEHOLDER.test(branchName)) {
1529
1529
  PLACEHOLDER.lastIndex = 0;
1530
1530
  throw new RepoConfigError(
1531
- `${file}: branch.name ("${branchName}") names no placeholder \u2014 every ticket would get the same branch. Use at least one of {key} {number} {slug} {role}.`
1531
+ `${file}: branch.name ("${branchName}") names no placeholder \u2014 every ticket would get the same branch. Use at least one of {key} {number} {slug} {role} {prefix} {tag} {issue}.`
1532
1532
  );
1533
1533
  }
1534
1534
  PLACEHOLDER.lastIndex = 0;
@@ -1536,7 +1536,8 @@ function parseRepoConfig(text, file) {
1536
1536
  base: String(raw.branch?.base ?? "main"),
1537
1537
  name: branchName,
1538
1538
  push: String(raw.branch?.push ?? branchName),
1539
- remote: String(raw.branch?.remote ?? "origin")
1539
+ remote: String(raw.branch?.remote ?? "origin"),
1540
+ ...raw.branch?.lowercase !== void 0 ? { lowercase: parseLowercase(raw.branch.lowercase, file) } : {}
1540
1541
  };
1541
1542
  const copyRaw = raw.worktrees?.copy;
1542
1543
  if (copyRaw !== void 0 && !Array.isArray(copyRaw)) {
@@ -1733,9 +1734,13 @@ function slugify(title, maxLength = 40) {
1733
1734
  const s = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, maxLength).replace(/-+$/, "");
1734
1735
  return s || "work";
1735
1736
  }
1737
+ function parseLowercase(v, where) {
1738
+ if (typeof v !== "boolean") throw new RepoConfigError(`${where}: branch.lowercase must be true or false`);
1739
+ return v;
1740
+ }
1736
1741
  function renderBranchName(template, ctx) {
1737
1742
  const number = ctx.key.replace(/^\D+/, "");
1738
- return template.replace(PLACEHOLDER, (_m, name) => {
1743
+ const rendered = template.replace(PLACEHOLDER, (_m, name) => {
1739
1744
  switch (name) {
1740
1745
  case "key":
1741
1746
  return ctx.key;
@@ -1749,14 +1754,16 @@ function renderBranchName(template, ctx) {
1749
1754
  return ctx.tag ?? ctx.key;
1750
1755
  case "prefix":
1751
1756
  return ctx.prefix ?? "";
1757
+ case "issue":
1758
+ return (ctx.tag ?? ctx.key).toLowerCase();
1752
1759
  default:
1753
1760
  return "";
1754
1761
  }
1755
1762
  });
1763
+ return ctx.lowercase ? rendered.toLowerCase() : rendered;
1756
1764
  }
1757
1765
  function renderWorktreeName(template, ctx) {
1758
- const issue = (ctx.tag ?? ctx.key).toLowerCase();
1759
- return renderBranchName(template.replace(/\{dir\}/g, ctx.dir).replace(/\{issue\}/g, issue), ctx);
1766
+ return renderBranchName(template.replace(/\{dir\}/g, ctx.dir), ctx);
1760
1767
  }
1761
1768
  function defaultWorktreePrefix(dir) {
1762
1769
  const name = basename2(dir.replace(/[/\\]+$/, ""));
@@ -1856,7 +1863,8 @@ function resolveRepoConfig(repo, ship, dir) {
1856
1863
  repo?.branch.remote === "origin" ? void 0 : repo?.branch.remote,
1857
1864
  ship?.branch?.remote,
1858
1865
  "origin"
1859
- )
1866
+ ),
1867
+ ...pick("branch.lowercase", repo?.branch.lowercase, ship?.branch?.lowercase, false) ? { lowercase: true } : {}
1860
1868
  },
1861
1869
  // Unlike every other field here, platform has no ship-level fallback: what
1862
1870
  // a repo needs of a host is a fact about that repo alone (its build
@@ -2055,7 +2063,7 @@ function parseWorktreeName(v, where, file) {
2055
2063
  return String(v);
2056
2064
  }
2057
2065
  var REPO_ENTRY_KEYS = /* @__PURE__ */ new Set(["dir", "hooks", "labels", "release", "branch", "hostPassengers", "worktreeName", "toolchain"]);
2058
- var BRANCH_OVERRIDE_KEYS = /* @__PURE__ */ new Set(["base", "name", "push", "remote"]);
2066
+ var BRANCH_OVERRIDE_KEYS = /* @__PURE__ */ new Set(["base", "name", "push", "remote", "lowercase"]);
2059
2067
  var RELEASE_OVERRIDE_KEYS = /* @__PURE__ */ new Set([
2060
2068
  "mode",
2061
2069
  "ci",
@@ -2139,8 +2147,12 @@ function parseBranchOverride(raw, where, file) {
2139
2147
  const name = raw.name !== void 0 ? String(raw.name) : void 0;
2140
2148
  const push = raw.push !== void 0 ? String(raw.push) : void 0;
2141
2149
  const remote = raw.remote !== void 0 ? String(raw.remote) : void 0;
2142
- if (base === void 0 && name === void 0 && push === void 0 && remote === void 0) return void 0;
2143
- return { base, name, push, remote };
2150
+ if (raw.lowercase !== void 0 && typeof raw.lowercase !== "boolean") {
2151
+ throw new ConfigError(`${file}: ${where}.branch.lowercase must be true or false`);
2152
+ }
2153
+ const lowercase = raw.lowercase;
2154
+ if (base === void 0 && name === void 0 && push === void 0 && remote === void 0 && lowercase === void 0) return void 0;
2155
+ return { base, name, push, remote, ...lowercase !== void 0 ? { lowercase } : {} };
2144
2156
  }
2145
2157
  function mergeRouteRelease(route, override) {
2146
2158
  const provider = override?.ci?.provider ?? route.ci?.provider;
@@ -6355,13 +6367,13 @@ ${waiting.map((t) => `| ${displayKey2(t)} | ${t.status} | ${blockers(t, i)} |`).
6355
6367
 
6356
6368
  // src/ticket-branch.ts
6357
6369
  import { basename as basename5 } from "path";
6358
- function branchRenderer(t, role) {
6370
+ function branchRenderer(t, role, lowercase) {
6359
6371
  const { tag, prefix } = ticketBranchContext(t);
6360
- return (template) => renderBranchName(template, { key: t.issue_id, title: t.title ?? void 0, role, tag, prefix });
6372
+ return (template) => renderBranchName(template, { key: t.issue_id, title: t.title ?? void 0, role, tag, prefix, lowercase });
6361
6373
  }
6362
6374
  function worktreeForTicket(dir, cfg, t, role) {
6363
6375
  const { prefix } = ticketBranchContext(t);
6364
- const branchName = branchRenderer(t, role)(effectiveBranchTemplate(cfg, prefix));
6376
+ const branchName = branchRenderer(t, role, cfg.branch.lowercase)(effectiveBranchTemplate(cfg, prefix));
6365
6377
  const number = t.issue_id.replace(/^\D+/, "");
6366
6378
  const { tag } = ticketBranchContext(t);
6367
6379
  return effectiveWorktreeDirName(cfg, dir, branchName, number, (template) => renderWorktreeName(template, {
@@ -6380,10 +6392,10 @@ function branchTemplates(cfg, t) {
6380
6392
  return { name, push };
6381
6393
  }
6382
6394
  function pushedBranchForTicket(cfg, t, role) {
6383
- return branchRenderer(t, role)(branchTemplates(cfg, t).push);
6395
+ return branchRenderer(t, role, cfg.branch.lowercase)(branchTemplates(cfg, t).push);
6384
6396
  }
6385
6397
  function existingBranchForTicket(dir, cfg, t, role) {
6386
- return branchForIssue(dir, t.issue_id, branchTemplates(cfg, t), branchRenderer(t, role));
6398
+ return branchForIssue(dir, t.issue_id, branchTemplates(cfg, t), branchRenderer(t, role, cfg.branch.lowercase));
6387
6399
  }
6388
6400
  function locateBranchForTicket(dir, cfg, t, role, fetchedDirs) {
6389
6401
  const local = existingBranchForTicket(dir, cfg, t, role);
@@ -6394,7 +6406,7 @@ function locateBranchForTicket(dir, cfg, t, role, fetchedDirs) {
6394
6406
  fetchedDirs.add(dir);
6395
6407
  fetchRemote(dir, remote);
6396
6408
  }
6397
- const found = remoteBranchForIssue(dir, remote, t.issue_id, branchTemplates(cfg, t), branchRenderer(t, role));
6409
+ const found = remoteBranchForIssue(dir, remote, t.issue_id, branchTemplates(cfg, t), branchRenderer(t, role, cfg.branch.lowercase));
6398
6410
  return found ? { branch: found, where: "remote" } : null;
6399
6411
  }
6400
6412
  function materializeBranchForTicket(dir, cfg, t, role, onProblem) {
@@ -6712,14 +6724,14 @@ function writeDigest(o, d, role, stateDir) {
6712
6724
  };
6713
6725
  const fetchedDirs = /* @__PURE__ */ new Set();
6714
6726
  const dirFor = (t) => dirForRepo(o.route, t.repo_id);
6715
- const render2 = (t) => branchRenderer(t, role);
6727
+ const render2 = (t, lowercase) => branchRenderer(t, role, lowercase);
6716
6728
  const input = {
6717
6729
  dirFor,
6718
6730
  repoStop: repoStopFor(openShipAttention(o.state), o.route),
6719
6731
  branchFor: (t) => {
6720
6732
  const cfg = repoFor(dirFor(t) ?? o.route.dir);
6721
6733
  const { prefix } = ticketBranchContext(t);
6722
- return render2(t)(effectiveBranchTemplate(cfg, prefix));
6734
+ return render2(t, cfg.branch.lowercase)(effectiveBranchTemplate(cfg, prefix));
6723
6735
  },
6724
6736
  // ISSUE-1028: the agent previously had to derive this itself from the
6725
6737
  // Environment section's own worked example, which is rendered before
@@ -7210,7 +7222,7 @@ function repoPrefix(repo) {
7210
7222
  function branchExample(repo, key) {
7211
7223
  const prefix = repoPrefix(repo);
7212
7224
  const template = effectiveBranchTemplate(repo, prefix);
7213
- return renderBranchName(template, { key, title: "Fix the widget", role: "dev", prefix });
7225
+ return renderBranchName(template, { key, title: "Fix the widget", role: "dev", prefix, lowercase: repo.branch.lowercase });
7214
7226
  }
7215
7227
  function repoSection(r, key, heading) {
7216
7228
  const repo = r.config;
@@ -8957,7 +8969,7 @@ var describeRunWorktreePlan = (p) => {
8957
8969
  var planIsUsable = (p) => p.action !== "leave-dirty" && p.action !== "leave-unpushed";
8958
8970
  function newBranchName(cfg, t, role) {
8959
8971
  const { prefix } = ticketBranchContext(t);
8960
- return branchRenderer(t, role)(effectiveBranchTemplate(cfg, prefix));
8972
+ return branchRenderer(t, role, cfg.branch.lowercase)(effectiveBranchTemplate(cfg, prefix));
8961
8973
  }
8962
8974
  function planRunWorktree(dir, cfg, t, role, opts = {}) {
8963
8975
  const { remote, base } = cfg.branch;
@@ -9721,9 +9733,9 @@ function reviewProblems(repo, contract) {
9721
9733
  if (!reviewApplies(repo, contract)) return [];
9722
9734
  const problems = [];
9723
9735
  const template = repo.branch.push ?? repo.branch.name;
9724
- if (!/\{(key|number|tag)\}/.test(template)) {
9736
+ if (!/\{(key|number|tag|issue)\}/.test(template)) {
9725
9737
  problems.push(
9726
- `branch.push "${template}" names none of {key}, {number} or {tag} \u2014 with release.mode "external" and a reviewing status, the pushed branch is the only link back to its ticket`
9738
+ `branch.push "${template}" names none of {key}, {number}, {tag} or {issue} \u2014 with release.mode "external" and a reviewing status, the pushed branch is the only link back to its ticket`
9727
9739
  );
9728
9740
  }
9729
9741
  return problems;
@@ -16139,7 +16151,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
16139
16151
  const branch = found.branch ?? branchForIssue(target.dir, `ISSUE-${n}`, {
16140
16152
  name: target.config.branch.name,
16141
16153
  push: target.config.branch.push
16142
- }, (t) => renderBranchName(t, { key: `ISSUE-${n}` }));
16154
+ }, (t) => renderBranchName(t, { key: `ISSUE-${n}`, lowercase: target.config.branch.lowercase }));
16143
16155
  const pids = pidsInWorktree(wt, num);
16144
16156
  if (pids.length) {
16145
16157
  emit.emit(`${dryRun ? "would kill" : "killing"} processes still inside ${wt}: ${pids.join(" ")}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tablation/crew",
3
- "version": "0.52.0",
3
+ "version": "0.53.0",
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",