@tablation/crew 0.22.0 → 0.23.0

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 +55 -8
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -3273,6 +3273,26 @@ function isAutomatic(t, wm) {
3273
3273
  if (!wm || wm.modes.size === 0) return true;
3274
3274
  return modeOf(t, wm) === "automatic";
3275
3275
  }
3276
+ function skippedByMode(tickets, wm) {
3277
+ const out = { manual: 0, hybrid: 0 };
3278
+ if (!wm || wm.modes.size === 0) return out;
3279
+ const kept = new Set(withinWorkMode(tickets, wm));
3280
+ for (const t of tickets) {
3281
+ if (kept.has(t)) continue;
3282
+ out[modeOf(t, wm) === "manual" ? "manual" : "hybrid"]++;
3283
+ }
3284
+ return out;
3285
+ }
3286
+ function withoutManualVerified(tickets, verifiedStatus, wm) {
3287
+ if (!wm || wm.modes.size === 0) return tickets;
3288
+ return tickets.filter((t) => t.status !== verifiedStatus || modeOf(t, wm) !== "manual");
3289
+ }
3290
+ function projectModeLabels(rows) {
3291
+ return rows.map((r) => {
3292
+ const name = r.name ?? r.id;
3293
+ return { id: r.id, name, mode: r.mode, label: `${name} (${r.mode})` };
3294
+ });
3295
+ }
3276
3296
 
3277
3297
  // src/board-lock.ts
3278
3298
  import { StaleWriteError } from "@tablation/client";
@@ -3517,6 +3537,12 @@ var Tracker = class {
3517
3537
  if (!model) return [];
3518
3538
  return this.client.records.list(model, { limit: 200 });
3519
3539
  }
3540
+ /** Every Projects row with its raw `work_mode`, for `crew status`. */
3541
+ async projectRowsWithModes() {
3542
+ const rows = await this.projectRows();
3543
+ const modes = await this.projectWorkModes();
3544
+ return rows.map((r) => ({ ...r, mode: modes.get(r.id) ?? "automatic" }));
3545
+ }
3520
3546
  /** Create a `Repos` row. Throws when the workspace has no Repos table. */
