@tablation/crew 0.127.3 → 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 +25 -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
  }
@@ -13134,12 +13145,14 @@ function baseLayer() {
13134
13145
  ].join("\n");
13135
13146
  return { name: "base", tag: `crew-sandbox-base:${hash({ SANDBOX_BASE_IMAGE, SANDBOX_OPENSHELL_VERSION })}`, dockerfile, files: {} };
13136
13147
  }
13148
+ var APT_HTTPS_COMMAND = "sed -i -E 's#http://([a-z0-9.-]*\\.)?ubuntu\\.com#https://\\1ubuntu.com#g' /etc/apt/sources.list.d/ubuntu.sources 2>/dev/null || true";
13137
13149
  function crewLayer(base, intel, bridge) {
13138
13150
  const files = { "credential-helper": CREDENTIAL_HELPER, "mcp.json": MCP_CONFIG, ...bridge ? { "intel-bridge": bridge } : {} };
13139
13151
  const env = Object.entries(CREW_ENV).map(([k, v]) => `${k}=${v}`).join(" ");
13140
13152
  const dockerfile = [
13141
13153
  `FROM ${base.tag}`,
13142
13154
  "USER root",
13155
+ run2(APT_HTTPS_COMMAND),
13143
13156
  run2(`apt-get update && apt-get install -y --no-install-recommends ca-certificates curl git${intel ? " python3 python3-venv nodejs" : ""} && rm -rf /var/lib/apt/lists/*`),
13144
13157
  `COPY credential-helper ${CREW_PREFIX}/bin/credential-helper`,
13145
13158
  `COPY mcp.json ${CREW_PREFIX}/mcp/mcp.json`,
@@ -13188,6 +13201,7 @@ function repoLayer(repo, decl, crew, warm) {
13188
13201
  lines2.push(`FROM ${crew.tag}`, "USER root");
13189
13202
  }
13190
13203
  if (decl.packages.length > 0) {
13204
+ lines2.push(run2(APT_HTTPS_COMMAND));
13191
13205
  lines2.push(run2(`apt-get update && apt-get install -y --no-install-recommends ${decl.packages.join(" ")} && rm -rf /var/lib/apt/lists/*`));
13192
13206
  }
13193
13207
  lines2.push(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tablation/crew",
3
- "version": "0.127.3",
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",