@tablation/crew 0.40.1 → 0.41.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 +21 -10
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -5232,7 +5232,14 @@ function missingBlockerIds(tickets) {
5232
5232
  function blockerInfoMap(tickets, extra = []) {
5233
5233
  const info = {};
5234
5234
  for (const t of [...tickets, ...extra]) {
5235
- info[t.id] = { issue_id: t.issue_id, issue_tag: t.issue_tag, status: t.status };
5235
+ info[t.id] = {
5236
+ issue_id: t.issue_id,
5237
+ issue_tag: t.issue_tag,
5238
+ status: t.status,
5239
+ repo_id: t.repo_id,
5240
+ merged_at: t.merged_at,
5241
+ commit_sha: t.commit_sha
5242
+ };
5236
5243
  }
5237
5244
  return info;
5238
5245
  }
@@ -5241,14 +5248,18 @@ function computeBlockedIds(tickets, info) {
5241
5248
  for (const t of tickets) if (unresolvedBlockers(t, info).length > 0) blocked.add(t.id);
5242
5249
  return blocked;
5243
5250
  }
5244
- function unresolvedBlockers(t, info) {
5251
+ function unresolvedBlockers(t, info, opts = {}) {
5252
+ const mergeGated = opts.mergeGated ?? true;
5245
5253
  return (t.blocked_by ?? []).filter((id) => {
5246
- const status2 = info[id]?.status;
5247
- return status2 !== void 0 && !RESOLVED_STATUSES.has(status2);
5254
+ const b = info[id];
5255
+ const status2 = b?.status;
5256
+ if (!b || status2 === void 0) return false;
5257
+ if (!RESOLVED_STATUSES.has(status2)) return true;
5258
+ return mergeGated && status2 === "verified" && !!t.repo_id && b.repo_id === t.repo_id && !b.merged_at && !b.commit_sha;
5248
5259
  });
5249
5260
  }
5250
- function describeUnresolved(t, info) {
5251
- return unresolvedBlockers(t, info).map((id) => `${info[id]?.issue_tag ?? info[id]?.issue_id ?? "?"} (${info[id]?.status ?? "unknown"})`).join(", ");
5261
+ function describeUnresolved(t, info, opts = {}) {
5262
+ return unresolvedBlockers(t, info, opts).map((id) => `${info[id]?.issue_tag ?? info[id]?.issue_id ?? "?"} (${info[id]?.status ?? "unknown"})`).join(", ");
5252
5263
  }
5253
5264
  function sweepDiagnostics(tickets, info) {
5254
5265
  const dangling = [];
@@ -5317,7 +5328,7 @@ function filingErrors(tickets, opts = {}) {
5317
5328
  function sweepComment(step) {
5318
5329
  return step.action === "park" ? `Parked as **Blocked** by the crew \u2014 waiting on: ${step.blockers}.
5319
5330
 
5320
- This is automatic and reversible: an approved ticket whose \`Blocked by\` entries are not all resolved is parked out of the queue, and restored on the first poll after the last one resolves. Nothing else about the ticket changes. A blocker counts as resolved at \`verified\`, \`closed_deployed\`, \`closed_wont_fix\` or \`closed_duplicate\` \u2014 not at \`fixed\`, which is still an unmerged branch awaiting QA.` : `Restored to **Approved** by the crew \u2014 every blocker has resolved${step.blockers && step.blockers !== "\u2014" ? ` (${step.blockers})` : ""}.
5331
+ This is automatic and reversible: an approved ticket whose \`Blocked by\` entries are not all resolved is parked out of the queue, and restored on the first poll after the last one resolves. Nothing else about the ticket changes. A blocker counts as resolved at \`closed_deployed\`, \`closed_wont_fix\` or \`closed_duplicate\`, or at \`verified\` when it is in a different repository or already merged (\`merged_at\` set) \u2014 not at \`fixed\`, which is still an unmerged branch awaiting QA, and not at \`verified\` for a blocker in this ticket's own repository until the release has merged it, so nothing gets built stacked on an unmerged branch.` : `Restored to **Approved** by the crew \u2014 every blocker has resolved${step.blockers && step.blockers !== "\u2014" ? ` (${step.blockers})` : ""}.
5321
5332
 
5322
5333
  This restores the status it was approved at before being parked; it is back in the queue and a role may pick it up. If a blocker resolved as \`closed_wont_fix\` or \`closed_duplicate\` this ticket may still not be buildable as written \u2014 that is a call for a human, not the crew.`;
5323
5334
  }
@@ -5897,7 +5908,7 @@ ${sorted(held).map((t) => `| ${displayKey2(t)} | ${stopOf(t)} |`).join("\n")}
5897
5908
  "\nWork the first one you are not required to skip. Tickets waiting on an\nunresolved dependency are already out of this table \u2014 see Blocked below.\n",
5898
5909
  table(step2, i, header, row),
5899
5910
  "\n### Blocked \u2014 waiting on a dependency, not yours to start\n",
5900
- '\nListed rather than hidden so "nothing to do" stays distinguishable from\n"everything is parked". The loop parks and restores these itself, on every\npoll: an approved ticket whose `Blocked by` entries are unresolved moves to\n`blocked`, and back to `accepted` once the last one resolves. **Do not pick\none up, do not set or clear `blocked` by hand, and do not "unblock" one by\nediting its `Blocked by` field** \u2014 if a blocker looks wrong, say so in your\nrun summary. A blocker counts as resolved at `verified`, `closed_deployed`,\n`closed_wont_fix` or `closed_duplicate`; `fixed` is still an unmerged branch\nawaiting QA, so it does not count.\n',
5911
+ '\nListed rather than hidden so "nothing to do" stays distinguishable from\n"everything is parked". The loop parks and restores these itself, on every\npoll: an approved ticket whose `Blocked by` entries are unresolved moves to\n`blocked`, and back to `accepted` once the last one resolves. **Do not pick\none up, do not set or clear `blocked` by hand, and do not "unblock" one by\nediting its `Blocked by` field** \u2014 if a blocker looks wrong, say so in your\nrun summary. A blocker counts as resolved at `closed_deployed`,\n`closed_wont_fix` or `closed_duplicate`, or at `verified` when it is in another\nrepository or already merged; `fixed` is still an unmerged branch awaiting QA,\nand a `verified` blocker in the same repository as the blocked ticket is unmerged until the\nrelease merges it, so neither counts.\n',
5901
5912
  blockedTable(i.tickets.filter(isBlocked)),
5902
5913
  ...heldTable ? [heldTable] : []
5903
5914
  ]);
@@ -5926,7 +5937,7 @@ function qaDigest(i) {
5926
5937
  ### Waiting on blockers \u2014 not yours to verify yet
5927
5938
 
5928
5939
  A \`fixed\` or \`qa\` ticket whose \`Blocked by\` names something not yet resolved
5929
- (\`verified\`, \`closed_deployed\`, \`closed_wont_fix\` or \`closed_duplicate\`) is held out of
5940
+ (\`closed_deployed\`, \`closed_wont_fix\` or \`closed_duplicate\`, or \`verified\` once merged or in another repository) is held out of
5930
5941
  verification: its change needs the blocker released first. It becomes yours on
5931
5942
  the first poll after the last blocker resolves. No one needs to change the ticket.
5932
5943
 
@@ -8421,7 +8432,7 @@ function checkGuards(cwd, base = "main") {
8421
8432
  }
8422
8433
  function heldByBlockers(tickets, verifiedStatus, info) {
8423
8434
  if (!info) return [];
8424
- return tickets.filter((t) => t.status === verifiedStatus && unresolvedBlockers(t, info).length > 0).map((ticket) => ({ ticket, blockers: describeUnresolved(ticket, info) }));
8435
+ return tickets.filter((t) => t.status === verifiedStatus && unresolvedBlockers(t, info, { mergeGated: false }).length > 0).map((ticket) => ({ ticket, blockers: describeUnresolved(ticket, info, { mergeGated: false }) }));
8425
8436
  }
8426
8437
  var describeHeld = (held) => held.map((h) => `${h.ticket.issue_tag ?? h.ticket.issue_id} held: blocked by ${h.blockers}`);
8427
8438
  var CHANGELOG_LINE = /^Changelog:[ \t]*(.*)$/gm;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tablation/crew",
3
- "version": "0.40.1",
3
+ "version": "0.41.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",