3521
3547
  async createRepoRow(fields) {
3522
3548
  const model = this.route.resolved?.reposModelId;
@@ -3929,9 +3955,9 @@ function sweepDiagnostics(tickets, info) {
3929
3955
  return { dangling, selfBlocked: [...new Set(selfBlocked)] };
3930
3956
  }
3931
3957
  var describeBlockers = (t, info) => (t.blocked_by ?? []).map((id) => `${info[id]?.issue_id ?? "?"} (${info[id]?.status ?? "unknown"})`).join(", ");
3932
- function planSweep(tickets, info, blocked) {
3958
+ function planSweep(tickets, info, blocked, wm) {
3933
3959
  const steps = [];
3934
- for (const t of tickets) {
3960
+ for (const t of withinWorkMode(tickets, wm)) {
3935
3961
  if (t.status === "accepted" && blocked.has(t.id)) {
3936
3962
  steps.push({ action: "park", ticket: t, blockers: describeBlockers(t, info), to: "blocked" });
3937
3963
  } else if (t.status === "blocked" && !blocked.has(t.id)) {
@@ -4652,7 +4678,7 @@ var ms = (iso) => {
4652
4678
  const n = iso ? Date.parse(iso) : NaN;
4653
4679
  return Number.isFinite(n) ? n : 0;
4654
4680
  };
4655
- function planStalled(tickets, comments, ships, contract, holds2 = /* @__PURE__ */ new Set(), now = Date.now()) {
4681
+ function planStalled(tickets, comments, ships, contract, holds2 = /* @__PURE__ */ new Set(), now = Date.now(), wm) {
4656
4682
  const watched = /* @__PURE__ */ new Set([contract.statuses.building, contract.statuses.handoff, contract.statuses.verifying]);
4657
4683
  const byTicket = /* @__PURE__ */ new Map();
4658
4684
  for (const c of comments) {
@@ -4661,7 +4687,7 @@ function planStalled(tickets, comments, ships, contract, holds2 = /* @__PURE__ *
4661
4687
  else byTicket.set(c.ticket_id, [c]);
4662
4688
  }
4663
4689
  const out = [];
4664
- for (const t of tickets) {
4690
+ for (const t of withinWorkMode(tickets, wm)) {
4665
4691
  if (!watched.has(t.status)) continue;
4666
4692
  if (t.assignee_id && holds2.has(t.assignee_id)) continue;
4667
4693
  const mine = byTicket.get(t.id) ?? [];
@@ -4785,9 +4811,15 @@ async function decideCycle(o) {
4785
4811
  if (diag.selfBlocked.length) {
4786
4812
  emit.warn(`ticket(s) blocking themselves, parked permanently: ${diag.selfBlocked.join(", ")}`);
4787
4813
  }
4788
- const sweep = planSweep(tickets, info, blocked);
4814
+ const sweep = planSweep(tickets, info, blocked, workModes);
4789
4815
  const holdReleases = planHoldReleases(tickets, tracker.contract);
4790
- const stalled = planStalled(tickets, comments, ships, tracker.contract, holds2);
4816
+ const stalled = planStalled(tickets, comments, ships, tracker.contract, holds2, Date.now(), workModes);
4817
+ const skipped = skippedByMode(tickets, workModes);
4818
+ if (skipped.manual || skipped.hybrid) {
4819
+ emit.emit(`sweeps skipped ${skipped.manual} manual/project-less and ${skipped.hybrid} unassigned hybrid ticket(s)`, {
4820
+ data: { sweepSkipped: skipped }
4821
+ });
4822
+ }
4791
4823
  const blockedIssues = tickets.filter((t) => blocked.has(t.id)).map((t) => t.issue_id);
4792
4824
  emit.emit(`${blockedIssues.length} ticket(s) parked as blocked`, { data: { blocked: blockedIssues } });
4793
4825
  const qaHeld = tickets.filter((t) => t.status === tracker.contract.statuses.verifying).map((t) => t.issue_id);
@@ -11034,7 +11066,12 @@ ${changed2.map((c) => ` - ${c}
11034
11066
  let seen = precheckTickets.get(c.route);
11035
11067
  if (!seen) precheckTickets.set(c.route, seen = tracker.openTickets());
11036
11068
  const { byRepo: preByRepo, unplaceable: preUnplaceable } = ticketsByRepo(c, await seen);
11037
- const pre = preByRepo.get(target.name) ?? [];
11069
+ const preModes = { modes: await tracker.projectWorkModes(), agentAssignees: /* @__PURE__ */ new Set() };
11070
+ const pre = withoutManualVerified(
11071
+ preByRepo.get(target.name) ?? [],
11072
+ tracker.contract.statuses.verified,
11073
+ preModes
11074
+ );
11038
11075
  reportUnplaceable(c.route, preUnplaceable, tracker.contract.statuses.verified, remit);
11039
11076
  const d = decideRelease(target.dir, pre, tracker.contract, {
11040
11077
  tagPattern: repo.release.tagPattern ?? "v*",
@@ -11082,7 +11119,12 @@ ${changed2.map((c) => ` - ${c}
11082
11119
  }
11083
11120
  const all = await tracker.openTickets();
11084
11121
  const { byRepo, unplaceable } = ticketsByRepo(c, all);
11085
- const tickets = byRepo.get(target.name) ?? [];
11122
+ const releaseModes = { modes: await tracker.projectWorkModes(), agentAssignees: /* @__PURE__ */ new Set() };
11123
+ const tickets = withoutManualVerified(
11124
+ byRepo.get(target.name) ?? [],
11125
+ tracker.contract.statuses.verified,
11126
+ releaseModes
11127
+ );
11086
11128
  reportUnplaceable(c.route, unplaceable, tracker.contract.statuses.verified, remit);
11087
11129
  const outcome = await runRelease({
11088
11130
  cwd: target.dir,
@@ -12967,6 +13009,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
12967
13009
  const engagedTickets = engagedTicketIds.length ? await statusTracker.ticketsByIds(engagedTicketIds) : [];
12968
13010
  const issueLabelById = new Map(engagedTickets.map((t) => [t.id, t.issue_id]));
12969
13011
  const todo = await statusTracker.openTickets().then((open) => operatorTodo(open, statusTracker.contract, route.resolved?.operator ?? "")).catch(() => []);
13012
+ const projects = projectModeLabels(await statusTracker.projectRowsWithModes().catch(() => []));
12970
13013
  if (flag("json")) {
12971
13014
  const repos = await resolvedRepos(route);
12972
13015
  const seatIds = route.resolved?.seats ?? {};
@@ -13016,6 +13059,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
13016
13059
  watermark: state.watermark(),
13017
13060
  route: {
13018
13061
  name: route.route,
13062
+ // Additive (CREW-1496): each project and its work mode. `version` stays 1.
13063
+ projects: projects.map((p) => ({ id: p.id, name: p.name, workMode: p.mode })),
13019
13064
  dir: route.dir,
13020
13065
  enabled: route.enabled,
13021
13066
  area: route.area ?? null,
@@ -13073,6 +13118,8 @@ watermark: ${state.watermark()}
13073
13118
  if (state.isRolePaused(r)) process.stdout.write(`role ${r}: paused
13074
13119
  `);
13075
13120
  }
13121
+ if (projects.length) process.stdout.write(`projects: ${projects.map((p) => p.label).join(", ")}
13122
+ `);
13076
13123
  for (const i of openShipAttention(state)) {
13077
13124
  process.stdout.write(`attention: ${i.kind} \u2014 ${i.message}
13078
13125
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tablation/crew",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
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",