@sideboard-ai/core 0.1.246 → 0.1.248

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 (48) hide show
  1. package/dist/{abletime-OI6FNSXX.js → abletime-GAPUN2LL.js} +4 -4
  2. package/dist/{abletime-CWIJ7INC.js → abletime-HTRIHTL5.js} +4 -4
  3. package/dist/{abletime-oauth-CHVTUZBR.js → abletime-oauth-G6ZPGVAE.js} +3 -3
  4. package/dist/{abletime-oauth-PUIPZTA4.js → abletime-oauth-ME7ZY2RS.js} +3 -3
  5. package/dist/{abletime-rest-32NIVXAY.js → abletime-rest-73EKIAZU.js} +3 -3
  6. package/dist/{abletime-rest-APJUKFKL.js → abletime-rest-M2D2NELV.js} +3 -3
  7. package/dist/{agents-G7F3BJNB.js → agents-3PQ2BWGB.js} +5 -5
  8. package/dist/{agents-47WI5IV2.js → agents-7SCJTTZN.js} +5 -5
  9. package/dist/{app-settings-VOPXJMGV.js → app-settings-2BHGANQH.js} +7 -1
  10. package/dist/{app-settings-652J57CX.js → app-settings-NKXH4PKM.js} +7 -1
  11. package/dist/{chunk-576S3HJ7.js → chunk-64NUGRZ3.js} +4 -4
  12. package/dist/{chunk-IMO7AZI4.js → chunk-6HIAYIXT.js} +2 -2
  13. package/dist/{chunk-WURF67ID.js → chunk-BEQE7VOE.js} +5 -5
  14. package/dist/{chunk-6I5YQTNK.js → chunk-CXTIOJRX.js} +12 -0
  15. package/dist/{chunk-RLNEKXXT.js → chunk-D3DYMGZS.js} +4 -4
  16. package/dist/{chunk-TLPJ44Y7.js → chunk-ERQYYL4I.js} +74 -23
  17. package/dist/{chunk-C64YQAA6.js → chunk-GFGSC4Z7.js} +3 -3
  18. package/dist/{chunk-43F44EKX.js → chunk-GVAR6R6R.js} +2 -2
  19. package/dist/{chunk-QFRX5S2E.js → chunk-KFXP4KSP.js} +143 -24
  20. package/dist/{chunk-G45QP2ZD.js → chunk-LPS6D346.js} +3 -3
  21. package/dist/{chunk-7HOKP4GR.js → chunk-PELMZRDB.js} +12 -0
  22. package/dist/{chunk-PPJ5JTWA.js → chunk-QVQPIFBV.js} +28 -2
  23. package/dist/{chunk-QNJXJIWJ.js → chunk-RF46WMSI.js} +5 -5
  24. package/dist/{chunk-GFRC6JQN.js → chunk-U2ZZMGPV.js} +4 -4
  25. package/dist/{chunk-HRWCW6LC.js → chunk-UVWIYSAA.js} +4 -4
  26. package/dist/{chunk-E6C5NR46.js → chunk-W4F7JKXF.js} +20 -6
  27. package/dist/{chunk-YLGBQVRK.js → chunk-XEBQRCDW.js} +28 -2
  28. package/dist/{chunk-5ZFULOTC.js → chunk-XM4UIFQU.js} +20 -6
  29. package/dist/{chunk-BL2UGKWU.js → chunk-Z7QWXE4W.js} +3 -3
  30. package/dist/{coordinator-prompt-ZBYRREHD.js → coordinator-prompt-BFO2TAVR.js} +4 -4
  31. package/dist/{coordinator-prompt-WQIICPT6.js → coordinator-prompt-IG676OSP.js} +4 -4
  32. package/dist/{global-workspace-3SQU4GL4.js → global-workspace-J6H7X5SM.js} +4 -4
  33. package/dist/{global-workspace-2N6KISDO.js → global-workspace-RLCICVQE.js} +4 -4
  34. package/dist/index.cjs +193 -14
  35. package/dist/index.d.cts +66 -2
  36. package/dist/index.d.ts +66 -2
  37. package/dist/index.js +27 -16
  38. package/dist/mcp/run-stdio.cjs +117 -14
  39. package/dist/mcp/run-stdio.js +17 -15
  40. package/dist/{orchestrator-DPMS5KMY.js → orchestrator-2NZUV4LI.js} +6 -6
  41. package/dist/{orchestrator-2QGJ7CW2.js → orchestrator-JCETTHVV.js} +6 -6
  42. package/dist/{thread-store-JQB7KZJR.js → thread-store-BEBZMOS7.js} +1 -1
  43. package/dist/{thread-store-SCV2IZYS.js → thread-store-S6PJADDM.js} +1 -1
  44. package/dist/{workspaces-QLKWW3O2.js → workspaces-NJF3GG5F.js} +4 -4
  45. package/dist/{workspaces-CFI7XVAE.js → workspaces-OLBW42V3.js} +4 -4
  46. package/dist/{worktree-PK3QETXO.js → worktree-2FYRYJTB.js} +3 -3
  47. package/dist/{worktree-4UAOZUJ4.js → worktree-UKLK7VHH.js} +3 -3
  48. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -1365,6 +1365,7 @@ __export(app_settings_exports, {
1365
1365
  HARNESS_ENV_KEYS: () => HARNESS_ENV_KEYS,
1366
1366
  ISSUE_SOURCE_LABELS: () => ISSUE_SOURCE_LABELS,
1367
1367
  PROFILE_NOTES_MAX: () => PROFILE_NOTES_MAX,
1368
+ REVIEW_LABEL_MAX: () => REVIEW_LABEL_MAX,
1368
1369
  USAGE_ON_LIMIT_VALUES: () => USAGE_ON_LIMIT_VALUES,
1369
1370
  agentDoneSound: () => agentDoneSound,
1370
1371
  appSettingsPath: () => appSettingsPath,
@@ -1421,6 +1422,7 @@ __export(app_settings_exports, {
1421
1422
  issueSourceLabel: () => issueSourceLabel,
1422
1423
  loadAppSettings: () => loadAppSettings,
1423
1424
  maxConcurrentAgents: () => maxConcurrentAgents,
1425
+ normalizeReviewLabel: () => normalizeReviewLabel,
1424
1426
  orchestrationQuotaFallbackAgent: () => orchestrationQuotaFallbackAgent,
1425
1427
  orchestrationQuotaOnLimit: () => orchestrationQuotaOnLimit,
1426
1428
  readViewerContext: () => readViewerContext,
@@ -1432,6 +1434,7 @@ __export(app_settings_exports, {
1432
1434
  resolveNewOrchestratorOptions: () => resolveNewOrchestratorOptions,
1433
1435
  resolveNewThreadOptions: () => resolveNewThreadOptions,
1434
1436
  resolveOrchestratorDefaults: () => resolveOrchestratorDefaults,
1437
+ resolveProjectReviewLabel: () => resolveProjectReviewLabel,
1435
1438
  resolveThreadDefaults: () => resolveThreadDefaults,
1436
1439
  resolveUsageOnLimit: () => resolveUsageOnLimit,
1437
1440
  resolveViewerProfile: () => resolveViewerProfile,
@@ -1764,13 +1767,25 @@ function normalizeDefaults(raw) {
1764
1767
  function profileRepoKey(path2) {
1765
1768
  return path2.trim().replace(/\/+$/, "");
1766
1769
  }
1770
+ function normalizeReviewLabel(value) {
1771
+ const label = (value ?? "").trim().replace(/^:+/, "").slice(0, REVIEW_LABEL_MAX);
1772
+ if (!label || /[,|]/.test(label)) return "";
1773
+ return label;
1774
+ }
1775
+ function projectProfileHasFields(profile) {
1776
+ return Boolean(profile.notes || profile.reviewLabel);
1777
+ }
1767
1778
  function normalizeProjectProfile(raw) {
1768
1779
  if (!raw || typeof raw !== "object") return void 0;
1769
1780
  const source = raw;
1770
1781
  const out = {};
1771
1782
  const notes = foldLegacyRolesIntoNotes(source.roles, source.role, source.notes);
1772
1783
  if (notes) out.notes = notes;
1773
- return out.notes ? out : void 0;
1784
+ const reviewLabel = normalizeReviewLabel(
1785
+ typeof source.reviewLabel === "string" ? source.reviewLabel : null
1786
+ );
1787
+ if (reviewLabel) out.reviewLabel = reviewLabel;
1788
+ return projectProfileHasFields(out) ? out : void 0;
1774
1789
  }
1775
1790
  function normalizeProjects(raw) {
1776
1791
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
@@ -2443,12 +2458,22 @@ function updateProjectProfileSettings(repoPath, patch) {
2443
2458
  if (!notes) delete next.notes;
2444
2459
  else next.notes = notes;
2445
2460
  }
2461
+ if ("reviewLabel" in patch) {
2462
+ const reviewLabel = normalizeReviewLabel(patch.reviewLabel);
2463
+ if (!reviewLabel) delete next.reviewLabel;
2464
+ else next.reviewLabel = reviewLabel;
2465
+ }
2446
2466
  delete next.roles;
2447
2467
  delete next.role;
2448
- if (!next.notes) delete projects[key];
2468
+ if (!projectProfileHasFields(next)) delete projects[key];
2449
2469
  else projects[key] = next;
2450
2470
  return saveAppSettings({ ...current, projects });
2451
2471
  }
2472
+ function resolveProjectReviewLabel(settings, repoPath) {
2473
+ const key = findProjectProfileKey(settings.projects, repoPath);
2474
+ if (!key) return "";
2475
+ return settings.projects[key]?.reviewLabel?.trim() || "";
2476
+ }
2452
2477
  function getDefaultAgent(settings = loadAppSettings()) {
2453
2478
  return settings.defaults.agent ?? "claude";
2454
2479
  }
@@ -2973,7 +2998,7 @@ function childEnvWithAppSettings(extra) {
2973
2998
  function harnessEnvKey(harness) {
2974
2999
  return HARNESS_ENV_KEYS[harness];
2975
3000
  }
2976
- var import_node_crypto2, import_node_fs8, import_node_os3, import_node_path9, HARNESS_ENV_KEYS, DEFAULT_AGENTS, GITHUB_GIT_AUTH_MODES, FOLLOW_UP_BEHAVIORS, AGENT_DONE_SOUNDS, CLOUD_CONNECT_AGENTS, ISSUE_SOURCES, GIT_AUTH_MODES, EMPTY_SETTINGS, CONFIRM_ACCOUNT, CONFIRM_PROJECT, DEFAULT_CLI_BIN;
3001
+ var import_node_crypto2, import_node_fs8, import_node_os3, import_node_path9, HARNESS_ENV_KEYS, DEFAULT_AGENTS, GITHUB_GIT_AUTH_MODES, FOLLOW_UP_BEHAVIORS, AGENT_DONE_SOUNDS, CLOUD_CONNECT_AGENTS, ISSUE_SOURCES, GIT_AUTH_MODES, REVIEW_LABEL_MAX, EMPTY_SETTINGS, CONFIRM_ACCOUNT, CONFIRM_PROJECT, DEFAULT_CLI_BIN;
2977
3002
  var init_app_settings = __esm({
2978
3003
  "src/store/app-settings.ts"() {
2979
3004
  "use strict";
@@ -3030,6 +3055,7 @@ var init_app_settings = __esm({
3030
3055
  ]);
3031
3056
  ISSUE_SOURCES = /* @__PURE__ */ new Set(["linear", "github", "abletime"]);
3032
3057
  GIT_AUTH_MODES = new Set(GITHUB_GIT_AUTH_MODES);
3058
+ REVIEW_LABEL_MAX = 50;
3033
3059
  EMPTY_SETTINGS = {
3034
3060
  environment: {},
3035
3061
  claude: {},
@@ -3315,6 +3341,12 @@ function normalizeThread(raw) {
3315
3341
  prTitle: raw.prTitle ?? null,
3316
3342
  prState: raw.prState ?? null,
3317
3343
  prIsDraft: Boolean(raw.prIsDraft),
3344
+ prReviewDecision: raw.prReviewDecision ?? null,
3345
+ prMergeable: raw.prMergeable ?? null,
3346
+ prMergeStateStatus: raw.prMergeStateStatus ?? null,
3347
+ prIsInMergeQueue: Boolean(raw.prIsInMergeQueue),
3348
+ prLabels: Array.isArray(raw.prLabels) ? raw.prLabels : [],
3349
+ prChecksFailed: typeof raw.prChecksFailed === "boolean" ? raw.prChecksFailed : void 0,
3318
3350
  skipAutoArchiveOnMerge: Boolean(raw.skipAutoArchiveOnMerge),
3319
3351
  cowboy: Boolean(raw.cowboy),
3320
3352
  stackId: raw.stackId ?? null,
@@ -3350,6 +3382,12 @@ function createEmptyThread(partial) {
3350
3382
  prIsDraft: Boolean(partial.prIsDraft),
3351
3383
  prAuthorLogin: partial.prAuthorLogin?.trim() || null,
3352
3384
  prReviewerLogins: partial.prReviewerLogins ?? [],
3385
+ prReviewDecision: partial.prReviewDecision ?? null,
3386
+ prMergeable: partial.prMergeable ?? null,
3387
+ prMergeStateStatus: partial.prMergeStateStatus ?? null,
3388
+ prIsInMergeQueue: Boolean(partial.prIsInMergeQueue),
3389
+ prLabels: partial.prLabels ?? [],
3390
+ prChecksFailed: partial.prChecksFailed,
3353
3391
  skipAutoArchiveOnMerge: partial.skipAutoArchiveOnMerge ?? false,
3354
3392
  cowboy: Boolean(partial.cowboy),
3355
3393
  stackId: partial.stackId ?? null,
@@ -5823,6 +5861,72 @@ function classifyWorktreeColumn(group) {
5823
5861
  }
5824
5862
  return "new";
5825
5863
  }
5864
+ function labelsInclude(labels, name) {
5865
+ const want = name.trim().toLowerCase();
5866
+ if (!want) return false;
5867
+ return (labels ?? []).some((label) => label.trim().toLowerCase() === want);
5868
+ }
5869
+ function reviewLabelForRepo(labelsByRepo, repoPath) {
5870
+ const want = repoPath.trim().replace(/\/+$/, "");
5871
+ if (!want || !labelsByRepo) return "";
5872
+ const direct = labelsByRepo[want]?.trim();
5873
+ if (direct) return direct;
5874
+ let best = "";
5875
+ let bestLen = 0;
5876
+ for (const [key, label] of Object.entries(labelsByRepo)) {
5877
+ const stored = key.trim().replace(/\/+$/, "");
5878
+ const value = label.trim();
5879
+ if (!stored || !value) continue;
5880
+ if (stored === want || want.startsWith(`${stored}/`)) {
5881
+ if (stored.length > bestLen) {
5882
+ best = value;
5883
+ bestLen = stored.length;
5884
+ }
5885
+ }
5886
+ }
5887
+ return best;
5888
+ }
5889
+ function groupPrLabels(group) {
5890
+ const out = [];
5891
+ const seen = /* @__PURE__ */ new Set();
5892
+ for (const thread of group) {
5893
+ for (const label of thread.prLabels ?? []) {
5894
+ const key = label.trim().toLowerCase();
5895
+ if (!key || seen.has(key)) continue;
5896
+ seen.add(key);
5897
+ out.push(label.trim());
5898
+ }
5899
+ }
5900
+ return out;
5901
+ }
5902
+ function classifyWorktreeListBadges(group, viewerLogin = "", reviewLabel = "") {
5903
+ if (group.length === 0 || group.some((t) => t.cowboy)) return [];
5904
+ const open = group.filter((t) => isOpenPrState(t.prUrl, t.prState));
5905
+ if (open.length === 0) return [];
5906
+ const ownership = classifyWorktreeOwnership(group, viewerLogin);
5907
+ if (ownership === "reviewing") {
5908
+ return [
5909
+ {
5910
+ id: "review",
5911
+ label: "review",
5912
+ title: "Someone else's PR",
5913
+ mod: "is-review"
5914
+ }
5915
+ ];
5916
+ }
5917
+ const draft = open.some((t) => t.prIsDraft);
5918
+ const readyLabel = reviewLabel.trim();
5919
+ const inReview = readyLabel ? labelsInclude(groupPrLabels(group), readyLabel) : !draft;
5920
+ if (!inReview) return [];
5921
+ return [
5922
+ {
5923
+ id: "pending",
5924
+ label: "pending",
5925
+ title: "Waiting for review",
5926
+ mod: "is-pending"
5927
+ }
5928
+ ];
5929
+ }
5826
5930
  function worktreeBoardStatus(group) {
5827
5931
  const order = ["running", "queued", "error", "broken"];
5828
5932
  for (const status of order) {
@@ -7996,6 +8100,10 @@ function normalizePrListQueue(queue, state) {
7996
8100
  }
7997
8101
  return void 0;
7998
8102
  }
8103
+ function reviewQueueLabel(reviewLabel) {
8104
+ const label = (reviewLabel ?? "").trim();
8105
+ return label || QUEUE_DEFAULTS.review.labels[0];
8106
+ }
7999
8107
  function ticketRefsFromPrTitle(title) {
8000
8108
  const out = [];
8001
8109
  const seen = /* @__PURE__ */ new Set();
@@ -8013,10 +8121,12 @@ function resolveListPrsOptions(opts) {
8013
8121
  const defaults = queue ? QUEUE_DEFAULTS[queue] : void 0;
8014
8122
  const stateIsQueueAlias = Boolean(normalizePrListQueue(void 0, opts?.state));
8015
8123
  const labels = normalizePrLabels(opts?.labels);
8124
+ const readyLabel = reviewQueueLabel(opts?.reviewLabel);
8125
+ const queueLabels = queue === "review" || queue === "mine" ? [readyLabel] : defaults?.labels ?? [];
8016
8126
  return {
8017
8127
  ...queue ? { queue } : {},
8018
8128
  state: normalizePrListState(stateIsQueueAlias ? defaults?.state : opts?.state ?? defaults?.state),
8019
- labels: labels.length ? labels : defaults?.labels ?? [],
8129
+ labels: labels.length ? labels : queueLabels,
8020
8130
  ...opts?.reviewer?.trim() ? { reviewer: opts.reviewer.trim() } : defaults?.reviewer ? { reviewer: defaults.reviewer } : {},
8021
8131
  ...opts?.query?.trim() ? { query: opts.query.trim() } : {},
8022
8132
  ...opts?.draft !== void 0 ? { draft: opts.draft } : defaults?.draft !== void 0 ? { draft: defaults.draft } : {},
@@ -8377,6 +8487,12 @@ function prIsInMergeQueue(gate) {
8377
8487
  if (gate.isInMergeQueue) return true;
8378
8488
  return (gate.mergeStateStatus ?? "").toUpperCase() === "QUEUED";
8379
8489
  }
8490
+ function ciChecksFailed(checks) {
8491
+ if (!checks?.length) return false;
8492
+ return checks.some(
8493
+ (check) => (!check.kind || check.kind === "ci") && check.bucket === "fail"
8494
+ );
8495
+ }
8380
8496
  function buildMergeGateChecks(gate, opts = {}) {
8381
8497
  const rows = [];
8382
8498
  const base = gate.baseRefName?.trim() || "the base branch";
@@ -9284,7 +9400,7 @@ async function getPrMeta(cwd, selector) {
9284
9400
  "view",
9285
9401
  selector,
9286
9402
  "--json",
9287
- "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,isInMergeQueue,mergeStateStatus,mergeable,author,reviewRequests"
9403
+ "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,isInMergeQueue,mergeStateStatus,mergeable,author,reviewRequests,labels"
9288
9404
  ];
9289
9405
  if (slug) viewArgs.push("--repo", slug);
9290
9406
  let { stdout, exitCode, stderr } = await gh(viewArgs, cwd, { reject: false });
@@ -9294,7 +9410,7 @@ async function getPrMeta(cwd, selector) {
9294
9410
  "view",
9295
9411
  selector,
9296
9412
  "--json",
9297
- "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,mergeStateStatus,mergeable,author,reviewRequests"
9413
+ "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,mergeStateStatus,mergeable,author,reviewRequests,labels"
9298
9414
  ];
9299
9415
  if (slug) retry.push("--repo", slug);
9300
9416
  ({ stdout, exitCode, stderr } = await gh(retry, cwd, { reject: false }));
@@ -9329,7 +9445,8 @@ async function getPrMeta(cwd, selector) {
9329
9445
  mergeable: resolved.mergeable,
9330
9446
  mergeStateStatus: resolved.mergeStateStatus,
9331
9447
  authorLogin: author.login?.trim() || null,
9332
- reviewerLogins: humanReviewerLogins(view.reviewRequests)
9448
+ reviewerLogins: humanReviewerLogins(view.reviewRequests),
9449
+ labels: labelNames(view.labels)
9333
9450
  };
9334
9451
  } catch {
9335
9452
  return null;
@@ -10562,7 +10679,7 @@ var init_coordinator_prompt = __esm({
10562
10679
  "Discover:",
10563
10680
  "- list_workspaces \u2014 registered repos (path + github slug + project context when set). Use account / project context to pick the right repo for tickets or reviews.",
10564
10681
  "- list_board \u2014 Home Kanban of worktrees (New / Draft / Review / Merged; one card per checkout). Path to merge: no PR \u2192 draft PR \u2192 open PR \u2192 merged. Archive removes the card to Settings \u2192 History. Queued/running are activity on the card, not columns. Orchestration chats are not on the board. Filters: query, repoPath, kind, column, limit.",
10565
- '- list_branches / list_prs / list_issues \u2014 pass repoPath from list_workspaces. 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_thread sourceType=pr. That is open non-draft PRs labeled 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).',
10682
+ `- list_branches / list_prs / list_issues \u2014 pass repoPath from list_workspaces. 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_thread 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).`,
10566
10683
  "- 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_thread 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.",
10567
10684
  "- 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.",
10568
10685
  "- linear_* (when Linear is connected) \u2014 list_teams for team key/states; get/create/update/comment with ENG-123. Worktree agents have the same Account tools \u2014 prefer they comment, update status, and create spin-offs (parent=) on their own ticket when they were asked to do the work. When reviewing a PR or ticket, do not comment or update until the user types a next step \u2014 they should work through the feedback in chat first. Do not tell the child to ask_user after the review. Scope errors: reconnect Linear in Account settings. Prefer these over any Linear MCP the CLI may still list.",
@@ -16634,11 +16751,41 @@ function threadPrMetaPatch(thread, meta) {
16634
16751
  if (!sameLoginList(nextReviewers, thread.prReviewerLogins)) {
16635
16752
  patch.prReviewerLogins = nextReviewers;
16636
16753
  }
16754
+ const nextDecision = meta.reviewDecision ?? null;
16755
+ if ((thread.prReviewDecision ?? null) !== nextDecision) {
16756
+ patch.prReviewDecision = nextDecision;
16757
+ }
16758
+ const nextMergeable = meta.mergeable ?? null;
16759
+ if ((thread.prMergeable ?? null) !== nextMergeable) {
16760
+ patch.prMergeable = nextMergeable;
16761
+ }
16762
+ const nextMergeState = meta.mergeStateStatus ?? null;
16763
+ if ((thread.prMergeStateStatus ?? null) !== nextMergeState) {
16764
+ patch.prMergeStateStatus = nextMergeState;
16765
+ }
16766
+ const nextQueued = Boolean(meta.isInMergeQueue);
16767
+ if (Boolean(thread.prIsInMergeQueue) !== nextQueued) {
16768
+ patch.prIsInMergeQueue = nextQueued;
16769
+ }
16770
+ const nextLabels = meta.labels ?? [];
16771
+ if (!sameLoginList(nextLabels, thread.prLabels)) {
16772
+ patch.prLabels = nextLabels;
16773
+ }
16774
+ const mergeState = (nextMergeState ?? "").toUpperCase();
16775
+ if (mergeState === "CLEAN" && thread.prChecksFailed) {
16776
+ patch.prChecksFailed = false;
16777
+ } else if ((mergeState === "UNSTABLE" || mergeState === "BLOCKED" && (nextDecision ?? "").toUpperCase() === "APPROVED") && !thread.prChecksFailed) {
16778
+ patch.prChecksFailed = true;
16779
+ }
16637
16780
  if (thread.skipAutoArchiveOnMerge && nextState && nextState !== "MERGED" && nextState !== "CLOSED") {
16638
16781
  patch.skipAutoArchiveOnMerge = false;
16639
16782
  }
16640
16783
  return patch;
16641
16784
  }
16785
+ function threadPrChecksFailedPatch(thread, failed) {
16786
+ if (thread.prChecksFailed === failed) return {};
16787
+ return { prChecksFailed: failed };
16788
+ }
16642
16789
  function shouldAutoArchiveOnPrMerge(opts) {
16643
16790
  if (!opts.autoArchiveEnabled) return false;
16644
16791
  if (opts.isGlobal) return false;
@@ -17242,6 +17389,12 @@ function worktreeBindingFrom(from) {
17242
17389
  prIsDraft: from.prIsDraft,
17243
17390
  prAuthorLogin: from.prAuthorLogin,
17244
17391
  prReviewerLogins: from.prReviewerLogins,
17392
+ prReviewDecision: from.prReviewDecision,
17393
+ prMergeable: from.prMergeable,
17394
+ prMergeStateStatus: from.prMergeStateStatus,
17395
+ prIsInMergeQueue: from.prIsInMergeQueue,
17396
+ prLabels: from.prLabels,
17397
+ prChecksFailed: from.prChecksFailed,
17245
17398
  skipAutoArchiveOnMerge: from.skipAutoArchiveOnMerge,
17246
17399
  stackId: from.stackId,
17247
17400
  stackLayer: from.stackLayer,
@@ -20725,6 +20878,7 @@ async function createThread(input, _onSetupLine) {
20725
20878
  let prUrl = null;
20726
20879
  let prTitle = null;
20727
20880
  let prAuthorLogin2 = null;
20881
+ let prLabels = [];
20728
20882
  if (sourceType === "pr") {
20729
20883
  const num2 = Number(input.sourceRef.replace(/^#/, ""));
20730
20884
  if (!Number.isFinite(num2)) throw new Error(`Invalid PR number: ${input.sourceRef}`);
@@ -20741,6 +20895,7 @@ async function createThread(input, _onSetupLine) {
20741
20895
  sourceIsFork = pr.isCrossRepository;
20742
20896
  prUrl = pr.url;
20743
20897
  prAuthorLogin2 = pr.author?.login?.trim() || null;
20898
+ prLabels = pr.labels ?? [];
20744
20899
  const localFetchBranch = `sideboard-pr-${pr.number}`;
20745
20900
  await fetchPrHead(repoPath, pr.number, localFetchBranch);
20746
20901
  sourceRef = localFetchBranch;
@@ -20764,6 +20919,7 @@ async function createThread(input, _onSetupLine) {
20764
20919
  prUrl = existing.url;
20765
20920
  prTitle = existing.title;
20766
20921
  prAuthorLogin2 = existing.author?.login?.trim() || null;
20922
+ prLabels = existing.labels ?? [];
20767
20923
  }
20768
20924
  }
20769
20925
  }
@@ -20799,7 +20955,8 @@ async function createThread(input, _onSetupLine) {
20799
20955
  status: "idle",
20800
20956
  prUrl,
20801
20957
  prTitle,
20802
- prAuthorLogin: prAuthorLogin2
20958
+ prAuthorLogin: prAuthorLogin2,
20959
+ prLabels
20803
20960
  });
20804
20961
  writeThread(thread);
20805
20962
  await ensureWorkspace(repoPath);
@@ -24255,6 +24412,7 @@ var init_orchestrator = __esm({
24255
24412
  init_stack();
24256
24413
  init_agent_git_actions();
24257
24414
  init_pr_merge_archive();
24415
+ init_pr_gates();
24258
24416
  init_conductor();
24259
24417
  init_dev_preview();
24260
24418
  init_setup_log();
@@ -26451,10 +26609,20 @@ var init_orchestrator = __esm({
26451
26609
  return { thread, selectors: connectedPrSelectors(thread, headUrl), cwd, headUrl };
26452
26610
  }
26453
26611
  async getPrChecks(threadRef) {
26454
- const { selectors, cwd } = await this.withPrSelector(threadRef);
26612
+ const { thread, selectors, cwd } = await this.withPrSelector(threadRef);
26455
26613
  for (const selector of selectors) {
26456
26614
  const checks = await getPrChecks(cwd, selector);
26457
- if (checks) return checks;
26615
+ if (!checks) continue;
26616
+ const live = this.requireThread(thread.id);
26617
+ const failed = ciChecksFailed(checks);
26618
+ const siblings = threadsSharingWorktree(live.worktreePath);
26619
+ const targets = siblings.length > 0 ? siblings : [live];
26620
+ for (const t of targets) {
26621
+ const patch = threadPrChecksFailedPatch(t, failed);
26622
+ if (Object.keys(patch).length === 0) continue;
26623
+ updateThread(t.id, patch);
26624
+ }
26625
+ return checks;
26458
26626
  }
26459
26627
  return null;
26460
26628
  }
@@ -27127,6 +27295,7 @@ __export(index_exports, {
27127
27295
  PR_DETAILS_JSON_FIELDS: () => PR_DETAILS_JSON_FIELDS,
27128
27296
  REPO_REVIEW_NAME: () => REPO_REVIEW_NAME,
27129
27297
  REPO_REVIEW_PATH: () => REPO_REVIEW_PATH,
27298
+ REVIEW_LABEL_MAX: () => REVIEW_LABEL_MAX,
27130
27299
  REVIEW_REQUEST_NAME: () => REVIEW_REQUEST_NAME,
27131
27300
  REVIEW_REQUEST_PATH: () => REVIEW_REQUEST_PATH,
27132
27301
  REVIEW_REQUEST_PREFILL: () => REVIEW_REQUEST_PREFILL,
@@ -27258,6 +27427,7 @@ __export(index_exports, {
27258
27427
  clampHistoryMaxDays: () => clampHistoryMaxDays,
27259
27428
  classifyRunScriptPreview: () => classifyRunScriptPreview,
27260
27429
  classifyWorktreeColumn: () => classifyWorktreeColumn,
27430
+ classifyWorktreeListBadges: () => classifyWorktreeListBadges,
27261
27431
  claudeAdapter: () => claudeAdapter,
27262
27432
  claudeChromeEnabled: () => claudeChromeEnabled,
27263
27433
  claudeUsageOverLimitWindows: () => claudeUsageOverLimitWindows,
@@ -27698,6 +27868,7 @@ __export(index_exports, {
27698
27868
  normalizeCodeSelection: () => normalizeCodeSelection,
27699
27869
  normalizeLinearRelationType: () => normalizeLinearRelationType,
27700
27870
  normalizeParseResult: () => normalizeParseResult,
27871
+ normalizeReviewLabel: () => normalizeReviewLabel,
27701
27872
  normalizeServiceOrigin: () => normalizeServiceOrigin,
27702
27873
  normalizeThinkingEffort: () => normalizeThinkingEffort,
27703
27874
  normalizeThread: () => normalizeThread,
@@ -27825,6 +27996,7 @@ __export(index_exports, {
27825
27996
  resolvePlanMarkdown: () => resolvePlanMarkdown,
27826
27997
  resolvePrSelector: () => resolvePrSelector,
27827
27998
  resolvePrSelectors: () => resolvePrSelectors,
27999
+ resolveProjectReviewLabel: () => resolveProjectReviewLabel,
27828
28000
  resolveQuotaFallbackAgent: () => resolveQuotaFallbackAgent,
27829
28001
  resolveRepoRoot: () => resolveRepoRoot,
27830
28002
  resolveReviewGuidelines: () => resolveReviewGuidelines,
@@ -27839,6 +28011,7 @@ __export(index_exports, {
27839
28011
  resolveViewerProfile: () => resolveViewerProfile,
27840
28012
  resolveViewerProfileForRepo: () => resolveViewerProfileForRepo,
27841
28013
  resolveWorktreeStartPoint: () => resolveWorktreeStartPoint,
28014
+ reviewLabelForRepo: () => reviewLabelForRepo,
27842
28015
  rewriteAbleTimeError: () => rewriteAbleTimeError,
27843
28016
  rewriteLinearError: () => rewriteLinearError,
27844
28017
  run: () => run,
@@ -34398,12 +34571,12 @@ async function startMcpServer() {
34398
34571
  );
34399
34572
  server.tool(
34400
34573
  "list_prs",
34401
- 'List GitHub PRs for a registered workspace (the review surface for assigned ticket work \u2014 not the tickets). Pass repoPath from list_workspaces. "Get me N tickets to review" \u2192 queue=review and limit=N: open non-draft PRs labeled eng-review with no individual user reviewer. Prefer teams that match Settings \u2192 Agents / Projects roles for this repo. A team like engineering-team is not a claim (you are on that team). Also: state (open|closed|merged|all|review), label, reviewer (me|unassigned|login), query, limit (default 40, max 250). Then create_thread with sourceType=pr.',
34574
+ `List GitHub PRs for a registered workspace (the review surface for assigned ticket work \u2014 not the tickets). Pass repoPath from list_workspaces. "Get me N tickets to review" \u2192 queue=review and limit=N: 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. Prefer teams that match Settings \u2192 Agents / Projects roles for this repo. A team like engineering-team is not a claim (you are on that team). Also: state (open|closed|merged|all|review), label, reviewer (me|unassigned|login), query, limit (default 40, max 250). Then create_thread with sourceType=pr.`,
34402
34575
  {
34403
34576
  repoPath: import_zod9.z.string(),
34404
34577
  query: import_zod9.z.string().optional().describe("GitHub search tokens (title, draft:true, \u2026)"),
34405
34578
  queue: import_zod9.z.enum(["review", "mine", "approved", "changes"]).optional().describe(
34406
- 'review = unclaimed eng-review inbox (use for "get me N tickets to review"). mine = review-requested:@me. approved / changes = eng-approved / eng-requested-changes.'
34579
+ "review = unclaimed ready-for-review inbox (Settings \u2192 Projects label, default eng-review). mine = review-requested:@me. approved / changes = eng-approved / eng-requested-changes."
34407
34580
  ),
34408
34581
  state: import_zod9.z.enum(["open", "closed", "merged", "all", "review"]).optional().describe("GitHub PR state (default open). review is an alias for queue=review."),
34409
34582
  label: import_zod9.z.string().optional().describe(
@@ -34423,7 +34596,8 @@ async function startMcpServer() {
34423
34596
  state,
34424
34597
  labels: label,
34425
34598
  reviewer,
34426
- limit: page + 1
34599
+ limit: page + 1,
34600
+ reviewLabel: resolveProjectReviewLabel(loadAppSettings(), root)
34427
34601
  });
34428
34602
  const prs = await listPrs(root, resolved);
34429
34603
  const windowed = applyIssueListWindow(prs, page);
@@ -36957,6 +37131,7 @@ async function startSlackRelayServer(opts) {
36957
37131
  PR_DETAILS_JSON_FIELDS,
36958
37132
  REPO_REVIEW_NAME,
36959
37133
  REPO_REVIEW_PATH,
37134
+ REVIEW_LABEL_MAX,
36960
37135
  REVIEW_REQUEST_NAME,
36961
37136
  REVIEW_REQUEST_PATH,
36962
37137
  REVIEW_REQUEST_PREFILL,
@@ -37088,6 +37263,7 @@ async function startSlackRelayServer(opts) {
37088
37263
  clampHistoryMaxDays,
37089
37264
  classifyRunScriptPreview,
37090
37265
  classifyWorktreeColumn,
37266
+ classifyWorktreeListBadges,
37091
37267
  claudeAdapter,
37092
37268
  claudeChromeEnabled,
37093
37269
  claudeUsageOverLimitWindows,
@@ -37528,6 +37704,7 @@ async function startSlackRelayServer(opts) {
37528
37704
  normalizeCodeSelection,
37529
37705
  normalizeLinearRelationType,
37530
37706
  normalizeParseResult,
37707
+ normalizeReviewLabel,
37531
37708
  normalizeServiceOrigin,
37532
37709
  normalizeThinkingEffort,
37533
37710
  normalizeThread,
@@ -37655,6 +37832,7 @@ async function startSlackRelayServer(opts) {
37655
37832
  resolvePlanMarkdown,
37656
37833
  resolvePrSelector,
37657
37834
  resolvePrSelectors,
37835
+ resolveProjectReviewLabel,
37658
37836
  resolveQuotaFallbackAgent,
37659
37837
  resolveRepoRoot,
37660
37838
  resolveReviewGuidelines,
@@ -37669,6 +37847,7 @@ async function startSlackRelayServer(opts) {
37669
37847
  resolveViewerProfile,
37670
37848
  resolveViewerProfileForRepo,
37671
37849
  resolveWorktreeStartPoint,
37850
+ reviewLabelForRepo,
37672
37851
  rewriteAbleTimeError,
37673
37852
  rewriteLinearError,
37674
37853
  run,