@clipboard-health/groundcrew 4.18.0 → 4.18.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.
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAIA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAanE,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AA6mBD,wBAAsB,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAU/F;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7D"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAIA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAcnE,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAoqBD,wBAAsB,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAU/F;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7D"}
@@ -307,7 +307,16 @@ function inventoryField(label, value) {
307
307
  function formatPullRequests(prs) {
308
308
  return prs.map((pr) => `${pr.url} (${pr.state})`).join(", ");
309
309
  }
310
- async function writeInventoryWorktrees(config, probe) {
310
+ /**
311
+ * Inventory `ticket:` value: the worktree's remote canonical status. Slots are
312
+ * consumed solely by `in-progress` issues (see `inProgressCount`), so that one
313
+ * status is spelled out as `slot held` to make the otherwise-implicit rule
314
+ * legible on the row; every other status renders bare.
315
+ */
316
+ function formatTicketStatus(canonicalStatus) {
317
+ return canonicalStatus === "in-progress" ? "in-progress (slot held)" : canonicalStatus;
318
+ }
319
+ async function writeInventoryWorktrees(config, probe, statusByTicket) {
311
320
  writeSection("Worktrees");
312
321
  const entries = worktrees
313
322
  .list(config)
@@ -338,6 +347,14 @@ async function writeInventoryWorktrees(config, probe) {
338
347
  writeOutput(inventoryField("title", runState.title));
339
348
  }
340
349
  writeOutput(inventoryField("state", inventoryStateText(runState, probe, entry.ticket, now)));
350
+ // `state:` is the local run lifecycle; `ticket:` is the remote status that
351
+ // actually drives the slot count. They're sourced independently and can
352
+ // legitimately disagree, so they sit adjacent. Omitted when the board fetch
353
+ // failed (no map) or the ticket isn't in the fetched board.
354
+ const ticketStatus = statusByTicket?.get(entry.ticket);
355
+ if (ticketStatus !== undefined) {
356
+ writeOutput(inventoryField("ticket", formatTicketStatus(ticketStatus)));
357
+ }
341
358
  writeOutput(inventoryField("repo", entry.repository));
342
359
  writeOutput(inventoryField("worktree", entry.dir));
343
360
  if (accessHint !== undefined) {
@@ -432,6 +449,32 @@ async function fetchBoardForStatus(config) {
432
449
  return { kind: "error", error };
433
450
  }
434
451
  }
452
+ /**
453
+ * Maps each fetched issue's lowercased natural id to its canonical status when
454
+ * exactly one fetched issue has that natural id, so the Worktrees section can
455
+ * show a `ticket:` field per row without guessing across sources. The key
456
+ * matches the lowercased `WorktreeEntry.ticket` (same join as
457
+ * `inProgressWithoutWorktree`). `undefined` when the board fetch failed —
458
+ * callers then omit the field rather than guess.
459
+ */
460
+ function statusByWorktreeTicket(boardResult) {
461
+ if (boardResult.kind !== "ok") {
462
+ return undefined;
463
+ }
464
+ const statuses = new Map();
465
+ const matchCounts = new Map();
466
+ for (const issue of boardResult.issues) {
467
+ const ticket = naturalIdFromCanonical(issue.id).toLowerCase();
468
+ matchCounts.set(ticket, (matchCounts.get(ticket) ?? 0) + 1);
469
+ statuses.set(ticket, issue.status);
470
+ }
471
+ for (const [ticket, matchCount] of matchCounts) {
472
+ if (matchCount > 1) {
473
+ statuses.delete(ticket);
474
+ }
475
+ }
476
+ return statuses;
477
+ }
435
478
  function writeQueueSections(boardResult) {
436
479
  if (boardResult.kind === "error") {
437
480
  writeSection("Queue");
@@ -484,6 +527,9 @@ function writeInProgressIssue(issue) {
484
527
  const naturalId = naturalIdFromCanonical(issue.id);
485
528
  writeOutput(issue.url === undefined ? naturalId : `${naturalId} ${issue.url}`);
486
529
  writeOutput(inventoryField("title", issue.title));
530
+ // These are all in-progress by definition, but spell out the slot-held
531
+ // status so every holder row reads the same whether or not it has a worktree.
532
+ writeOutput(inventoryField("ticket", formatTicketStatus(issue.status)));
487
533
  if (issue.repository !== undefined) {
488
534
  writeOutput(inventoryField("repo", issue.repository));
489
535
  }
@@ -508,12 +554,16 @@ async function writeInventoryStatus(config) {
508
554
  // Banner ("groundcrew status\n=================") dropped: the command
509
555
  // you just ran already tells you what report you're looking at, and the
510
556
  // section headers (`Worktrees`, `Queue`, etc.) carry the visual anchors.
557
+ // The board fetch runs concurrently with the probe, but we await it before
558
+ // rendering: each Worktrees row carries the remote ticket status, so the
559
+ // inventory can't print until the source resolves. A failed fetch returns
560
+ // quickly and still renders rows (without the `ticket:` field).
511
561
  const boardResultPromise = fetchBoardForStatus(config);
512
562
  const probe = await withLogOutputSuppressed(async () => await workspaces.probe(config));
513
- await writeInventoryWorktrees(config, probe);
563
+ const boardResult = await boardResultPromise;
564
+ await writeInventoryWorktrees(config, probe, statusByWorktreeTicket(boardResult));
514
565
  const worktreeTickets = new Set(worktrees.list(config).map((entry) => entry.ticket));
515
566
  writeStraySessions(probe, worktreeTickets);
516
- const boardResult = await boardResultPromise;
517
567
  writeInProgressWithoutWorktree(boardResult, worktreeTickets);
518
568
  if (boardResult.kind === "ok") {
519
569
  const used = inProgressCount(boardResult.issues);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clipboard-health/groundcrew",
3
- "version": "4.18.0",
3
+ "version": "4.18.1",
4
4
  "description": "Linear-driven orchestrator that launches AI coding agents in git worktrees, with workspace lifecycle and usage tracking.",
5
5
  "keywords": [
6
6
  "agent",