@tablation/crew 0.49.4 → 0.49.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.
package/dist/cli.js
CHANGED
|
@@ -6112,6 +6112,7 @@ function operatorTodo(tickets, contract, operatorId) {
|
|
|
6112
6112
|
}
|
|
6113
6113
|
|
|
6114
6114
|
// src/digest.ts
|
|
6115
|
+
var DIRECTION_EVENT_TYPES = ["conflict_handed_back", "requeued", "bounced", "conflict_escalated"];
|
|
6115
6116
|
function displayKey2(t) {
|
|
6116
6117
|
return t.issue_tag ?? t.issue_id ?? "?";
|
|
6117
6118
|
}
|
|
@@ -6146,10 +6147,24 @@ function lastComment(i, ticketId) {
|
|
|
6146
6147
|
if (!last) return "\u2014";
|
|
6147
6148
|
return `${last.created_at.slice(0, 16)}Z ${author(last, i)}`;
|
|
6148
6149
|
}
|
|
6150
|
+
var eventAt = (e) => e.occurred_at ?? e.created_at ?? "";
|
|
6151
|
+
function pendingDirection(i, ticketId) {
|
|
6152
|
+
const mine = (i.events ?? []).filter((e) => e.record_id === ticketId);
|
|
6153
|
+
const pushed = mine.filter((e) => e.event_type === "pushed").map(eventAt).sort().pop() ?? "";
|
|
6154
|
+
return mine.filter((e) => DIRECTION_EVENT_TYPES.includes(e.event_type) && eventAt(e) > pushed).sort((a, b) => eventAt(a) < eventAt(b) ? 1 : -1)[0];
|
|
6155
|
+
}
|
|
6156
|
+
function directionCell(i, ticketId) {
|
|
6157
|
+
const d = pendingDirection(i, ticketId);
|
|
6158
|
+
if (!d) return "\u2014";
|
|
6159
|
+
const msg = (d.message ?? "").replace(/\s+/g, " ").trim().slice(0, 160);
|
|
6160
|
+
return `${d.event_type} ${eventAt(d).slice(0, 16)}Z${msg ? ` \u2014 ${msg}` : ""}`.replace(/\|/g, "/");
|
|
6161
|
+
}
|
|
6149
6162
|
function newFromOthers(i, ticketId) {
|
|
6150
|
-
|
|
6163
|
+
const comments = i.comments.filter(
|
|
6151
6164
|
(c) => c.ticket_id === ticketId && c.team_member_id !== i.me && c.created_at > i.watermark
|
|
6152
6165
|
).length;
|
|
6166
|
+
const d = pendingDirection(i, ticketId);
|
|
6167
|
+
return comments + (d && eventAt(d) > i.watermark ? 1 : 0);
|
|
6153
6168
|
}
|
|
6154
6169
|
function who(t, i) {
|
|
6155
6170
|
return whoAssigned(t, i) + heldBySuffix(t, i);
|
|
@@ -6188,14 +6203,14 @@ ${sorted(ts).map(row).join("\n")}
|
|
|
6188
6203
|
`;
|
|
6189
6204
|
}
|
|
6190
6205
|
function buildingDigest(i) {
|
|
6191
|
-
const header = "| ticket | repo | branch | worktree | mode | status | assignee | filed by | sev | pri | eff | updated | last comment | new since last poll |\n
|
|
6206
|
+
const header = "| ticket | repo | branch | worktree | mode | status | assignee | filed by | sev | pri | eff | updated | last comment | runner direction | new since last poll |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|";
|
|
6192
6207
|
const row = (t) => {
|
|
6193
6208
|
const n = newFromOthers(i, t.id);
|
|
6194
6209
|
const branch = i.branchFor ? i.branchFor(t) : "";
|
|
6195
6210
|
const worktree = i.worktreeFor ? i.worktreeFor(t) : "";
|
|
6196
6211
|
const dir = i.dirFor ? i.dirFor(t) : null;
|
|
6197
6212
|
const repo = i.dirFor ? dir ?? "**NO CHECKOUT**" : "";
|
|
6198
|
-
return `| ${displayKey2(t)} | ${repo} | ${branch} | ${worktree} | ${modeCell(t, i)} | ${t.status} | ${who(t, i)} | ${filedBy(t, i)} | ${t.severity ?? "\u2014"} | ${t.priority ?? "\u2014"} | ${effCell(t)} | ${t.updated_at.slice(0, 16)}Z | ${lastComment(i, t.id)} | ${n > 0 ? `**${n} new**` : "\u2014"} |`;
|
|
6213
|
+
return `| ${displayKey2(t)} | ${repo} | ${branch} | ${worktree} | ${modeCell(t, i)} | ${t.status} | ${who(t, i)} | ${filedBy(t, i)} | ${t.severity ?? "\u2014"} | ${t.priority ?? "\u2014"} | ${effCell(t)} | ${t.updated_at.slice(0, 16)}Z | ${lastComment(i, t.id)} | ${directionCell(i, t.id)} | ${n > 0 ? `**${n} new**` : "\u2014"} |`;
|
|
6199
6214
|
};
|
|
6200
6215
|
const blockedRow = (t) => `| ${displayKey2(t)} | ${t.status} | ${who(t, i)} | p${effectivePriority(t)} | ${blockers(t, i)} |`;
|
|
6201
6216
|
const blockedTable = (ts) => ts.length === 0 ? "\n_None._\n" : `
|
|
@@ -6226,6 +6241,7 @@ ${sorted(held).map((t) => `| ${displayKey2(t)} | ${stopOf(t)} |`).join("\n")}
|
|
|
6226
6241
|
"## Current queue \u2014 built for you by the poll\n",
|
|
6227
6242
|
"\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",
|
|
6228
6243
|
'\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',
|
|
6244
|
+
'\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`. It is direction, same as a comment from a hold: act on it.\n',
|
|
6229
6245
|
"\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",
|
|
6230
6246
|
...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"] : [],
|
|
6231
6247
|
"\n### Step 1 \u2014 open tickets that may be yours to act on\n",
|
|
@@ -6449,6 +6465,7 @@ async function decideCycle(o) {
|
|
|
6449
6465
|
tracker.projectWorkModes(),
|
|
6450
6466
|
tracker.events("stalled")
|
|
6451
6467
|
]);
|
|
6468
|
+
const directionEvents = (await Promise.all([...DIRECTION_EVENT_TYPES, "pushed"].map((t) => tracker.events(t)))).flat();
|
|
6452
6469
|
emit.emit(`${tickets.length} open ticket(s), ${comments.length} comment(s)`, {
|
|
6453
6470
|
data: { tickets: tickets.length, comments: comments.length }
|
|
6454
6471
|
});
|
|
@@ -6594,6 +6611,7 @@ async function decideCycle(o) {
|
|
|
6594
6611
|
selection,
|
|
6595
6612
|
actionable,
|
|
6596
6613
|
watermark,
|
|
6614
|
+
directionEvents,
|
|
6597
6615
|
selectionInput,
|
|
6598
6616
|
attention: attentionCurrent
|
|
6599
6617
|
};
|
|
@@ -6673,6 +6691,7 @@ function writeDigest(o, d, role, stateDir) {
|
|
|
6673
6691
|
ships: d.ships,
|
|
6674
6692
|
myShipId: d.ships.find((s) => (s.name ?? "").trim() === o.ship.name.trim())?.id ?? null,
|
|
6675
6693
|
watermark: d.watermark,
|
|
6694
|
+
events: d.directionEvents,
|
|
6676
6695
|
blocked: d.blocked,
|
|
6677
6696
|
blockerInfo: d.info
|
|
6678
6697
|
};
|
|
@@ -6777,7 +6796,14 @@ function worktreeForBranch(cwd, branch) {
|
|
|
6777
6796
|
}
|
|
6778
6797
|
return null;
|
|
6779
6798
|
}
|
|
6780
|
-
var
|
|
6799
|
+
var stampOf = (e) => e.occurred_at ?? e.created_at ?? "";
|
|
6800
|
+
var priorBounces = (ticket, events) => {
|
|
6801
|
+
const mine = events.filter((e) => e.record_id === ticket.id);
|
|
6802
|
+
const pushes = mine.filter((e) => e.event_type === "pushed").map(stampOf);
|
|
6803
|
+
return mine.filter(
|
|
6804
|
+
(e) => e.event_type === "conflict_handed_back" && pushes.some((p) => p > stampOf(e))
|
|
6805
|
+
).length;
|
|
6806
|
+
};
|
|
6781
6807
|
function planConflictBounce(cwd, ticket, branch, base, paths, events) {
|
|
6782
6808
|
return {
|
|
6783
6809
|
ticket,
|
|
@@ -13822,7 +13848,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
13822
13848
|
}
|
|
13823
13849
|
}
|
|
13824
13850
|
if (outcome.conflicts?.length || outcome.unbuildable?.length) {
|
|
13825
|
-
const handedBack = await tracker.events("conflict_handed_back");
|
|
13851
|
+
const handedBack = [...await tracker.events("conflict_handed_back"), ...await tracker.events("pushed")];
|
|
13826
13852
|
const flagged = await tracker.events("stranded_flagged");
|
|
13827
13853
|
const seat = releaseSeat(c);
|
|
13828
13854
|
for (const f of outcome.conflicts ?? []) {
|
package/package.json
CHANGED
|
@@ -141,6 +141,12 @@ Then:
|
|
|
141
141
|
the ticket again because the status was left alone.
|
|
142
142
|
- A hold gave new direction on an `in_progress` ticket → adjust the branch
|
|
143
143
|
and comment on what changed.
|
|
144
|
+
- A ticket whose `runner direction` cell is not `—` has been handed back by
|
|
145
|
+
the crew itself (a conflict with the base, a requeue, a bounce). That is
|
|
146
|
+
direction exactly as a comment from a hold is, and "no new comment" does not
|
|
147
|
+
cancel it: for `conflict_handed_back`, merge `<remote>/<base>` into the
|
|
148
|
+
branch, resolve, run the suite, push as a fast-forward and set `fixed`
|
|
149
|
+
(Step 4.4). Never answer it with "nothing to build".
|
|
144
150
|
- An `in_progress` ticket with no assignee and no new comment → still "up
|
|
145
151
|
for grabs, more work to do": a hold clearing the assignee *is* the signal.
|
|
146
152
|
- **An `in_progress` ticket assigned to you with no new comment is your own
|
|
@@ -397,8 +403,10 @@ worktree, runs the test gate and deploys. Three consequences:
|
|
|
397
403
|
lines from Step 3.5 are the only input the release phase has; nobody
|
|
398
404
|
reads the diff later to fill them in.
|
|
399
405
|
3. **A branch that no longer squash-merges cleanly comes back to you** at
|
|
400
|
-
`in_progress`, reassigned to your lane, with
|
|
401
|
-
|
|
406
|
+
`in_progress`, reassigned to your lane, with the hand-back
|
|
407
|
+
posted to the event log, which the digest's `runner direction` column
|
|
408
|
+
shows (a ticket there reads as new direction even with no new comment).
|
|
409
|
+
Fix it in the same worktree and take it to `fixed` again. "Never
|
|
402
410
|
merge" above means never merge INTO the base; merging the base INTO your
|
|
403
411
|
own ticket branch is the expected fix (rule 4), and it is not "by hand to
|
|
404
412
|
help".
|
|
@@ -142,6 +142,12 @@ Then:
|
|
|
142
142
|
the ticket again because the status was left alone.
|
|
143
143
|
- A hold gave new direction on an `in_progress` ticket → adjust the branch
|
|
144
144
|
and comment on what changed.
|
|
145
|
+
- A ticket whose `runner direction` cell is not `—` has been handed back by
|
|
146
|
+
the crew itself (a conflict with the base, a requeue, a bounce). That is
|
|
147
|
+
direction exactly as a comment from a hold is, and "no new comment" does not
|
|
148
|
+
cancel it: for `conflict_handed_back`, merge `<remote>/<base>` into the
|
|
149
|
+
branch, resolve, run the suite, push as a fast-forward and set `fixed`
|
|
150
|
+
(Step 4.4). Never answer it with "nothing to build".
|
|
145
151
|
- An `in_progress` ticket with no assignee and no new comment → still "up
|
|
146
152
|
for grabs, more work to do": a hold clearing the assignee *is* the signal.
|
|
147
153
|
- **An `in_progress` ticket assigned to you with no new comment is your own
|
|
@@ -397,8 +403,10 @@ worktree, runs the test gate and deploys. Three consequences:
|
|
|
397
403
|
lines from Step 3.5 are the only input the release phase has; nobody
|
|
398
404
|
reads the diff later to fill them in.
|
|
399
405
|
3. **A branch that no longer squash-merges cleanly comes back to you** at
|
|
400
|
-
`in_progress`, reassigned to your lane, with
|
|
401
|
-
|
|
406
|
+
`in_progress`, reassigned to your lane, with the hand-back
|
|
407
|
+
posted to the event log, which the digest's `runner direction` column
|
|
408
|
+
shows (a ticket there reads as new direction even with no new comment).
|
|
409
|
+
Fix it in the same worktree and take it to `fixed` again. "Never
|
|
402
410
|
merge" above means never merge INTO the base; merging the base INTO your
|
|
403
411
|
own ticket branch is the expected fix (rule 4), and it is not "by hand to
|
|
404
412
|
help".
|