@tablation/crew 0.19.5 → 0.20.1
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 +39 -5
- package/package.json +1 -1
- package/prompts/default/personas/common.md +5 -0
package/dist/cli.js
CHANGED
|
@@ -1792,6 +1792,7 @@ var DEFAULT_CONTRACT = {
|
|
|
1792
1792
|
blockedBy: "blocked_by",
|
|
1793
1793
|
updatedAt: "updated_at",
|
|
1794
1794
|
needsDesign: "needs_design",
|
|
1795
|
+
workMode: "work_mode",
|
|
1795
1796
|
reportType: "report_type",
|
|
1796
1797
|
epic: "epic_id",
|
|
1797
1798
|
author: "filed_by_id",
|
|
@@ -1840,6 +1841,7 @@ var DEFAULT_CONTRACT = {
|
|
|
1840
1841
|
severityOrder: ["s1", "s2", "s3", "s4"],
|
|
1841
1842
|
unknownPriorityRank: 2,
|
|
1842
1843
|
coordinatingValue: "coordinating",
|
|
1844
|
+
manualWorkMode: "manual",
|
|
1843
1845
|
repoExemptReportTypes: ["coordinating", "question", "investigation"]
|
|
1844
1846
|
};
|
|
1845
1847
|
function closedStatuses(c) {
|
|
@@ -1860,6 +1862,7 @@ function resolveContract(override) {
|
|
|
1860
1862
|
severityOrder: override.severityOrder ?? DEFAULT_CONTRACT.severityOrder,
|
|
1861
1863
|
unknownPriorityRank: override.unknownPriorityRank ?? DEFAULT_CONTRACT.unknownPriorityRank,
|
|
1862
1864
|
coordinatingValue: override.coordinatingValue ?? DEFAULT_CONTRACT.coordinatingValue,
|
|
1865
|
+
manualWorkMode: override.manualWorkMode ?? DEFAULT_CONTRACT.manualWorkMode,
|
|
1863
1866
|
// A workspace overriding `coordinatingValue` alone (renaming the rollup
|
|
1864
1867
|
// marker) still gets that renamed value exempted here by default,
|
|
1865
1868
|
// without also having to repeat it — only an explicit
|
|
@@ -3239,6 +3242,17 @@ var SHELL_WORDS = /* @__PURE__ */ new Set([
|
|
|
3239
3242
|
"ok"
|
|
3240
3243
|
]);
|
|
3241
3244
|
|
|
3245
|
+
// src/work-mode.ts
|
|
3246
|
+
function manualProjectIds(rows, contract) {
|
|
3247
|
+
const out = /* @__PURE__ */ new Set();
|
|
3248
|
+
for (const r of rows) if (r[contract.columns.workMode] === contract.manualWorkMode) out.add(r.id);
|
|
3249
|
+
return out;
|
|
3250
|
+
}
|
|
3251
|
+
function withinWorkMode(tickets, me, manual) {
|
|
3252
|
+
if (!manual || manual.size === 0) return tickets;
|
|
3253
|
+
return tickets.filter((t) => !t.project_id || !manual.has(t.project_id) || !!me && t.assignee_id === me);
|
|
3254
|
+
}
|
|
3255
|
+
|
|
3242
3256
|
// src/board-lock.ts
|
|
3243
3257
|
import { StaleWriteError } from "@tablation/client";
|
|
3244
3258
|
function releaseLockScope(route, repo) {
|
|
@@ -3460,6 +3474,22 @@ var Tracker = class {
|
|
|
3460
3474
|
return void 0;
|
|
3461
3475
|
}
|
|
3462
3476
|
}
|
|
3477
|
+
/**
|
|
3478
|
+
* Ids of the Projects (area) rows in `manual` work mode (CREW-1445). One
|
|
3479
|
+
* extra read per cycle. A workspace with no Projects table, no `work_mode`
|
|
3480
|
+
* column, a null value or an unreadable table yields the empty set: every
|
|
3481
|
+
* project stays automatic, exactly as before the column existed.
|
|
3482
|
+
*/
|
|
3483
|
+
async manualProjectIds() {
|
|
3484
|
+
const model = this.route.resolved?.areaModelId;
|
|
3485
|
+
if (!model) return /* @__PURE__ */ new Set();
|
|
3486
|
+
try {
|
|
3487
|
+
const rows = await this.client.records.list(model, { limit: 200 });
|
|
3488
|
+
return manualProjectIds(rows, this.contract);
|
|
3489
|
+
} catch {
|
|
3490
|
+
return /* @__PURE__ */ new Set();
|
|
3491
|
+
}
|
|
3492
|
+
}
|
|
3463
3493
|
/** Every `Projects` (area) row — what `crew repos add` offers when a new Repos row needs one. */
|
|
3464
3494
|
async projectRows() {
|
|
3465
3495
|
const model = this.route.resolved?.areaModelId;
|
|
@@ -4154,7 +4184,7 @@ function notHeldByOtherShip(slice, i) {
|
|
|
4154
4184
|
function buildingRoleHasWork(role, i) {
|
|
4155
4185
|
const me = i.seats[role];
|
|
4156
4186
|
if (!me) return { hasWork: false, reason: "this ship does not crew that role" };
|
|
4157
|
-
const mine = notInStoppedRepo(notHeldByOtherShip(sliceFor(i.tickets, role), i), i);
|
|
4187
|
+
const mine = withinWorkMode(notInStoppedRepo(notHeldByOtherShip(sliceFor(i.tickets, role), i), i), me, i.manualProjects);
|
|
4158
4188
|
const startable = mine.filter(
|
|
4159
4189
|
(t) => (t.status === "accepted" || t.status === "blocked") && !i.blocked.has(t.id) && !isHeld(t, i.holds)
|
|
4160
4190
|
);
|
|
@@ -4204,7 +4234,7 @@ function roleCandidates(role, i) {
|
|
|
4204
4234
|
if (role === "pair") return [];
|
|
4205
4235
|
const me = i.seats[role];
|
|
4206
4236
|
if (!me) return [];
|
|
4207
|
-
return notInStoppedRepo(notHeldByOtherShip(sliceFor(i.tickets, role), i), i).filter(
|
|
4237
|
+
return withinWorkMode(notInStoppedRepo(notHeldByOtherShip(sliceFor(i.tickets, role), i), i), me, i.manualProjects).filter(
|
|
4208
4238
|
(t) => ((t.status === "accepted" || t.status === "blocked") && !i.blocked.has(t.id) || t.status === "in_progress" && (t.assignee_id === me || !t.assignee_id)) && !isHeld(t, i.holds)
|
|
4209
4239
|
);
|
|
4210
4240
|
}
|
|
@@ -4499,6 +4529,7 @@ ${sorted(held).map((t) => `| ${displayKey2(t)} | ${stopOf(t)} |`).join("\n")}
|
|
|
4499
4529
|
"\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",
|
|
4500
4530
|
'\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',
|
|
4501
4531
|
"\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",
|
|
4532
|
+
...i.manualProjects && i.manualProjects.size > 0 ? ["\n**Manual projects.** Some projects on this board are in manual work mode: their tickets appear below only when a person has assigned them to YOU. An unassigned `accepted` ticket in such a project is not yours to claim and is deliberately absent \u2014 do not go looking for it.\n"] : [],
|
|
4502
4533
|
"\n### Step 1 \u2014 open tickets that may be yours to act on\n",
|
|
4503
4534
|
"\n`fixed` tickets are deliberately absent: they belong to the QA lane.\n",
|
|
4504
4535
|
table(step1, i, header, row),
|
|
@@ -4686,12 +4717,13 @@ async function decideCycle(o) {
|
|
|
4686
4717
|
const { route, ship, state, emit } = o;
|
|
4687
4718
|
const tracker = new Tracker(route, ship);
|
|
4688
4719
|
emit.enter("poll");
|
|
4689
|
-
const [tickets, comments, crewRows, epicRows, ships] = await Promise.all([
|
|
4720
|
+
const [tickets, comments, crewRows, epicRows, ships, manualProjects] = await Promise.all([
|
|
4690
4721
|
tracker.openTickets(),
|
|
4691
4722
|
tracker.comments(200),
|
|
4692
4723
|
tracker.crewRows(),
|
|
4693
4724
|
tracker.epicRows(),
|
|
4694
|
-
tracker.shipRows()
|
|
4725
|
+
tracker.shipRows(),
|
|
4726
|
+
tracker.manualProjectIds()
|
|
4695
4727
|
]);
|
|
4696
4728
|
emit.emit(`${tickets.length} open ticket(s), ${comments.length} comment(s)`, {
|
|
4697
4729
|
data: { tickets: tickets.length, comments: comments.length }
|
|
@@ -4799,6 +4831,7 @@ async function decideCycle(o) {
|
|
|
4799
4831
|
await checkRepoDisks({ state, emit, route, ship });
|
|
4800
4832
|
const selectionInput = {
|
|
4801
4833
|
repoStop: repoStopFor(openShipAttention(state), route),
|
|
4834
|
+
manualProjects,
|
|
4802
4835
|
tickets,
|
|
4803
4836
|
comments,
|
|
4804
4837
|
watermark,
|
|
@@ -4893,7 +4926,8 @@ function writeDigest(o, d, role, stateDir) {
|
|
|
4893
4926
|
}
|
|
4894
4927
|
return existingBranchForTicket(dir, repoFor(dir), t, role);
|
|
4895
4928
|
},
|
|
4896
|
-
tickets: role === "triage" ? d.tickets.filter((t) => t.assignee_id === o.route.resolved?.seats.triage) : sliceFor(d.tickets, role),
|
|
4929
|
+
tickets: role === "triage" ? d.tickets.filter((t) => t.assignee_id === o.route.resolved?.seats.triage) : role === "dev" || role === "design" ? withinWorkMode(sliceFor(d.tickets, role), me, d.selectionInput.manualProjects) : sliceFor(d.tickets, role),
|
|
4930
|
+
manualProjects: d.selectionInput.manualProjects,
|
|
4897
4931
|
comments: d.comments,
|
|
4898
4932
|
me,
|
|
4899
4933
|
roster: d.roster,
|
package/package.json
CHANGED
|
@@ -305,6 +305,11 @@ Never start work on a ticket without first setting `assignee_id` to your
|
|
|
305
305
|
own id (Step 3.3 already does this) — that claim is itself part of what
|
|
306
306
|
keeps a concurrent interactive session from re-entering the same ticket.
|
|
307
307
|
|
|
308
|
+
A project in **manual work mode** (its Projects row says so) is never consumed
|
|
309
|
+
automatically: the digest lists its tickets only when a person has assigned them to
|
|
310
|
+
your seat, and status alone never makes one yours. The digest says so when it
|
|
311
|
+
applies; do not go looking for the missing tickets. QA is unaffected.
|
|
312
|
+
|
|
308
313
|
Never touch a ticket still at `new` — only `accepted` tickets are yours to
|
|
309
314
|
pick up; triage (a separate process) is what promotes `new` → `accepted`/
|
|
310
315
|
`needs_info`.
|