@sideboard-ai/core 0.1.298 → 0.1.299

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 (29) hide show
  1. package/dist/{agents-GHZFY65E.js → agents-IVKPCK56.js} +3 -3
  2. package/dist/{agents-XL67HSZH.js → agents-JCCJJMA7.js} +2 -2
  3. package/dist/{chunk-RQMLJZ2V.js → chunk-EB3F5AWV.js} +2 -2
  4. package/dist/{chunk-IL77XODJ.js → chunk-EB7B4D2Z.js} +3 -3
  5. package/dist/{chunk-JM5P36PM.js → chunk-F3JYJ4UB.js} +2 -2
  6. package/dist/{chunk-BIMHIHX3.js → chunk-G52HLBQR.js} +21 -1
  7. package/dist/{chunk-4FWMUMXG.js → chunk-MMIDAD6J.js} +3 -3
  8. package/dist/{chunk-KRGQWQME.js → chunk-NF3WDI7F.js} +183 -109
  9. package/dist/{chunk-KMYCNSSI.js → chunk-QB4UQA5L.js} +3 -3
  10. package/dist/{chunk-5FMQU2RU.js → chunk-UMALFEDS.js} +2 -2
  11. package/dist/{chunk-2POLTCSC.js → chunk-YAILSKW3.js} +61 -47
  12. package/dist/{conductor-NGIEDMAB.js → conductor-JLHXMMKR.js} +2 -2
  13. package/dist/{conductor-HUITLA2M.js → conductor-UQDDDW35.js} +3 -3
  14. package/dist/{coordinator-prompt-JUN3IITF.js → coordinator-prompt-HKLVS26O.js} +2 -2
  15. package/dist/{coordinator-prompt-52T5A4SU.js → coordinator-prompt-SSUYZSJO.js} +1 -1
  16. package/dist/{global-workspace-VVUIFQ72.js → global-workspace-I32EWKX3.js} +2 -2
  17. package/dist/{global-workspace-Z5YRL67Y.js → global-workspace-MHE6FCUZ.js} +1 -1
  18. package/dist/index.cjs +336 -224
  19. package/dist/index.d.cts +44 -26
  20. package/dist/index.d.ts +44 -26
  21. package/dist/index.js +11 -11
  22. package/dist/mcp/run-stdio.cjs +81 -43
  23. package/dist/mcp/run-stdio.js +7 -7
  24. package/dist/{orchestrator-S7TKI6DR.js → orchestrator-ESLNXVOX.js} +5 -5
  25. package/dist/{orchestrator-JY4ZETFX.js → orchestrator-SX4SGZHF.js} +4 -4
  26. package/dist/{workspaces-OSF56FHS.js → workspaces-ATMIKXLK.js} +2 -2
  27. package/dist/{workspaces-7FVNGUBV.js → workspaces-O3L32CDO.js} +1 -1
  28. package/dist/{worktree-LFSBIUCN.js → worktree-RMYXBTJG.js} +1 -1
  29. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -6362,12 +6362,7 @@ var init_agent_block_view = __esm({
6362
6362
  }
6363
6363
  });
6364
6364
 
