@tablation/crew 0.54.1 → 0.54.3
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.
- package/dist/cli.js +34 -6
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6229,7 +6229,16 @@ function operatorTodo(tickets, contract, operatorId) {
|
|
|
6229
6229
|
}
|
|
6230
6230
|
|
|
6231
6231
|
// src/digest.ts
|
|
6232
|
-
var DIRECTION_EVENT_TYPES = [
|
|
6232
|
+
var DIRECTION_EVENT_TYPES = [
|
|
6233
|
+
"conflict_handed_back",
|
|
6234
|
+
"requeued",
|
|
6235
|
+
"bounced",
|
|
6236
|
+
"conflict_escalated",
|
|
6237
|
+
"interrupted",
|
|
6238
|
+
// The release gate's isolation path hands a ticket back as `build_failed`: it builds alone but not merged with the base.
|
|
6239
|
+
"build_failed"
|
|
6240
|
+
];
|
|
6241
|
+
var STATUS_EVENT_TYPES = ["gate_red"];
|
|
6233
6242
|
function displayKey2(t) {
|
|
6234
6243
|
return t.issue_tag ?? t.issue_id ?? "?";
|
|
6235
6244
|
}
|
|
@@ -6278,10 +6287,18 @@ function pendingDirection(i, ticketId) {
|
|
|
6278
6287
|
}
|
|
6279
6288
|
function directionCell(i, ticketId) {
|
|
6280
6289
|
const d = pendingDirection(i, ticketId);
|
|
6281
|
-
if (!d) return
|
|
6290
|
+
if (!d) return statusCell(i, ticketId);
|
|
6282
6291
|
const msg = (d.message ?? "").replace(/\s+/g, " ").trim().slice(0, 160);
|
|
6283
6292
|
return `${d.event_type} ${eventAt(d).slice(0, 16)}Z${msg ? ` \u2014 ${msg}` : ""}`.replace(/\|/g, "/");
|
|
6284
6293
|
}
|
|
6294
|
+
function statusCell(i, ticketId) {
|
|
6295
|
+
const mine = (i.events ?? []).filter((e) => e.record_id === ticketId);
|
|
6296
|
+
const pushed = mine.filter((e) => e.event_type === "pushed").map(eventAt).sort().pop() ?? "";
|
|
6297
|
+
const s = mine.filter((e) => STATUS_EVENT_TYPES.includes(e.event_type) && eventAt(e) > pushed).sort((a, b) => eventAt(a) < eventAt(b) ? 1 : -1)[0];
|
|
6298
|
+
if (!s) return "\u2014";
|
|
6299
|
+
const msg = (s.message ?? "").replace(/\s+/g, " ").trim().slice(0, 160);
|
|
6300
|
+
return `${s.event_type} ${eventAt(s).slice(0, 16)}Z (status only, nothing to build)${msg ? ` \u2014 ${msg}` : ""}`.replace(/\|/g, "/");
|
|
6301
|
+
}
|
|
6285
6302
|
function newFromOthers(i, ticketId) {
|
|
6286
6303
|
const comments = i.comments.filter(
|
|
6287
6304
|
(c) => c.ticket_id === ticketId && c.team_member_id !== i.me && c.created_at > i.watermark
|
|
@@ -6364,7 +6381,7 @@ ${sorted(held).map((t) => `| ${displayKey2(t)} | ${stopOf(t)} |`).join("\n")}
|
|
|
6364
6381
|
"## Current queue \u2014 built for you by the poll\n",
|
|
6365
6382
|
"\nAlready filtered to your lane, and already ordered by the Step 2 rule.\nTicket bodies are deliberately omitted: fetch the full record of only the\nticket you actually pick up. **Do not re-fetch the whole tracker.** This\ndigest comes from the same API call the poll just made, moments ago.\n",
|
|
6366
6383
|
'\n"new since last poll" counts comments from someone other than you since\nthe poll watermark \u2014 the same signal that woke this run.\n',
|
|
6367
|
-
'\n"runner direction" is the newest instruction the crew itself posted to the\nevent log (not Comments) that no push has answered yet: `conflict_handed_back`\n(merge the base into the branch and resolve), `requeued`, `bounced`,\n`conflict_escalated`, `interrupted` (an earlier run on this seat was cut short; its\nuncommitted worktree is yours, resume it). It is
|
|
6384
|
+
'\n"runner direction" is the newest instruction the crew itself posted to the\nevent log (not Comments) that no push has answered yet: `conflict_handed_back`\n(merge the base into the branch and resolve), `requeued`, `bounced`,\n`conflict_escalated`, `interrupted` (an earlier run on this seat was cut short; its\nuncommitted worktree is yours, resume it), `build_failed` (the release gate found the branch builds alone but\nnot merged with the current base: merge the base in, fix, push, hand back to QA; the output is quoted). It is\ndirection, same as a comment from a hold: act on it. `gate_red` is status only: the ticket is merged and the\ngate is red, so there is nothing to build on the branch.\n',
|
|
6368
6385
|
"\n**`repo` is the checkout a ticket's work happens in** \u2014 an area spans\nseveral repositories, so cut the worktree beside THAT directory, not beside\nwhichever one you started in. A ticket marked **NO CHECKOUT** is not yours:\nthis ship has no clone of its repository, and another ship may serve it.\n\n**Use the `branch` and `worktree` columns verbatim.** They are that\nrepository's own naming convention, rendered for THIS ticket \u2014 cut the\nworktree at `<repo>/../<worktree>`, on branch `<branch>`. Do not derive\neither yourself, and prefer these over the Environment section's own worked\nexample if the two ever disagree \u2014 that example is illustrative only,\nrendered before any ticket is picked.\n",
|
|
6369
6386
|
...i.workModes && [...i.workModes.modes.values()].some((m) => m !== "automatic") ? ["\n**Manual / hybrid projects.** Some projects on this board are not fully automatic. In a `manual` project no lane touches any ticket, and a ticket with no project is treated the same way, so none appear below. In a `hybrid` project a ticket appears below only when it is assigned to an agent. An `accepted` ticket that is absent for either reason is not yours to claim \u2014 do not go looking for it.\n"] : [],
|
|
6370
6387
|
"\n### Step 1 \u2014 open tickets that may be yours to act on\n",
|
|
@@ -6603,7 +6620,7 @@ async function decideCycle(o) {
|
|
|
6603
6620
|
optional("project work modes", tracker.projectWorkModes(), /* @__PURE__ */ new Map()),
|
|
6604
6621
|
tracker.events("stalled")
|
|
6605
6622
|
]);
|
|
6606
|
-
const directionEvents = (await Promise.all([...DIRECTION_EVENT_TYPES, "pushed"].map((t) => tracker.events(t)))).flat();
|
|
6623
|
+
const directionEvents = (await Promise.all([...DIRECTION_EVENT_TYPES, ...STATUS_EVENT_TYPES, "pushed"].map((t) => tracker.events(t)))).flat();
|
|
6607
6624
|
emit.emit(`${tickets.length} open ticket(s), ${comments.length} comment(s)`, {
|
|
6608
6625
|
data: { tickets: tickets.length, comments: comments.length }
|
|
6609
6626
|
});
|
|
@@ -10490,7 +10507,7 @@ var isolateCap = (o) => o.repo.release.isolateFailures?.maxCandidates ?? DEFAULT
|
|
|
10490
10507
|
async function isolateAndRetry(o, name, output, merged, preMerge, decision, fresh, releaseBranch) {
|
|
10491
10508
|
const applied = merged.filter((c) => c.sha && c.branch);
|
|
10492
10509
|
const cap = isolateCap(o);
|
|
10493
|
-
if (o.dryRun || !preMerge || applied.length <
|
|
10510
|
+
if (o.dryRun || !preMerge || applied.length < 1 || cap === 0) return null;
|
|
10494
10511
|
const head = headSha(o.cwd);
|
|
10495
10512
|
const withFiles = applied.map((c) => ({
|
|
10496
10513
|
c,
|
|
@@ -10503,6 +10520,17 @@ async function isolateAndRetry(o, name, output, merged, preMerge, decision, fres
|
|
|
10503
10520
|
if (byFile) {
|
|
10504
10521
|
o.emit.warn(`${name} failed; its output names files only ${byFile.c.branch} changed \u2014 treating it as the culprit`, { ticket: byFile.c.ticket.issue_id });
|
|
10505
10522
|
culprits.push({ c: byFile.c, how: "files", tail: lastLines(output, HOOK_FAILURE_TAIL_LINES) });
|
|
10523
|
+
} else if (applied.length === 1) {
|
|
10524
|
+
const only = applied[0];
|
|
10525
|
+
gitOk(o.cwd, ["reset", "--hard", preMerge]);
|
|
10526
|
+
const baseRun = await hook(o, name);
|
|
10527
|
+
if (baseRun && baseRun.code !== 0) {
|
|
10528
|
+
o.emit.warn(`${name} fails on the base without ${only.branch} \u2014 not blaming the branch`, { ticket: only.ticket.issue_id });
|
|
10529
|
+
gitOk(o.cwd, ["reset", "--hard", head]);
|
|
10530
|
+
return null;
|
|
10531
|
+
}
|
|
10532
|
+
o.emit.warn(`${name} failed with only ${only.branch} merged and passes without it \u2014 treating it as the culprit`, { ticket: only.ticket.issue_id });
|
|
10533
|
+
culprits.push({ c: only, how: "solo", tail: lastLines(output, HOOK_FAILURE_TAIL_LINES) });
|
|
10506
10534
|
} else if (applied.length > cap) {
|
|
10507
10535
|
o.emit.warn(`${name} failed with ${applied.length} branches merged, over release.isolateFailures.maxCandidates (${cap}) \u2014 not isolating`);
|
|
10508
10536
|
return null;
|
|
@@ -10691,7 +10719,7 @@ async function mergeAndRelease(o, decision, fresh, releaseBranch, isolated = fal
|
|
|
10691
10719
|
if (o.repo.hooks.build) {
|
|
10692
10720
|
if (o.dryRun) {
|
|
10693
10721
|
o.emit.emit(`would run: ${hookLabel(o.repo, "build")}`);
|
|
10694
|
-
if (applied
|
|
10722
|
+
if (applied >= 1) {
|
|
10695
10723
|
o.emit.emit(
|
|
10696
10724
|
`if it fails, would find the one of ${applied} merged branches that broke it (by the files it names, else by building each alone, up to ${isolateCap(o)}), hand that back to the dev lane and ship the rest`
|
|
10697
10725
|
);
|
package/package.json
CHANGED