@sideboard-ai/core 0.1.245 → 0.1.247

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-FTQZFMDS.js → agents-3PQ2BWGB.js} +5 -5
  8. package/dist/{agents-BOIIFMNG.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-QRWVKBGP.js → chunk-D3DYMGZS.js} +18 -5
  16. package/dist/{chunk-J5IZXFLD.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-NT6MCFSB.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-3ZEBUVUI.js → chunk-UVWIYSAA.js} +18 -5
  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 +207 -15
  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 +131 -15
  39. package/dist/mcp/run-stdio.js +17 -15
  40. package/dist/{orchestrator-5ROSTGGH.js → orchestrator-2NZUV4LI.js} +6 -6
  41. package/dist/{orchestrator-7YCQ7V5S.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,
@@ -4449,6 +4487,14 @@ function lastRunningSkillIndex(parts, parentId) {
4449
4487
  }
4450
4488
  return -1;
4451
4489
  }
4490
+ function markRunningSkillsDone(parts, parentId) {
4491
+ return parts.map((p) => {
4492
+ if (p.type === "tool" && isSkillToolName(p.name) && p.status === "running" && sameParentId(p.parentId, parentId)) {
4493
+ return withPartTimes({ ...p, status: "done" }, p);
4494
+ }
4495
+ return p;
4496
+ });
4497
+ }
4452
4498
  function promoteOrFoldSkillText(parts, textIdx) {
4453
4499
  const acc = parts[textIdx];
4454
4500
  if (acc?.type !== "text") return parts;
@@ -4583,7 +4629,10 @@ function applyAgentEvent(parts, event) {
4583
4629
  {
4584
4630
  ...prev,
4585
4631
  result: clipToolResultForStore(result),
4586
- status: looksLikeSkillBody(result) ? "done" : prev.status
4632
+ // Stay running after the YAML `name:` fence so later content_block_delta
4633
+ // tokens of SKILL.md stay on this row. thinking / tool_use / finalizeParts
4634
+ // mark the row done so the real reply can be chat.
4635
+ status: "running"
4587
4636
  },
4588
4637
  prev
4589
4638
  );
@@ -4625,6 +4674,7 @@ function applyAgentEvent(parts, event) {
4625
4674
  if (event.type === "thinking") {
4626
4675
  const data = event.data;
4627
4676
  if (!data) return parts;
4677
+ parts = markRunningSkillsDone(parts, event.parentId);
4628
4678
  const next = [...parts];
4629
4679
  if (event.replace) {
4630
4680
  for (let i = next.length - 1; i >= 0; i--) {
@@ -4672,6 +4722,7 @@ function applyAgentEvent(parts, event) {
4672
4722
  return withCompactFinish(next, data, event.parentId);
4673
4723
  }
4674
4724
  if (event.type === "tool_use") {
4725
+ parts = markRunningSkillsDone(parts, event.parentId);
4675
4726
  const input = asRecord(event.input);
4676
4727
  const diff = diffFromInput(input);
4677
4728
  const existing = parts.findIndex((p) => p.type === "tool" && p.id === event.id);
@@ -5810,6 +5861,72 @@ function classifyWorktreeColumn(group) {
5810
5861
  }
5811
5862
  return "new";
5812
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
+ }
5813
5930
  function worktreeBoardStatus(group) {
5814
5931
  const order = ["running", "queued", "error", "broken"];
5815
5932
  for (const status of order) {
@@ -7983,6 +8100,10 @@ function normalizePrListQueue(queue, state) {
7983
8100
  }
7984
8101
  return void 0;
7985
8102
  }
8103
+ function reviewQueueLabel(reviewLabel) {
8104
+ const label = (reviewLabel ?? "").trim();
8105
+ return label || QUEUE_DEFAULTS.review.labels[0];
8106
+ }
7986
8107
  function ticketRefsFromPrTitle(title) {
7987
8108
  const out = [];
7988
8109
  const seen = /* @__PURE__ */ new Set();
@@ -8000,10 +8121,12 @@ function resolveListPrsOptions(opts) {
8000
8121
  const defaults = queue ? QUEUE_DEFAULTS[queue] : void 0;
8001
8122
  const stateIsQueueAlias = Boolean(normalizePrListQueue(void 0, opts?.state));
8002
8123
  const labels = normalizePrLabels(opts?.labels);
8124
+ const readyLabel = reviewQueueLabel(opts?.reviewLabel);
8125
+ const queueLabels = queue === "review" || queue === "mine" ? [readyLabel] : defaults?.labels ?? [];
8003
8126
  return {
8004
8127
  ...queue ? { queue } : {},
8005
8128
  state: normalizePrListState(stateIsQueueAlias ? defaults?.state : opts?.state ?? defaults?.state),
8006
- labels: labels.length ? labels : defaults?.labels ?? [],
8129
+ labels: labels.length ? labels : queueLabels,
8007
8130
  ...opts?.reviewer?.trim() ? { reviewer: opts.reviewer.trim() } : defaults?.reviewer ? { reviewer: defaults.reviewer } : {},
8008
8131
  ...opts?.query?.trim() ? { query: opts.query.trim() } : {},
8009
8132
  ...opts?.draft !== void 0 ? { draft: opts.draft } : defaults?.draft !== void 0 ? { draft: defaults.draft } : {},
@@ -8364,6 +8487,12 @@ function prIsInMergeQueue(gate) {
8364
8487
  if (gate.isInMergeQueue) return true;
8365
8488
  return (gate.mergeStateStatus ?? "").toUpperCase() === "QUEUED";
8366
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
+ }
8367
8496
  function buildMergeGateChecks(gate, opts = {}) {
8368
8497
  const rows = [];
8369
8498
  const base = gate.baseRefName?.trim() || "the base branch";
@@ -9271,7 +9400,7 @@ async function getPrMeta(cwd, selector) {
9271
9400
  "view",
9272
9401
  selector,
9273
9402
  "--json",
9274
- "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"
9275
9404
  ];
9276
9405
  if (slug) viewArgs.push("--repo", slug);
9277
9406
  let { stdout, exitCode, stderr } = await gh(viewArgs, cwd, { reject: false });
@@ -9281,7 +9410,7 @@ async function getPrMeta(cwd, selector) {
9281
9410
  "view",
9282
9411
  selector,
9283
9412
  "--json",
9284
- "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"
9285
9414
  ];
9286
9415
  if (slug) retry.push("--repo", slug);
9287
9416
  ({ stdout, exitCode, stderr } = await gh(retry, cwd, { reject: false }));
@@ -9316,7 +9445,8 @@ async function getPrMeta(cwd, selector) {
9316
9445
  mergeable: resolved.mergeable,
9317
9446
  mergeStateStatus: resolved.mergeStateStatus,
9318
9447
  authorLogin: author.login?.trim() || null,
9319
- reviewerLogins: humanReviewerLogins(view.reviewRequests)
9448
+ reviewerLogins: humanReviewerLogins(view.reviewRequests),
9449
+ labels: labelNames(view.labels)
9320
9450
  };
9321
9451
  } catch {
9322
9452
  return null;
@@ -10549,7 +10679,7 @@ var init_coordinator_prompt = __esm({
10549
10679
  "Discover:",
10550
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.",
10551
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.",
10552
- '- 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).`,
10553
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.",
10554
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.",
10555
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.",
@@ -16621,11 +16751,41 @@ function threadPrMetaPatch(thread, meta) {
16621
16751
  if (!sameLoginList(nextReviewers, thread.prReviewerLogins)) {
16622
16752
  patch.prReviewerLogins = nextReviewers;
16623
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
+ }
16624
16780
  if (thread.skipAutoArchiveOnMerge && nextState && nextState !== "MERGED" && nextState !== "CLOSED") {
16625
16781
  patch.skipAutoArchiveOnMerge = false;
16626
16782
  }
16627
16783
  return patch;
16628
16784
  }
16785
+ function threadPrChecksFailedPatch(thread, failed) {
16786
+ if (thread.prChecksFailed === failed) return {};
16787
+ return { prChecksFailed: failed };
16788
+ }
16629
16789
  function shouldAutoArchiveOnPrMerge(opts) {
16630
16790
  if (!opts.autoArchiveEnabled) return false;
16631
16791
  if (opts.isGlobal) return false;
@@ -17229,6 +17389,12 @@ function worktreeBindingFrom(from) {
17229
17389
  prIsDraft: from.prIsDraft,
17230
17390
  prAuthorLogin: from.prAuthorLogin,
17231
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,
17232
17398
  skipAutoArchiveOnMerge: from.skipAutoArchiveOnMerge,
17233
17399
  stackId: from.stackId,
17234
17400
  stackLayer: from.stackLayer,
@@ -20712,6 +20878,7 @@ async function createThread(input, _onSetupLine) {
20712
20878
  let prUrl = null;
20713
20879
  let prTitle = null;
20714
20880
  let prAuthorLogin2 = null;
20881
+ let prLabels = [];
20715
20882
  if (sourceType === "pr") {
20716
20883
  const num2 = Number(input.sourceRef.replace(/^#/, ""));
20717
20884
  if (!Number.isFinite(num2)) throw new Error(`Invalid PR number: ${input.sourceRef}`);
@@ -20728,6 +20895,7 @@ async function createThread(input, _onSetupLine) {
20728
20895
  sourceIsFork = pr.isCrossRepository;
20729
20896
  prUrl = pr.url;
20730
20897
  prAuthorLogin2 = pr.author?.login?.trim() || null;
20898
+ prLabels = pr.labels ?? [];
20731
20899
  const localFetchBranch = `sideboard-pr-${pr.number}`;
20732
20900
  await fetchPrHead(repoPath, pr.number, localFetchBranch);
20733
20901
  sourceRef = localFetchBranch;
@@ -20751,6 +20919,7 @@ async function createThread(input, _onSetupLine) {
20751
20919
  prUrl = existing.url;
20752
20920
  prTitle = existing.title;
20753
20921
  prAuthorLogin2 = existing.author?.login?.trim() || null;
20922
+ prLabels = existing.labels ?? [];
20754
20923
  }
20755
20924
  }
20756
20925
  }
@@ -20786,7 +20955,8 @@ async function createThread(input, _onSetupLine) {
20786
20955
  status: "idle",
20787
20956
  prUrl,
20788
20957
  prTitle,
20789
- prAuthorLogin: prAuthorLogin2
20958
+ prAuthorLogin: prAuthorLogin2,
20959
+ prLabels
20790
20960
  });
20791
20961
  writeThread(thread);
20792
20962
  await ensureWorkspace(repoPath);
@@ -24242,6 +24412,7 @@ var init_orchestrator = __esm({
24242
24412
  init_stack();
24243
24413
  init_agent_git_actions();
24244
24414
  init_pr_merge_archive();
24415
+ init_pr_gates();
24245
24416
  init_conductor();
24246
24417
  init_dev_preview();
24247
24418
  init_setup_log();
@@ -26438,10 +26609,20 @@ var init_orchestrator = __esm({
26438
26609
  return { thread, selectors: connectedPrSelectors(thread, headUrl), cwd, headUrl };
26439
26610
  }
26440
26611
  async getPrChecks(threadRef) {
26441
- const { selectors, cwd } = await this.withPrSelector(threadRef);
26612
+ const { thread, selectors, cwd } = await this.withPrSelector(threadRef);
26442
26613
  for (const selector of selectors) {
26443
26614
  const checks = await getPrChecks(cwd, selector);
26444
- 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;
26445
26626
  }
26446
26627
  return null;
26447
26628
  }
@@ -27114,6 +27295,7 @@ __export(index_exports, {
27114
27295
  PR_DETAILS_JSON_FIELDS: () => PR_DETAILS_JSON_FIELDS,
27115
27296
  REPO_REVIEW_NAME: () => REPO_REVIEW_NAME,
27116
27297
  REPO_REVIEW_PATH: () => REPO_REVIEW_PATH,
27298
+ REVIEW_LABEL_MAX: () => REVIEW_LABEL_MAX,
27117
27299
  REVIEW_REQUEST_NAME: () => REVIEW_REQUEST_NAME,
27118
27300
  REVIEW_REQUEST_PATH: () => REVIEW_REQUEST_PATH,
27119
27301
  REVIEW_REQUEST_PREFILL: () => REVIEW_REQUEST_PREFILL,
@@ -27245,6 +27427,7 @@ __export(index_exports, {
27245
27427
  clampHistoryMaxDays: () => clampHistoryMaxDays,
27246
27428
  classifyRunScriptPreview: () => classifyRunScriptPreview,
27247
27429
  classifyWorktreeColumn: () => classifyWorktreeColumn,
27430
+ classifyWorktreeListBadges: () => classifyWorktreeListBadges,
27248
27431
  claudeAdapter: () => claudeAdapter,
27249
27432
  claudeChromeEnabled: () => claudeChromeEnabled,
27250
27433
  claudeUsageOverLimitWindows: () => claudeUsageOverLimitWindows,
@@ -27685,6 +27868,7 @@ __export(index_exports, {
27685
27868
  normalizeCodeSelection: () => normalizeCodeSelection,
27686
27869
  normalizeLinearRelationType: () => normalizeLinearRelationType,
27687
27870
  normalizeParseResult: () => normalizeParseResult,
27871
+ normalizeReviewLabel: () => normalizeReviewLabel,
27688
27872
  normalizeServiceOrigin: () => normalizeServiceOrigin,
27689
27873
  normalizeThinkingEffort: () => normalizeThinkingEffort,
27690
27874
  normalizeThread: () => normalizeThread,
@@ -27812,6 +27996,7 @@ __export(index_exports, {
27812
27996
  resolvePlanMarkdown: () => resolvePlanMarkdown,
27813
27997
  resolvePrSelector: () => resolvePrSelector,
27814
27998
  resolvePrSelectors: () => resolvePrSelectors,
27999
+ resolveProjectReviewLabel: () => resolveProjectReviewLabel,
27815
28000
  resolveQuotaFallbackAgent: () => resolveQuotaFallbackAgent,
27816
28001
  resolveRepoRoot: () => resolveRepoRoot,
27817
28002
  resolveReviewGuidelines: () => resolveReviewGuidelines,
@@ -27826,6 +28011,7 @@ __export(index_exports, {
27826
28011
  resolveViewerProfile: () => resolveViewerProfile,
27827
28012
  resolveViewerProfileForRepo: () => resolveViewerProfileForRepo,
27828
28013
  resolveWorktreeStartPoint: () => resolveWorktreeStartPoint,
28014
+ reviewLabelForRepo: () => reviewLabelForRepo,
27829
28015
  rewriteAbleTimeError: () => rewriteAbleTimeError,
27830
28016
  rewriteLinearError: () => rewriteLinearError,
27831
28017
  run: () => run,
@@ -34385,12 +34571,12 @@ async function startMcpServer() {
34385
34571
  );
34386
34572
  server.tool(
34387
34573
  "list_prs",
34388
- '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.`,
34389
34575
  {
34390
34576
  repoPath: import_zod9.z.string(),
34391
34577
  query: import_zod9.z.string().optional().describe("GitHub search tokens (title, draft:true, \u2026)"),
34392
34578
  queue: import_zod9.z.enum(["review", "mine", "approved", "changes"]).optional().describe(
34393
- '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."
34394
34580
  ),
34395
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."),
34396
34582
  label: import_zod9.z.string().optional().describe(
@@ -34410,7 +34596,8 @@ async function startMcpServer() {
34410
34596
  state,
34411
34597
  labels: label,
34412
34598
  reviewer,
34413
- limit: page + 1
34599
+ limit: page + 1,
34600
+ reviewLabel: resolveProjectReviewLabel(loadAppSettings(), root)
34414
34601
  });
34415
34602
  const prs = await listPrs(root, resolved);
34416
34603
  const windowed = applyIssueListWindow(prs, page);
@@ -36944,6 +37131,7 @@ async function startSlackRelayServer(opts) {
36944
37131
  PR_DETAILS_JSON_FIELDS,
36945
37132
  REPO_REVIEW_NAME,
36946
37133
  REPO_REVIEW_PATH,
37134
+ REVIEW_LABEL_MAX,
36947
37135
  REVIEW_REQUEST_NAME,
36948
37136
  REVIEW_REQUEST_PATH,
36949
37137
  REVIEW_REQUEST_PREFILL,
@@ -37075,6 +37263,7 @@ async function startSlackRelayServer(opts) {
37075
37263
  clampHistoryMaxDays,
37076
37264
  classifyRunScriptPreview,
37077
37265
  classifyWorktreeColumn,
37266
+ classifyWorktreeListBadges,
37078
37267
  claudeAdapter,
37079
37268
  claudeChromeEnabled,
37080
37269
  claudeUsageOverLimitWindows,
@@ -37515,6 +37704,7 @@ async function startSlackRelayServer(opts) {
37515
37704
  normalizeCodeSelection,
37516
37705
  normalizeLinearRelationType,
37517
37706
  normalizeParseResult,
37707
+ normalizeReviewLabel,
37518
37708
  normalizeServiceOrigin,
37519
37709
  normalizeThinkingEffort,
37520
37710
  normalizeThread,
@@ -37642,6 +37832,7 @@ async function startSlackRelayServer(opts) {
37642
37832
  resolvePlanMarkdown,
37643
37833
  resolvePrSelector,
37644
37834
  resolvePrSelectors,
37835
+ resolveProjectReviewLabel,
37645
37836
  resolveQuotaFallbackAgent,
37646
37837
  resolveRepoRoot,
37647
37838
  resolveReviewGuidelines,
@@ -37656,6 +37847,7 @@ async function startSlackRelayServer(opts) {
37656
37847
  resolveViewerProfile,
37657
37848
  resolveViewerProfileForRepo,
37658
37849
  resolveWorktreeStartPoint,
37850
+ reviewLabelForRepo,
37659
37851
  rewriteAbleTimeError,
37660
37852
  rewriteLinearError,
37661
37853
  run,