6365
- // src/board/home-board.ts
6366
- function isHomeBoardThread(thread) {
6367
- if (thread.sourceType === "orchestration") return false;
6368
- if (thread.repoPath === GLOBAL_WORKSPACE_ID) return false;
6369
- return true;
6370
- }
6365
+ // src/board/pr-state.ts
6371
6366
  function isOpenPrState(prUrl, prState) {
6372
6367
  if (!prUrl?.trim()) return false;
6373
6368
  const state = (prState ?? "OPEN").trim().toUpperCase();
@@ -6377,57 +6372,17 @@ function isMergedPrState(prUrl, prState) {
6377
6372
  if (!prUrl?.trim()) return false;
6378
6373
  return (prState ?? "").trim().toUpperCase() === "MERGED";
6379
6374
  }
6380
- function groupCreatedAt(group) {
6381
- let min = group[0]?.createdAt ?? "";
6382
- for (const t of group) {
6383
- if (t.createdAt && t.createdAt < min) min = t.createdAt;
6384
- }
6385
- return min;
6386
- }
6387
- function groupUpdatedAt(group) {
6388
- let max = group[0]?.updatedAt ?? "";
6389
- for (const t of group) {
6390
- if (t.updatedAt && t.updatedAt > max) max = t.updatedAt;
6391
- }
6392
- return max;
6393
- }
6394
- function sortThreadsInWorktree(list) {
6395
- return [...list].sort((a, b) => {
6396
- const created = a.createdAt.localeCompare(b.createdAt);
6397
- if (created !== 0) return created;
6398
- return a.id.localeCompare(b.id);
6399
- });
6400
- }
6401
- function compareWorktreeGroups(a, b, mode, labelOf) {
6402
- if (mode === "name") {
6403
- const named = (labelOf?.(a) ?? a[0]?.id ?? "").localeCompare(
6404
- labelOf?.(b) ?? b[0]?.id ?? "",
6405
- void 0,
6406
- { sensitivity: "base" }
6407
- );
6408
- if (named !== 0) return named;
6409
- } else if (mode === "activity") {
6410
- const activity = groupUpdatedAt(b).localeCompare(groupUpdatedAt(a));
6411
- if (activity !== 0) return activity;
6412
- } else {
6413
- const created = groupCreatedAt(b).localeCompare(groupCreatedAt(a));
6414
- if (created !== 0) return created;
6415
- }
6416
- return (a[0]?.id ?? "").localeCompare(b[0]?.id ?? "");
6417
- }
6418
- function defaultWorktreeGroupLabel(group) {
6419
- return worktreeDisplayLabelForGroup(group);
6375
+ function isApprovedOpenPr(thread) {
6376
+ if (!isOpenPrState(thread.prUrl, thread.prState) || thread.prIsDraft) return false;
6377
+ return (thread.prReviewDecision ?? "").trim().toUpperCase() === "APPROVED";
6420
6378
  }
6421
- function groupHomeBoardWorktrees(threads, sort = DEFAULT_WORKTREE_SORT, labelOf = defaultWorktreeGroupLabel) {
6422
- const map = /* @__PURE__ */ new Map();
6423
- for (const t of threads) {
6424
- const key = normalizeWorktreePath(t.worktreePath);
6425
- const list = map.get(key) ?? [];
6426
- list.push(t);
6427
- map.set(key, list);
6379
+ var init_pr_state = __esm({
6380
+ "src/board/pr-state.ts"() {
6381
+ "use strict";
6428
6382
  }
6429
- return [...map.values()].map((list) => sortThreadsInWorktree(list)).sort((a, b) => compareWorktreeGroups(a, b, sort, labelOf));
6430
- }
6383
+ });
6384
+
6385
+ // src/board/worktree-ownership.ts
6431
6386
  function normalizeViewerLogin(login) {
6432
6387
  return (login ?? "").trim().toLowerCase();
6433
6388
  }
@@ -6457,16 +6412,165 @@ function worktreeMatchesOwnership(group, filter, viewerLogin = "") {
6457
6412
  if (filter === "all") return true;
6458
6413
  return classifyWorktreeOwnership(group, viewerLogin) === filter;
6459
6414
  }
6460
- function classifyWorktreeColumn(group) {
6461
- if (group.some((t) => isMergedPrState(t.prUrl, t.prState))) return "done";
6462
- if (group.some((t) => isOpenPrState(t.prUrl, t.prState) && !t.prIsDraft)) {
6463
- return "review";
6415
+ var DEFAULT_BOARD_OWNERSHIP;
6416
+ var init_worktree_ownership = __esm({
6417
+ "src/board/worktree-ownership.ts"() {
6418
+ "use strict";
6419
+ DEFAULT_BOARD_OWNERSHIP = "all";
6464
6420
  }
6465
- if (group.some((t) => isOpenPrState(t.prUrl, t.prState) && t.prIsDraft)) {
6466
- return "draft";
6421
+ });
6422
+
6423
+ // src/git/pr-gates.ts
6424
+ function prIsInMergeQueue(gate) {
6425
+ if (gate.isInMergeQueue) return true;
6426
+ return (gate.mergeStateStatus ?? "").toUpperCase() === "QUEUED";
6427
+ }
6428
+ function applyCleanLocalMergeTree(gate) {
6429
+ const mergeable = (gate.mergeable ?? "").toUpperCase();
6430
+ const mergeState = (gate.mergeStateStatus ?? "").toUpperCase();
6431
+ const githubConflicting = mergeable === "CONFLICTING" || mergeState === "DIRTY";
6432
+ if (githubConflicting) {
6433
+ return {
6434
+ ...gate,
6435
+ mergeable: "MERGEABLE",
6436
+ mergeStateStatus: mergeState === "BEHIND" ? "BEHIND" : "CLEAN"
6437
+ };
6467
6438
  }
6468
- return "new";
6439
+ return {
6440
+ ...gate,
6441
+ mergeable: mergeable === "UNKNOWN" || !mergeable ? "MERGEABLE" : gate.mergeable,
6442
+ mergeStateStatus: mergeState === "BEHIND" ? "BEHIND" : mergeState === "UNKNOWN" || !mergeState ? "CLEAN" : gate.mergeStateStatus
6443
+ };
6444
+ }
6445
+ function classifyPrMergeIssue(gate) {
6446
+ if (!gate) return null;
6447
+ if (prIsInMergeQueue(gate)) return null;
6448
+ const mergeable = (gate.mergeable ?? "").toUpperCase();
6449
+ const mergeState = (gate.mergeStateStatus ?? "").toUpperCase();
6450
+ if (mergeable === "CONFLICTING" || mergeState === "DIRTY") return "conflicts";
6451
+ if (mergeState === "BEHIND") return "behind";
6452
+ return null;
6469
6453
  }
6454
+ function mergeStateSuggestsCheckFailure(gate) {
6455
+ if (!gate) return false;
6456
+ const mergeState = (gate.mergeStateStatus ?? "").toUpperCase();
6457
+ if (mergeState === "UNSTABLE") return true;
6458
+ if (mergeState === "BLOCKED") {
6459
+ return (gate.reviewDecision ?? "").toUpperCase() === "APPROVED";
6460
+ }
6461
+ return false;
6462
+ }
6463
+ function ciChecksFailed(checks) {
6464
+ if (!checks?.length) return false;
6465
+ return checks.some(
6466
+ (check) => (!check.kind || check.kind === "ci") && check.bucket === "fail"
6467
+ );
6468
+ }
6469
+ function buildMergeGateChecks(gate, opts = {}) {
6470
+ const rows = [];
6471
+ const base = gate.baseRefName?.trim() || "the base branch";
6472
+ const link = gate.url?.trim() || null;
6473
+ const mergeable = (gate.mergeable ?? "").toUpperCase();
6474
+ const mergeState = (gate.mergeStateStatus ?? "").toUpperCase();
6475
+ const review = (gate.reviewDecision ?? "").toUpperCase();
6476
+ const suppressBlocked = opts.suppressGenericBlocked !== false;
6477
+ const conflicting = mergeable === "CONFLICTING" || mergeState === "DIRTY";
6478
+ const inQueue = prIsInMergeQueue(gate);
6479
+ if (conflicting && !inQueue) {
6480
+ rows.push({
6481
+ name: "Merge conflicts",
6482
+ state: mergeable === "CONFLICTING" ? "CONFLICTING" : mergeState || "DIRTY",
6483
+ bucket: "fail",
6484
+ startedAt: null,
6485
+ completedAt: null,
6486
+ link,
6487
+ description: `This branch has conflicts that must be resolved before it can merge into ${base}.`,
6488
+ workflow: "mergeability",
6489
+ kind: "mergeability"
6490
+ });
6491
+ } else if (inQueue) {
6492
+ rows.push({
6493
+ name: "Merge queue",
6494
+ state: "QUEUED",
6495
+ bucket: "pending",
6496
+ startedAt: null,
6497
+ completedAt: null,
6498
+ link,
6499
+ description: `This pull request is in the GitHub merge queue for ${base}.`,
6500
+ workflow: "mergeability",
6501
+ kind: "mergeability"
6502
+ });
6503
+ } else if (mergeState === "BEHIND") {
6504
+ rows.push({
6505
+ name: "Branch behind",
6506
+ state: "BEHIND",
6507
+ bucket: "info",
6508
+ startedAt: null,
6509
+ completedAt: null,
6510
+ link,
6511
+ description: `Head is behind ${base}. Update this branch (merge or rebase) before merging.`,
6512
+ workflow: "mergeability",
6513
+ kind: "mergeability"
6514
+ });
6515
+ } else if (mergeState === "BLOCKED" && !suppressBlocked) {
6516
+ rows.push({
6517
+ name: "Merge blocked",
6518
+ state: "BLOCKED",
6519
+ bucket: "fail",
6520
+ startedAt: null,
6521
+ completedAt: null,
6522
+ link,
6523
+ description: "GitHub reports this PR as blocked (branch protection \u2014 required reviews and/or status checks).",
6524
+ workflow: "mergeability",
6525
+ kind: "mergeability"
6526
+ });
6527
+ } else if (mergeable === "UNKNOWN" || mergeState === "UNKNOWN") {
6528
+ rows.push({
6529
+ name: "Mergeability",
6530
+ state: "UNKNOWN",
6531
+ bucket: "pending",
6532
+ startedAt: null,
6533
+ completedAt: null,
6534
+ link,
6535
+ description: "GitHub is still computing whether this branch can merge.",
6536
+ workflow: "mergeability",
6537
+ kind: "mergeability"
6538
+ });
6539
+ }
6540
+ if (review === "CHANGES_REQUESTED") {
6541
+ rows.push({
6542
+ name: "Code review",
6543
+ state: "CHANGES_REQUESTED",
6544
+ bucket: "fail",
6545
+ startedAt: null,
6546
+ completedAt: null,
6547
+ link,
6548
+ description: "A reviewer requested changes. Address their feedback before merging.",
6549
+ workflow: "review",
6550
+ kind: "review"
6551
+ });
6552
+ } else if (review === "REVIEW_REQUIRED") {
6553
+ rows.push({
6554
+ name: "Code review",
6555
+ state: "REVIEW_REQUIRED",
6556
+ bucket: "pending",
6557
+ startedAt: null,
6558
+ completedAt: null,
6559
+ link,
6560
+ description: "A required approving review is still outstanding.",
6561
+ workflow: "review",
6562
+ kind: "review"
6563
+ });
6564
+ }
6565
+ return rows;
6566
+ }
6567
+ var init_pr_gates = __esm({
6568
+ "src/git/pr-gates.ts"() {
6569
+ "use strict";
6570
+ }
6571
+ });
6572
+
6573
+ // src/board/worktree-list-badges.ts
6470
6574
  function labelsInclude(labels, name) {
6471
6575
  const want = name.trim().toLowerCase();
6472
6576
  if (!want) return false;
@@ -6505,34 +6609,167 @@ function groupPrLabels(group) {
6505
6609
  }
6506
6610
  return out;
6507
6611
  }
6612
+ function isClosedPrState(prUrl, prState) {
6613
+ if (!prUrl?.trim()) return false;
6614
+ return (prState ?? "").trim().toUpperCase() === "CLOSED";
6615
+ }
6616
+ function listBadge(id, title, conflict = false) {
6617
+ return {
6618
+ id,
6619
+ label: id,
6620
+ title: conflict ? `${title} \xB7 merge conflicts` : title,
6621
+ mod: `is-${id}`,
6622
+ ...conflict ? { conflict: true } : {}
6623
+ };
6624
+ }
6625
+ function hasMergeConflict(thread) {
6626
+ if (!isOpenPrState(thread.prUrl, thread.prState)) return false;
6627
+ return classifyPrMergeIssue({
6628
+ mergeable: thread.prMergeable ?? null,
6629
+ mergeStateStatus: thread.prMergeStateStatus ?? null,
6630
+ isInMergeQueue: thread.prIsInMergeQueue
6631
+ }) === "conflicts";
6632
+ }
6633
+ function groupPrError(group) {
6634
+ const open = group.filter(
6635
+ (thread) => isOpenPrState(thread.prUrl, thread.prState) && !thread.prIsInMergeQueue
6636
+ );
6637
+ if (open.some(
6638
+ (thread) => thread.prChecksFailed || mergeStateSuggestsCheckFailure({
6639
+ mergeStateStatus: thread.prMergeStateStatus ?? null,
6640
+ reviewDecision: thread.prReviewDecision ?? null
6641
+ })
6642
+ )) {
6643
+ return { label: "failing", title: "Checks failing" };
6644
+ }
6645
+ if (open.some(
6646
+ (thread) => !thread.prIsDraft && (thread.prReviewDecision ?? "").trim().toUpperCase() === "CHANGES_REQUESTED"
6647
+ )) {
6648
+ return { label: "changes", title: "Changes requested" };
6649
+ }
6650
+ return void 0;
6651
+ }
6652
+ function withPrError(badges, error, conflict) {
6653
+ if (!error) return badges;
6654
+ const [first] = badges;
6655
+ if (!first) {
6656
+ return [
6657
+ {
6658
+ id: "error",
6659
+ label: error.label,
6660
+ title: conflict ? `${error.title} \xB7 merge conflicts` : error.title,
6661
+ mod: "is-error",
6662
+ ...conflict ? { conflict: true } : {}
6663
+ }
6664
+ ];
6665
+ }
6666
+ return [{ ...first, error }];
6667
+ }
6508
6668
  function classifyWorktreeListBadges(group, viewerLogin = "", reviewLabel = "") {
6509
6669
  if (group.length === 0 || group.some((t) => t.cowboy)) return [];
6670
+ if (group.some((t) => isMergedPrState(t.prUrl, t.prState))) {
6671
+ return [listBadge("merged", "Merged")];
6672
+ }
6673
+ const conflict = group.some(hasMergeConflict);
6674
+ const error = groupPrError(group);
6675
+ const finish2 = (badges) => withPrError(badges, error, conflict);
6676
+ if (group.some((t) => isApprovedOpenPr(t))) {
6677
+ return finish2([listBadge("approved", "Approved, not merged", conflict)]);
6678
+ }
6510
6679
  const open = group.filter((t) => isOpenPrState(t.prUrl, t.prState));
6511
- if (open.length === 0) return [];
6680
+ if (open.length === 0) {
6681
+ if (group.some((t) => isClosedPrState(t.prUrl, t.prState))) return [];
6682
+ return finish2([listBadge("new", "No pull request")]);
6683
+ }
6512
6684
  const ownership = classifyWorktreeOwnership(group, viewerLogin);
6513
6685
  if (ownership === "reviewing") {
6514
- return [
6515
- {
6516
- id: "review",
6517
- label: "review",
6518
- title: "Someone else's PR",
6519
- mod: "is-review"
6520
- }
6521
- ];
6686
+ return finish2([listBadge("review", "Someone else's PR", conflict)]);
6687
+ }
6688
+ if (!normalizeViewerLogin(viewerLogin)) return finish2([]);
6689
+ if (!open.some((t) => !t.prIsDraft)) {
6690
+ return finish2([listBadge("draft", "Draft pull request", conflict)]);
6522
6691
  }
6523
- if (!normalizeViewerLogin(viewerLogin)) return [];
6524
- const draft = open.some((t) => t.prIsDraft);
6525
6692
  const readyLabel = reviewLabel.trim();
6526
- const inReview = readyLabel ? labelsInclude(groupPrLabels(group), readyLabel) : !draft;
6527
- if (!inReview) return [];
6528
- return [
6529
- {
6530
- id: "pending",
6531
- label: "pending",
6532
- title: "Waiting for review",
6533
- mod: "is-pending"
6534
- }
6535
- ];
6693
+ const inReview = readyLabel ? labelsInclude(groupPrLabels(group), readyLabel) : true;
6694
+ if (!inReview) return finish2([]);
6695
+ return finish2([listBadge("pending", "Waiting for review", conflict)]);
6696
+ }
6697
+ var init_worktree_list_badges = __esm({
6698
+ "src/board/worktree-list-badges.ts"() {
6699
+ "use strict";
6700
+ init_pr_gates();
6701
+ init_pr_state();
6702
+ init_worktree_ownership();
6703
+ }
6704
+ });
6705
+
6706
+ // src/board/home-board.ts
6707
+ function isHomeBoardThread(thread) {
6708
+ if (thread.sourceType === "orchestration") return false;
6709
+ if (thread.repoPath === GLOBAL_WORKSPACE_ID) return false;
6710
+ return true;
6711
+ }
6712
+ function groupCreatedAt(group) {
6713
+ let min = group[0]?.createdAt ?? "";
6714
+ for (const t of group) {
6715
+ if (t.createdAt && t.createdAt < min) min = t.createdAt;
6716
+ }
6717
+ return min;
6718
+ }
6719
+ function groupUpdatedAt(group) {
6720
+ let max = group[0]?.updatedAt ?? "";
6721
+ for (const t of group) {
6722
+ if (t.updatedAt && t.updatedAt > max) max = t.updatedAt;
6723
+ }
6724
+ return max;
6725
+ }
6726
+ function sortThreadsInWorktree(list) {
6727
+ return [...list].sort((a, b) => {
6728
+ const created = a.createdAt.localeCompare(b.createdAt);
6729
+ if (created !== 0) return created;
6730
+ return a.id.localeCompare(b.id);
6731
+ });
6732
+ }
6733
+ function compareWorktreeGroups(a, b, mode, labelOf) {
6734
+ if (mode === "name") {
6735
+ const named = (labelOf?.(a) ?? a[0]?.id ?? "").localeCompare(
6736
+ labelOf?.(b) ?? b[0]?.id ?? "",
6737
+ void 0,
6738
+ { sensitivity: "base" }
6739
+ );
6740
+ if (named !== 0) return named;
6741
+ } else if (mode === "activity") {
6742
+ const activity = groupUpdatedAt(b).localeCompare(groupUpdatedAt(a));
6743
+ if (activity !== 0) return activity;
6744
+ } else {
6745
+ const created = groupCreatedAt(b).localeCompare(groupCreatedAt(a));
6746
+ if (created !== 0) return created;
6747
+ }
6748
+ return (a[0]?.id ?? "").localeCompare(b[0]?.id ?? "");
6749
+ }
6750
+ function defaultWorktreeGroupLabel(group) {
6751
+ return worktreeDisplayLabelForGroup(group);
6752
+ }
6753
+ function groupHomeBoardWorktrees(threads, sort = DEFAULT_WORKTREE_SORT, labelOf = defaultWorktreeGroupLabel) {
6754
+ const map = /* @__PURE__ */ new Map();
6755
+ for (const t of threads) {
6756
+ const key = normalizeWorktreePath(t.worktreePath);
6757
+ const list = map.get(key) ?? [];
6758
+ list.push(t);
6759
+ map.set(key, list);
6760
+ }
6761
+ return [...map.values()].map((list) => sortThreadsInWorktree(list)).sort((a, b) => compareWorktreeGroups(a, b, sort, labelOf));
6762
+ }
6763
+ function classifyWorktreeColumn(group) {
6764
+ if (group.some((t) => isMergedPrState(t.prUrl, t.prState))) return "done";
6765
+ if (group.some((t) => isApprovedOpenPr(t))) return "approved";
6766
+ if (group.some((t) => isOpenPrState(t.prUrl, t.prState) && !t.prIsDraft)) {
6767
+ return "review";
6768
+ }
6769
+ if (group.some((t) => isOpenPrState(t.prUrl, t.prState) && t.prIsDraft)) {
6770
+ return "draft";
6771
+ }
6772
+ return "new";
6536
6773
  }
6537
6774
  function normalizeIssueKey(value) {
6538
6775
  return value.trim().toLowerCase().replace(/^#/, "");
@@ -6840,6 +7077,7 @@ function emptyColumns() {
6840
7077
  new: [],
6841
7078
  draft: [],
6842
7079
  review: [],
7080
+ approved: [],
6843
7081
  done: []
6844
7082
  };
6845
7083
  }
@@ -6851,6 +7089,7 @@ function emptyHidden() {
6851
7089
  new: 0,
6852
7090
  draft: 0,
6853
7091
  review: 0,
7092
+ approved: 0,
6854
7093
  done: 0
6855
7094
  };
6856
7095
  }
@@ -6919,6 +7158,7 @@ function assembleHomeBoard(input) {
6919
7158
  new: 0,
6920
7159
  draft: 0,
6921
7160
  review: 0,
7161
+ approved: 0,
6922
7162
  done: 0,
6923
7163
  tickets: 0,
6924
7164
  prs: 0,
@@ -6931,6 +7171,7 @@ function assembleHomeBoard(input) {
6931
7171
  new: [],
6932
7172
  draft: [],
6933
7173
  review: [],
7174
+ approved: [],
6934
7175
  done: []
6935
7176
  };
6936
7177
  for (const group of groupHomeBoardWorktrees(live)) {
@@ -6965,27 +7206,30 @@ function assembleHomeBoard(input) {
6965
7206
  }
6966
7207
  return { columns, hidden, totals };
6967
7208
  }
6968
- var GLOBAL_WORKSPACE_ID, BOARD_COLUMN_DEFS, HOME_BOARD_CACHE_TTL_MS, DEFAULT_WORKTREE_SORT, DEFAULT_BOARD_OWNERSHIP, DEFAULTISH_BRANCH, BOARD_PAGE_SIZE, HOME_BOARD_AGENT_HINT;
7209
+ var GLOBAL_WORKSPACE_ID, BOARD_COLUMN_DEFS, HOME_BOARD_CACHE_TTL_MS, DEFAULT_WORKTREE_SORT, DEFAULTISH_BRANCH, BOARD_PAGE_SIZE, HOME_BOARD_AGENT_HINT;
6969
7210
  var init_home_board = __esm({
6970
7211
  "src/board/home-board.ts"() {
6971
7212
  "use strict";
6972
7213
  init_message_parts();
6973
7214
  init_worktree_labels();
6974
7215
  init_agent_block_view();
7216
+ init_pr_state();
7217
+ init_worktree_ownership();
7218
+ init_worktree_list_badges();
6975
7219
  init_agent_block_view();
6976
7220
  GLOBAL_WORKSPACE_ID = "__global__";
6977
7221
  BOARD_COLUMN_DEFS = [
6978
7222
  { id: "new", title: "New" },
6979
7223
  { id: "draft", title: "Draft" },
6980
7224
  { id: "review", title: "Review" },
7225
+ { id: "approved", title: "Approved" },
6981
7226
  { id: "done", title: "Merged" }
6982
7227
  ];
6983
7228
  HOME_BOARD_CACHE_TTL_MS = 15 * 60 * 1e3;
6984
7229
  DEFAULT_WORKTREE_SORT = "created";
6985
- DEFAULT_BOARD_OWNERSHIP = "all";
6986
7230
  DEFAULTISH_BRANCH = /^(main|master|develop|development|trunk|default|head)$/;
6987
7231
  BOARD_PAGE_SIZE = 40;
6988
- HOME_BOARD_AGENT_HINT = "Home is a Kanban of workspaces (one card per checkout; sibling chats on a card are separate agents). Do not create a second workspace for a ticket, PR, or named branch that already has a live checkout \u2014 create_workspace / start_board_card return that checkout (alreadyStarted) and its chats. Add another agent with fork_chat. Creating from the default branch still opens a new isolated workspace. Columns are the path to merge: New (no PR) \u2192 Draft (draft PR) \u2192 Review (open PR) \u2192 Merged. ownership=mine is PRs you authored (or WIP with no PR); ownership=reviewing is someone else's PR (or a review-requested checkout). Viewer login comes from gh, not a setting. Archive removes the card to Settings \u2192 History (list_archive to browse, restore_workspace to bring it back). Queued/running are activity on the card, not columns. blockedReason means an agent on that card is waiting on a person (the question, plan approval, or a reported block) \u2014 read it and do not send_to_chat a check-in. Orchestration chats stay in the sidebar. Do not invent status.";
7232
+ HOME_BOARD_AGENT_HINT = "Home is a Kanban of workspaces (one card per checkout; sibling chats on a card are separate agents). Do not create a second workspace for a ticket, PR, or named branch that already has a live checkout \u2014 create_workspace / start_board_card return that checkout (alreadyStarted) and its chats. Add another agent with fork_chat. Creating from the default branch still opens a new isolated workspace. Columns are the path to merge: New (no PR) \u2192 Draft (draft PR) \u2192 Review (open PR) \u2192 Approved (approved, not merged) \u2192 Merged. ownership=mine is PRs you authored (or WIP with no PR); ownership=reviewing is someone else's PR (or a review-requested checkout). Viewer login comes from gh, not a setting. Archive removes the card to Settings \u2192 History (list_archive to browse, restore_workspace to bring it back). Queued/running are activity on the card, not columns. blockedReason means an agent on that card is waiting on a person (the question, plan approval, or a reported block) \u2014 read it and do not send_to_chat a check-in. Orchestration chats stay in the sidebar. Do not invent status.";
6989
7233
  }
6990
7234
  });
6991
7235
 
@@ -9039,138 +9283,6 @@ var init_repo_git_lock = __esm({
9039
9283
  }
9040
9284
  });
9041
9285
 
9042
- // src/git/pr-gates.ts
9043
- function prIsInMergeQueue(gate) {
9044
- if (gate.isInMergeQueue) return true;
9045
- return (gate.mergeStateStatus ?? "").toUpperCase() === "QUEUED";
9046
- }
9047
- function applyCleanLocalMergeTree(gate) {
9048
- const mergeable = (gate.mergeable ?? "").toUpperCase();
9049
- const mergeState = (gate.mergeStateStatus ?? "").toUpperCase();
9050
- const githubConflicting = mergeable === "CONFLICTING" || mergeState === "DIRTY";
9051
- if (githubConflicting) {
9052
- return {
9053
- ...gate,
9054
- mergeable: "MERGEABLE",
9055
- mergeStateStatus: mergeState === "BEHIND" ? "BEHIND" : "CLEAN"
9056
- };
9057
- }
9058
- return {
9059
- ...gate,
9060
- mergeable: mergeable === "UNKNOWN" || !mergeable ? "MERGEABLE" : gate.mergeable,
9061
- mergeStateStatus: mergeState === "BEHIND" ? "BEHIND" : mergeState === "UNKNOWN" || !mergeState ? "CLEAN" : gate.mergeStateStatus
9062
- };
9063
- }
9064
- function ciChecksFailed(checks) {
9065
- if (!checks?.length) return false;
9066
- return checks.some(
9067
- (check) => (!check.kind || check.kind === "ci") && check.bucket === "fail"
9068
- );
9069
- }
9070
- function buildMergeGateChecks(gate, opts = {}) {
9071
- const rows = [];
9072
- const base = gate.baseRefName?.trim() || "the base branch";
9073
- const link = gate.url?.trim() || null;
9074
- const mergeable = (gate.mergeable ?? "").toUpperCase();
9075
- const mergeState = (gate.mergeStateStatus ?? "").toUpperCase();
9076
- const review = (gate.reviewDecision ?? "").toUpperCase();
9077
- const suppressBlocked = opts.suppressGenericBlocked !== false;
9078
- const conflicting = mergeable === "CONFLICTING" || mergeState === "DIRTY";
9079
- const inQueue = prIsInMergeQueue(gate);
9080
- if (conflicting && !inQueue) {
9081
- rows.push({
9082
- name: "Merge conflicts",
9083
- state: mergeable === "CONFLICTING" ? "CONFLICTING" : mergeState || "DIRTY",
9084
- bucket: "fail",
9085
- startedAt: null,
9086
- completedAt: null,
9087
- link,
9088
- description: `This branch has conflicts that must be resolved before it can merge into ${base}.`,
9089
- workflow: "mergeability",
9090
- kind: "mergeability"
9091
- });
9092
- } else if (inQueue) {
9093
- rows.push({
9094
- name: "Merge queue",
9095
- state: "QUEUED",
9096
- bucket: "pending",
9097
- startedAt: null,
9098
- completedAt: null,
9099
- link,
9100
- description: `This pull request is in the GitHub merge queue for ${base}.`,
9101
- workflow: "mergeability",
9102
- kind: "mergeability"
9103
- });
9104
- } else if (mergeState === "BEHIND") {
9105
- rows.push({
9106
- name: "Branch behind",
9107
- state: "BEHIND",
9108
- bucket: "info",
9109
- startedAt: null,
9110
- completedAt: null,
9111
- link,
9112
- description: `Head is behind ${base}. Update this branch (merge or rebase) before merging.`,
9113
- workflow: "mergeability",
9114
- kind: "mergeability"
9115
- });
9116
- } else if (mergeState === "BLOCKED" && !suppressBlocked) {
9117
- rows.push({
9118
- name: "Merge blocked",
9119
- state: "BLOCKED",
9120
- bucket: "fail",
9121
- startedAt: null,
9122
- completedAt: null,
9123
- link,
9124
- description: "GitHub reports this PR as blocked (branch protection \u2014 required reviews and/or status checks).",
9125
- workflow: "mergeability",
9126
- kind: "mergeability"
9127
- });
9128
- } else if (mergeable === "UNKNOWN" || mergeState === "UNKNOWN") {
9129
- rows.push({
9130
- name: "Mergeability",
9131
- state: "UNKNOWN",
9132
- bucket: "pending",
9133
- startedAt: null,
9134
- completedAt: null,
9135
- link,
9136
- description: "GitHub is still computing whether this branch can merge.",
9137
- workflow: "mergeability",
9138
- kind: "mergeability"
9139
- });
9140
- }
9141
- if (review === "CHANGES_REQUESTED") {
9142
- rows.push({
9143
- name: "Code review",
9144
- state: "CHANGES_REQUESTED",
9145
- bucket: "fail",
9146
- startedAt: null,
9147
- completedAt: null,
9148
- link,
9149
- description: "A reviewer requested changes. Address their feedback before merging.",
9150
- workflow: "review",
9151
- kind: "review"
9152
- });
9153
- } else if (review === "REVIEW_REQUIRED") {
9154
- rows.push({
9155
- name: "Code review",
9156
- state: "REVIEW_REQUIRED",
9157
- bucket: "pending",
9158
- startedAt: null,
9159
- completedAt: null,
9160
- link,
9161
- description: "A required approving review is still outstanding.",
9162
- workflow: "review",
9163
- kind: "review"
9164
- });
9165
- }
9166
- return rows;
9167
- }
9168
- var init_pr_gates = __esm({
9169
- "src/git/pr-gates.ts"() {
9170
- "use strict";
9171
- }
9172
- });
9173
-
9174
9286
  // src/git/stack.ts
9175
9287
  async function detectGhStack(cwd) {
9176
9288
  const now = Date.now();
@@ -11717,7 +11829,7 @@ function coordinatorTurnReminder(opts) {
11717
11829
  goal ? `- Goal / title: ${goal}` : null,
11718
11830
  accountDefaultsPlaybookLine(),
11719
11831
  accountContextReminderLine() || null,
11720
- "- Status: list_board (worktree Kanban: New \u2192 Draft \u2192 Review \u2192 Merged) or list_chats. Link chats as `[Title](sideboard://chat/<id>)`. Merge only if the user asked. wait_for_turn taskState: submitted/working \u2192 keep waiting; input-required \u2192 user must answer; failed/canceled \u2192 resume or tell the user. blockedReason is why that agent is waiting \u2014 read it; do not send_to_chat a check-in."
11832
+ "- Status: list_board (worktree Kanban: New \u2192 Draft \u2192 Review \u2192 Approved \u2192 Merged) or list_chats. Link chats as `[Title](sideboard://chat/<id>)`. Merge only if the user asked. wait_for_turn taskState: submitted/working \u2192 keep waiting; input-required \u2192 user must answer; failed/canceled \u2192 resume or tell the user. blockedReason is why that agent is waiting \u2014 read it; do not send_to_chat a check-in."
11721
11833
  ].filter(Boolean).join("\n");
11722
11834
  }
11723
11835
  function ensureGlobalCoordinatorCwd(opts) {
@@ -11825,7 +11937,7 @@ var init_coordinator_prompt = __esm({
11825
11937
  "Sideboard MCP (fleet control \u2014 prefer these for status and orchestration):",
11826
11938
  "Discover:",
11827
11939
  "- list_projects \u2014 registered repos (path + github slug + project context when set). Use account / project context to pick the right repo for tickets or reviews.",
11828
- "- list_board \u2014 Home Kanban of workspaces (New / Draft / Review / Merged; one card per checkout). Sibling chats on a card are separate agents, not one agent. Path to merge: no PR \u2192 draft PR \u2192 open PR \u2192 merged. Archive removes the card to Settings \u2192 History (list_archive to browse, restore_workspace to bring it back). Queued/running are activity on the card, not columns. Orchestration chats are not on the board. Filters: query, repoPath, kind, column, limit.",
11940
+ "- list_board \u2014 Home Kanban of workspaces (New / Draft / Review / Approved / Merged; one card per checkout). Sibling chats on a card are separate agents, not one agent. Path to merge: no PR \u2192 draft PR \u2192 open PR \u2192 approved \u2192 merged. Archive removes the card to Settings \u2192 History (list_archive to browse, restore_workspace to bring it back). Queued/running are activity on the card, not columns. Orchestration chats are not on the board. Filters: query, repoPath, kind, column, limit.",
11829
11941
  `- list_branches / list_prs / list_issues \u2014 pass repoPath from list_projects. Review is PRs (the surface for assigned ticket work), not the tickets: "Get me N tickets to review" \u2192 list_prs(queue=review, limit=N) then create_workspace sourceType=pr. That is open non-draft PRs labeled with this project's ready-for-review GitHub label (Settings \u2192 Projects; default eng-review) with no individual user reviewer yet. A team request (engineering-team) is not a claim \u2014 the viewer is on that team and can pick it up; claimed means an individual account is the reviewer. Bots ignored. Use Settings \u2192 Agents / Projects context (roles, teams, labels \u2014 freeform text; project adds to account) to prefer the right queues. queue=mine is review-requested:@me; queue=approved|changes uses those labels. Also: state, label, reviewer=me|unassigned|login, query, limit default 40 max 250; raise limit or tighten when truncated. list_issues (query, assignee=me|unassigned|all|user, limit default 40 max 250) lists Linear, AbleTime, or GitHub tickets \u2014 do not use it for that review-inbox ask. When they ask for tickets to work on, follow their context (assignee=me or unassigned as it says).`,
11830
11942
  "- Find work: when they ask to find work, pick up tickets, or list reviews, use Settings \u2192 Agents (account) plus Settings \u2192 Projects (per-repo) context. Tickets \u2192 Sideboard list_issues / linear_* (Account Linear). Reviews \u2192 list_prs(queue=review). Do not call Claude Linear MCP or any other vendor Linear MCP \u2014 those HTTP connectors hang or flap on the first turn. If a vendor MCP is down or reconnecting, ignore it and keep going with Sideboard tools. Show the options \u2014 do not create_workspace or start unless they also asked to start (e.g. \u201Cfind me work and start it\u201D). Do not start this unprompted. To change that context, show the proposed text, ask_user (Save this context / Do not save), wait, then update_viewer_context with confirmed=true. Never write it without confirmation.",
11831
11943
  "- Ticket updates: list_issues / linear_search_issues / github_search_issues with updatedSince (yesterday, 2d, or ISO). One call returns new assignments, ticket edits, and new comments \u2014 do not get_issue every ticket just to check for comments. kind=created vs updated distinguishes new tickets from existing ones.",
@@ -37040,7 +37152,7 @@ async function startMcpServer() {
37040
37152
  );
37041
37153
  server.tool(
37042
37154
  "list_board",
37043
- "Home Kanban of workspaces (New, Draft, Review, Merged) \u2014 one card per checkout; sibling chats on that card are separate agents. Same cards as desktop Home. Path to merge: no PR \u2192 draft PR \u2192 open PR \u2192 merged. Archive removes the card to Settings \u2192 History (list_archive to browse, restore_workspace to bring it back). Queued/running are activity on the card, not columns. Orchestration chats are not on the board. Filters: query, repoPath, kind (ticket/PR/branch source), ownership (mine = your PRs / WIP; reviewing = someone else's PR), column, limit (default 40). create_workspace adds a workspace (and a Home card), or returns the live checkout if that ticket/PR/named branch is already checked out. That reuse is one checkout, not one agent \u2014 fork_chat to add another.",
37155
+ "Home Kanban of workspaces (New, Draft, Review, Approved, Merged) \u2014 one card per checkout; sibling chats on that card are separate agents. Same cards as desktop Home. Path to merge: no PR \u2192 draft PR \u2192 open PR \u2192 approved \u2192 merged. Archive removes the card to Settings \u2192 History (list_archive to browse, restore_workspace to bring it back). Queued/running are activity on the card, not columns. Orchestration chats are not on the board. Filters: query, repoPath, kind (ticket/PR/branch source), ownership (mine = your PRs / WIP; reviewing = someone else's PR), column, limit (default 40). create_workspace adds a workspace (and a Home card), or returns the live checkout if that ticket/PR/named branch is already checked out. That reuse is one checkout, not one agent \u2014 fork_chat to add another.",
37044
37156
  {
37045
37157
  query: import_zod11.z.string().optional().describe("Case-insensitive token search across title, id, labels, workspace tags, repo"),
37046
37158
  repoPath: import_zod11.z.string().optional().describe("Limit to one project path from list_projects"),
@@ -37048,8 +37160,8 @@ async function startMcpServer() {
37048
37160
  ownership: import_zod11.z.enum(["all", "mine", "reviewing"]).optional().describe(
37049
37161
  "Mine = PRs you authored or local WIP; reviewing = someone else's PR. Uses the gh login."
37050
37162
  ),
37051
- column: import_zod11.z.enum(["new", "draft", "review", "done", "needs_you"]).optional().describe(
37052
- "Return cards for this column only (totals still include the rest). needs_you is a legacy alias for new."
37163
+ column: import_zod11.z.enum(["new", "draft", "review", "approved", "done", "needs_you"]).optional().describe(
37164
+ "Return cards for this column only (totals still include the rest). approved is an open PR GitHub has approved. needs_you is a legacy alias for new."
37053
37165
  ),
37054
37166
  limit: import_zod11.z.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
37055
37167
  },
@@ -41901,7 +42013,7 @@ function listPhoneSidebar(viewerLogin = phoneViewerLogin()) {
41901
42013
  label: worktreeDisplayLabelForGroup(group),
41902
42014
  chats: group.map(summarize),
41903
42015
  ...tags.length > 0 ? { tags } : {},
41904
- ...badge ? { badge: { id: badge.id, label: badge.label, title: badge.title } } : {}
42016
+ ...badge ? { badge: { id: badge.id, label: badge.label, title: badge.title, ...badge.conflict ? { conflict: true } : {}, ...badge.error ? { error: badge.error } : {} } } : {}
41905
42017
  };
41906
42018
  })
41907
42019
  }));