@tablation/crew 0.127.4 → 0.127.5

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 +22 -11
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -9683,18 +9683,28 @@ async function missingAcceptance(i, t) {
9683
9683
  if (lines2.length === 0 || !i.writer.comments) return null;
9684
9684
  const cc = i.contract.comments;
9685
9685
  const mine = (await i.writer.comments()).map((c) => c).filter((r) => r[cc.parent] === t.id && r[cc.author] === i.seatId).sort((a, b) => String(a[cc.createdAt] ?? "").localeCompare(String(b[cc.createdAt] ?? "")));
9686
- const last = mine.at(-1);
9687
- const missing = unquotedAcceptance(lines2, last ? String(last[cc.body] ?? "") : "");
9688
- return missing.length > 0 ? missing : null;
9686
+ const from = i.since ? Date.parse(i.since) : NaN;
9687
+ const recent = Number.isNaN(from) ? [] : mine.filter((r) => Date.parse(String(r[cc.createdAt] ?? "")) >= from);
9688
+ const candidates2 = recent.length > 0 ? recent : mine.slice(-1);
9689
+ if (candidates2.length === 0) return { missing: lines2, judged: null };
9690
+ let best = null;
9691
+ for (const c of candidates2) {
9692
+ const missing = unquotedAcceptance(lines2, String(c[cc.body] ?? ""));
9693
+ if (missing.length === 0) return null;
9694
+ if (!best || missing.length <= best.missing.length) best = { missing, judged: String(c[cc.createdAt] ?? "") || null };
9695
+ }
9696
+ return best;
9689
9697
  }
9690
- function acceptanceBounceComment(missing, shipName2) {
9698
+ function acceptanceBounceComment(missing, shipName2, judged) {
9691
9699
  return `${ACCEPTANCE_BOUNCED_MARKER}
9692
9700
 
9693
9701
  **Hand-off bounced: the hand-off comment does not quote every line of the ticket's Acceptance section.**
9694
9702
 
9695
9703
  ${shipName2 ? `Ship: ${shipName2}
9696
9704
 
9697
- ` : ""}Not quoted verbatim:
9705
+ ` : ""}${judged ? `Judged the comment posted at ${judged}; no comment this run quoted every line.
9706
+
9707
+ ` : "This seat has no comment on the ticket.\n\n"}Not quoted verbatim:
9698
9708
  ${missing.map((l) => `- ${l}`).join("\n")}
9699
9709
 
9700
9710
  Set back to \`in_progress\`. Post a comment that lists every Acceptance line verbatim, each followed by the test, command or manual check that exercised it in this run and its result (a line you could not exercise is listed with the reason, never omitted), then set \`fixed\` again. The branch is already pushed.`;
@@ -9715,8 +9725,8 @@ async function enforceHandoff(i) {
9715
9725
  return { kind: "unverified", why: v.why };
9716
9726
  }
9717
9727
  if (v.kind === "pushed" && !claimed) {
9718
- const missing = await missingAcceptance(i, t);
9719
- if (missing) {
9728
+ const gap = await missingAcceptance(i, t);
9729
+ if (gap) {
9720
9730
  const heldNow = i.hold ? t[i.hold.column] : void 0;
9721
9731
  if (i.hold && heldNow && heldNow !== i.hold.shipId || await takenOverSince(i, t.id)) {
9722
9732
  i.log.warn(`${t.issue_id} is held by another ship now \u2014 the acceptance check is stale, left alone`, log);
@@ -9724,10 +9734,11 @@ async function enforceHandoff(i) {
9724
9734
  }
9725
9735
  await i.writer.updateTicket(t.id, {
9726
9736
  status: i.contract.statuses.building,
9727
- ...t.assignee_id ? {} : { assignee_id: i.seatId },
9737
+ // CREW-2962: the hand-off may have assigned QA's seat; a bounce is the building seat's again.
9738
+ assignee_id: i.seatId,
9728
9739
  ...i.hold && !heldNow ? { [i.hold.column]: i.hold.shipId } : {}
9729
9740
  });
9730
- await i.writer.postEvent(t.id, acceptanceBounceComment(missing, i.shipName), i.seatId, "bounced").catch(() => {
9741
+ await i.writer.postEvent(t.id, acceptanceBounceComment(gap.missing, i.shipName, gap.judged), i.seatId, "bounced").catch(() => {
9731
9742
  i.log.warn("bounced, but the note failed to post", log);
9732
9743
  });
9733
9744
  i.log.warn(`${t.issue_id} bounced back to ${i.contract.statuses.building}: hand-off comment lacks the acceptance checklist`, log);
@@ -9781,7 +9792,7 @@ async function enforceHandoff(i) {
9781
9792
  }
9782
9793
  await i.writer.updateTicket(t.id, {
9783
9794
  status: i.contract.statuses.building,
9784
- ...t.assignee_id ? {} : { assignee_id: i.seatId },
9795
+ assignee_id: i.seatId,
9785
9796
  ...i.hold && !heldBy ? { [i.hold.column]: i.hold.shipId } : {}
9786
9797
  });
9787
9798
  await i.writer.postEvent(t.id, bounceComment(v, i.shipName, pushError), i.seatId, "bounced").catch(() => {
@@ -9921,7 +9932,7 @@ ${i.shipName ? `${i.shipName}'s ` : "A "}${i.role} run set it to \`${st.handoff}
9921
9932
  i.log.warn(`${t.issue_id}: \`${v.remote}/${v.branch}\` is at ${v.origin.slice(0, 7)}, not a tip this checkout built \u2014 left alone`, at);
9922
9933
  continue;
9923
9934
  }
9924
- await i.writer.updateTicket(t.id, { status: st.building, ...t.assignee_id ? {} : { assignee_id: i.seatId } });
9935
+ await i.writer.updateTicket(t.id, { status: st.building, assignee_id: i.seatId });
9925
9936
  await i.writer.postEvent(t.id, bounceComment(v, i.shipName), i.seatId, "bounced").catch(() => {
9926
9937
  });
9927
9938
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tablation/crew",
3
- "version": "0.127.4",
3
+ "version": "0.127.5",
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",