@sideboard-ai/core 0.1.315 → 0.1.317

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-QQZWYJXC.js → abletime-NZ2ABWAL.js} +3 -3
  2. package/dist/{abletime-QK3KMRQD.js → abletime-TKAFNXCM.js} +3 -3
  3. package/dist/{abletime-oauth-CAHTMB4V.js → abletime-oauth-CBFFBINA.js} +2 -2
  4. package/dist/{abletime-oauth-I2RRPVKT.js → abletime-oauth-S4AXA4WR.js} +2 -2
  5. package/dist/{abletime-rest-5V4KGMSY.js → abletime-rest-NSOP72AI.js} +2 -2
  6. package/dist/{abletime-rest-W5TLQK7C.js → abletime-rest-ORBELZQO.js} +2 -2
  7. package/dist/{agents-Y3M3EKLY.js → agents-BJVSYC6Y.js} +4 -4
  8. package/dist/{agents-5XJ7EHGU.js → agents-KABJUDPI.js} +4 -4
  9. package/dist/{chunk-IODJCVMN.js → chunk-4XZJYBO2.js} +6 -34
  10. package/dist/{chunk-JRZTJ4F7.js → chunk-7SCVGGF6.js} +4 -4
  11. package/dist/{chunk-5CTG5W7S.js → chunk-A46CIARU.js} +1 -1
  12. package/dist/{chunk-PN32Z4JL.js → chunk-A6C67DY5.js} +2 -2
  13. package/dist/{chunk-CYPFOIUK.js → chunk-BXTISNKW.js} +16 -16
  14. package/dist/{chunk-7J2KOIYD.js → chunk-CCUGIVYN.js} +2 -2
  15. package/dist/{chunk-DV6ID6CK.js → chunk-ELBK34TY.js} +4 -4
  16. package/dist/{chunk-FVTR54LY.js → chunk-GH7UMULC.js} +4 -4
  17. package/dist/{chunk-MKGSMPWB.js → chunk-GXSV3X3L.js} +2 -2
  18. package/dist/{chunk-U35AHE2P.js → chunk-HOLB6ZFZ.js} +33 -2
  19. package/dist/{chunk-72OWKHFJ.js → chunk-I5CLCWUK.js} +2 -2
  20. package/dist/{chunk-YR7NZYUT.js → chunk-NRSC4N5X.js} +6 -34
  21. package/dist/{chunk-GFM63DTU.js → chunk-PX324N7P.js} +2 -2
  22. package/dist/{chunk-BMORGDYI.js → chunk-U53JICJ2.js} +18 -17
  23. package/dist/{chunk-NZVD5EVX.js → chunk-VVV7EI6T.js} +2 -1
  24. package/dist/{chunk-Y4WARKPW.js → chunk-VZAJVX36.js} +2 -1
  25. package/dist/{chunk-NKVNHBDB.js → chunk-XWUDFLBX.js} +3 -3
  26. package/dist/{chunk-CHUPIJP7.js → chunk-YEIHCSZS.js} +63 -2
  27. package/dist/{chunk-BPBDGJ43.js → chunk-YN6XONV5.js} +1 -1
  28. package/dist/{conductor-IMKNYQWZ.js → conductor-NIUBJLTS.js} +4 -4
  29. package/dist/{conductor-DKWTQADV.js → conductor-TP2NA44I.js} +4 -4
  30. package/dist/{coordinator-prompt-HUZC4DZ2.js → coordinator-prompt-3CT3D5UM.js} +3 -3
  31. package/dist/{coordinator-prompt-SPOYUZG5.js → coordinator-prompt-FQC2SZPP.js} +3 -3
  32. package/dist/{global-workspace-3NNDDTFD.js → global-workspace-72K6D354.js} +3 -3
  33. package/dist/{global-workspace-SK4JQBPN.js → global-workspace-BUWMRNW5.js} +3 -3
  34. package/dist/index.cjs +360 -42
  35. package/dist/index.d.cts +11 -5
  36. package/dist/index.d.ts +11 -5
  37. package/dist/index.js +301 -21
  38. package/dist/mcp/run-stdio.cjs +38 -34
  39. package/dist/mcp/run-stdio.js +9 -9
  40. package/dist/{orchestrator-4XFRK57E.js → orchestrator-E72HYGTB.js} +6 -6
  41. package/dist/{orchestrator-ZRMZ4TCL.js → orchestrator-N54V3B76.js} +6 -6
  42. package/dist/{thread-store-5OF6M6VZ.js → thread-store-7BBA63KV.js} +1 -1
  43. package/dist/{thread-store-GOVVXQV2.js → thread-store-PLNIWFZ4.js} +1 -1
  44. package/dist/{workspaces-EYNZDL45.js → workspaces-2V5UYPYC.js} +3 -3
  45. package/dist/{workspaces-52L6NZ3P.js → workspaces-GAUHXBUL.js} +3 -3
  46. package/dist/{worktree-BP5ICYQ2.js → worktree-6FUQ7FEF.js} +2 -2
  47. package/dist/{worktree-UCOSAI7J.js → worktree-XJKLDYOL.js} +2 -2
  48. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -4112,6 +4112,38 @@ var init_caffeinate_hold = __esm({
4112
4112
  }
4113
4113
  });
4114
4114
 
4115
+ // src/review/review-write-gate.ts
4116
+ function hasLiveReviewSibling(thread, siblings = []) {
4117
+ const selfId = thread.id;
4118
+ return siblings.some(
4119
+ (t) => t.id !== selfId && t.status !== "archived" && t.title.trim() === REVIEW_TAB_TITLE
4120
+ );
4121
+ }
4122
+ function isReviewWriteGatedThread(thread, siblings) {
4123
+ if (thread.sourceType === "pr" || thread.title.trim() === REVIEW_TAB_TITLE) {
4124
+ return true;
4125
+ }
4126
+ return hasLiveReviewSibling(thread, siblings);
4127
+ }
4128
+ function formatReviewWriteGateDirective() {
4129
+ return [
4130
+ "Review writes are public. The PR or ticket author sees GitHub/Linear/AbleTime comments, ticket updates, and submitted reviews immediately \u2014 a draft will confuse them.",
4131
+ "Write the recommendation in chat so the user can work through the feedback first. Do not call github_comment, linear_comment, abletime_comment, github_update_issue, linear_update_issue, abletime_update_task, or run `gh pr review` / `gh pr comment`, until they type a next step that asks you to post.",
4132
+ "After the review is in chat, stop. Do not call ask_user. The user needs time to read and will type the next steps. If they already asked you to post a specific comment or update in this turn, that is confirmation.",
4133
+ "Do not request reviewers, change labels, or assign yourself unless they asked \u2014 those also notify the author."
4134
+ ].join("\n");
4135
+ }
4136
+ function formatReviewWriteGateReminder() {
4137
+ return "Review writes: keep the review in chat. Do not ask_user after it \u2014 they read and type next steps. Do not github_comment / linear_comment / *_update_* / `gh pr review` on the PR or ticket until they ask. Authors see those immediately.";
4138
+ }
4139
+ var REVIEW_TAB_TITLE;
4140
+ var init_review_write_gate = __esm({
4141
+ "src/review/review-write-gate.ts"() {
4142
+ "use strict";
4143
+ REVIEW_TAB_TITLE = "Review";
4144
+ }
4145
+ });
4146
+
4115
4147
  // src/git/team-meta.ts
4116
4148
  var SOCCER_TEAM_META;
4117
4149
  var init_team_meta = __esm({
@@ -4983,11 +5015,12 @@ function branchDisplayLabel(branchName, worktreePath) {
4983
5015
  }
4984
5016
  function taskPurposeTitle(title) {
4985
5017
  const trimmed = title?.trim() ?? "";
4986
- if (!trimmed || teamSlugFromName(trimmed)) return null;
5018
+ if (!trimmed || teamSlugFromName(trimmed) || trimmed === REVIEW_TAB_TITLE) return null;
4987
5019
  return trimmed;
4988
5020
  }
4989
5021
  function threadDisplayLabel(thread) {
4990
- if (thread.userSetTitle && thread.title?.trim()) return thread.title.trim();
5022
+ const titled = thread.title?.trim() ?? "";
5023
+ if (thread.userSetTitle && titled && titled !== REVIEW_TAB_TITLE) return titled;
4991
5024
  if (thread.prTitle?.trim()) return thread.prTitle.trim();
4992
5025
  if (isPlaceholderBranch(thread.branchName, thread.worktreePath)) {
4993
5026
  const purpose = taskPurposeTitle(thread.title);
@@ -5003,6 +5036,35 @@ function worktreeDisplayLabelForGroup(threads) {
5003
5036
  const canonical = [...threads].sort((a, b) => a.createdAt.localeCompare(b.createdAt))[0];
5004
5037
  return threadDisplayLabel(canonical);
5005
5038
  }
5039
+ function worktreeAnchorLabel(threads) {
5040
+ if (threads.length === 0) return { title: "Worktree", subtitle: null };
5041
+ const canonical = [...threads].sort((a, b) => a.createdAt.localeCompare(b.createdAt))[0];
5042
+ const pr = threads.map((t) => t.prTitle?.trim()).find((v) => Boolean(v)) ?? "";
5043
+ const branch = (canonical.branchName ?? "").trim();
5044
+ const placeholder = isPlaceholderBranch(branch, canonical.worktreePath);
5045
+ const display = branchDisplayLabel(canonical.branchName, canonical.worktreePath);
5046
+ const realBranch = !placeholder && branch && branch !== "HEAD" ? branch : null;
5047
+ if (pr) {
5048
+ return {
5049
+ title: pr,
5050
+ subtitle: realBranch && realBranch !== pr ? realBranch : null
5051
+ };
5052
+ }
5053
+ if (placeholder) {
5054
+ const purpose = taskPurposeTitle(canonical.title);
5055
+ if (purpose) return { title: purpose, subtitle: null };
5056
+ }
5057
+ if (display) {
5058
+ return {
5059
+ title: display,
5060
+ subtitle: realBranch && realBranch !== display ? realBranch : null
5061
+ };
5062
+ }
5063
+ return {
5064
+ title: worktreeNameFromPath(canonical.worktreePath),
5065
+ subtitle: realBranch
5066
+ };
5067
+ }
5006
5068
  function agentNicknameFromWorktreePath(worktreePath) {
5007
5069
  const dir = worktreeNameFromPath(worktreePath);
5008
5070
  if (!soccerTokenFromTakenSlug(dir)) return null;
@@ -5053,6 +5115,7 @@ var WORKSPACE_TAG_LIMIT, WORKSPACE_TAG_MAX_LENGTH;
5053
5115
  var init_worktree_labels = __esm({
5054
5116
  "src/git/worktree-labels.ts"() {
5055
5117
  "use strict";
5118
+ init_review_write_gate();
5056
5119
  init_teams();
5057
5120
  WORKSPACE_TAG_LIMIT = 8;
5058
5121
  WORKSPACE_TAG_MAX_LENGTH = 32;
@@ -20775,6 +20838,7 @@ function worktreeBindingFrom(from) {
20775
20838
  prIsInMergeQueue: from.prIsInMergeQueue,
20776
20839
  prLabels: from.prLabels,
20777
20840
  prChecksFailed: from.prChecksFailed,
20841
+ issueBlockedBy: from.issueBlockedBy,
20778
20842
  skipAutoArchiveOnMerge: from.skipAutoArchiveOnMerge,
20779
20843
  stackId: from.stackId,
20780
20844
  stackLayer: from.stackLayer,
@@ -20886,38 +20950,6 @@ var init_chat_tabs = __esm({
20886
20950
  }
20887
20951
  });
20888
20952
 
20889
- // src/review/review-write-gate.ts
20890
- function hasLiveReviewSibling(thread, siblings = []) {
20891
- const selfId = thread.id;
20892
- return siblings.some(
20893
- (t) => t.id !== selfId && t.status !== "archived" && t.title.trim() === REVIEW_TAB_TITLE
20894
- );
20895
- }
20896
- function isReviewWriteGatedThread(thread, siblings) {
20897
- if (thread.sourceType === "pr" || thread.title.trim() === REVIEW_TAB_TITLE) {
20898
- return true;
20899
- }
20900
- return hasLiveReviewSibling(thread, siblings);
20901
- }
20902
- function formatReviewWriteGateDirective() {
20903
- return [
20904
- "Review writes are public. The PR or ticket author sees GitHub/Linear/AbleTime comments, ticket updates, and submitted reviews immediately \u2014 a draft will confuse them.",
20905
- "Write the recommendation in chat so the user can work through the feedback first. Do not call github_comment, linear_comment, abletime_comment, github_update_issue, linear_update_issue, abletime_update_task, or run `gh pr review` / `gh pr comment`, until they type a next step that asks you to post.",
20906
- "After the review is in chat, stop. Do not call ask_user. The user needs time to read and will type the next steps. If they already asked you to post a specific comment or update in this turn, that is confirmation.",
20907
- "Do not request reviewers, change labels, or assign yourself unless they asked \u2014 those also notify the author."
20908
- ].join("\n");
20909
- }
20910
- function formatReviewWriteGateReminder() {
20911
- return "Review writes: keep the review in chat. Do not ask_user after it \u2014 they read and type next steps. Do not github_comment / linear_comment / *_update_* / `gh pr review` on the PR or ticket until they ask. Authors see those immediately.";
20912
- }
20913
- var REVIEW_TAB_TITLE;
20914
- var init_review_write_gate = __esm({
20915
- "src/review/review-write-gate.ts"() {
20916
- "use strict";
20917
- REVIEW_TAB_TITLE = "Review";
20918
- }
20919
- });
20920
-
20921
20953
  // src/review/review-request-template.ts
20922
20954
  function wrapReviewSkillMarkdown(body) {
20923
20955
  const trimmed = body.replace(/^\uFEFF/, "").trim();
@@ -23916,6 +23948,7 @@ function restTaskToRaw(raw, users) {
23916
23948
  projectId: asString(rec.projectId),
23917
23949
  categoryId: asString(rec.projectCategoryId) || asString(rec.categoryId),
23918
23950
  parentId: asString(rec.parentTaskId) || asString(rec.parentId),
23951
+ depends_on: rec.dependencyParentTimeflowTaskId ?? null,
23919
23952
  tags: rec.tags,
23920
23953
  comments,
23921
23954
  attachments: rec.attachments ?? rec.files ?? rec.media,
@@ -33550,6 +33583,7 @@ __export(index_exports, {
33550
33583
  recordSlackOutboundWatch: () => recordSlackOutboundWatch,
33551
33584
  refreshBrightsyAccessToken: () => refreshBrightsyAccessToken,
33552
33585
  refreshGitHubAuth: () => refreshGitHubAuth,
33586
+ refreshWorktreeIssueBlocked: () => refreshWorktreeIssueBlocked,
33553
33587
  registerBrightsyOAuthClient: () => registerBrightsyOAuthClient,
33554
33588
  registerPackagedUserMcpClients: () => registerPackagedUserMcpClients,
33555
33589
  releaseCaffeinateHoldForThread: () => releaseCaffeinateHoldForThread,
@@ -35491,15 +35525,13 @@ function listPhoneSidebar(viewerLogin = phoneViewerLogin()) {
35491
35525
  path: path2,
35492
35526
  worktrees: groupHomeBoardWorktrees(repoThreads).map((group) => {
35493
35527
  const tags = workspaceTagsFromGroup(group);
35494
- const badge = classifyWorktreeListBadges(
35495
- group,
35496
- viewerLogin,
35497
- resolveProjectReviewLabel(settings, path2)
35498
- )[0];
35528
+ const badge = classifyWorktreeListBadges(group, viewerLogin, resolveProjectReviewLabel(settings, path2))[0];
35529
+ const blockedBy = issueBlockedByIds(group);
35499
35530
  return {
35500
- label: worktreeDisplayLabelForGroup(group),
35531
+ label: worktreeAnchorLabel(group).title,
35501
35532
  chats: group.map(summarize),
35502
35533
  ...tags.length > 0 ? { tags } : {},
35534
+ ...blockedBy.length > 0 ? { blockedBy } : {},
35503
35535
  ...badge ? { badge: { id: badge.id, label: badge.label, title: badge.title, ...badge.conflict ? { conflict: true } : {} } } : {}
35504
35536
  };
35505
35537
  })
@@ -35576,7 +35608,7 @@ function projectPlace(thread) {
35576
35608
  return {
35577
35609
  kind: "project",
35578
35610
  repoPath: thread.repoPath,
35579
- worktree: worktreeDisplayLabelForGroup(group.length > 0 ? group : [thread])
35611
+ worktree: worktreeAnchorLabel(group.length > 0 ? group : [thread]).title
35580
35612
  };
35581
35613
  }
35582
35614
  function assertKnownProject(repoPath) {
@@ -38923,7 +38955,7 @@ function phoneBoardKey(sidebar) {
38923
38955
  const orchestration = sidebar.orchestration.map(chatKey).join("\n");
38924
38956
  const projects = sidebar.projects.map((project) => {
38925
38957
  const trees = project.worktrees.map(
38926
- (tree) => `${tree.label} ${tree.badge?.id ?? ""} ${(tree.tags ?? []).join(",")} ${tree.chats.map(chatKey).join("\n")}`
38958
+ (tree) => `${tree.label} ${tree.badge?.id ?? ""} ${(tree.tags ?? []).join(",")} ${(tree.blockedBy ?? []).join(",")} ${tree.chats.map(chatKey).join("\n")}`
38927
38959
  ).join("\n");
38928
38960
  return `${project.path} ${project.name}
38929
38961
  ${trees}`;
@@ -45511,6 +45543,291 @@ init_pilot_tools();
45511
45543
  // src/index.ts
45512
45544
  init_profile();
45513
45545
  init_home_board();
45546
+
45547
+ // src/integrations/abletime-blocked.ts
45548
+ init_abletime_mcp();
45549
+ init_abletime_rest();
45550
+ function asRecord18(value) {
45551
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
45552
+ }
45553
+ function asString5(value) {
45554
+ return typeof value === "string" ? value.trim() : "";
45555
+ }
45556
+ function firstString5(record, keys) {
45557
+ if (!record) return "";
45558
+ for (const key of keys) {
45559
+ const value = asString5(record[key]);
45560
+ if (value) return value;
45561
+ }
45562
+ return "";
45563
+ }
45564
+ function firstRecord2(record, keys) {
45565
+ if (!record) return null;
45566
+ for (const key of keys) {
45567
+ const value = asRecord18(record[key]);
45568
+ if (value) return value;
45569
+ }
45570
+ return null;
45571
+ }
45572
+ function identifierFromUnknown(value) {
45573
+ if (typeof value === "string" || typeof value === "number") {
45574
+ const id2 = String(value).trim();
45575
+ return id2 ? [id2] : [];
45576
+ }
45577
+ if (Array.isArray(value)) return value.flatMap(identifierFromUnknown);
45578
+ const rec = asRecord18(value);
45579
+ if (!rec) return [];
45580
+ for (const key of ["nodes", "data", "tasks", "items"]) {
45581
+ if (Array.isArray(rec[key])) return identifierFromUnknown(rec[key]);
45582
+ }
45583
+ const id = firstString5(rec, ["identifier", "reference", "ref", "key", "code", "number", "id"]);
45584
+ return id ? [id] : [];
45585
+ }
45586
+ function ableTimeBlockedByIdentifiers(raw) {
45587
+ const record = asRecord18(raw);
45588
+ const nested = firstRecord2(record, ["task", "data"]) ?? record;
45589
+ if (!nested) return [];
45590
+ const value = nested.blocked_by ?? nested.blockedBy ?? nested.depends_on ?? nested.dependsOn ?? nested.dependencyParentTimeflowTaskId ?? nested.wait_on ?? nested.waitOn;
45591
+ const out = [];
45592
+ const seen = /* @__PURE__ */ new Set();
45593
+ for (const id of identifierFromUnknown(value)) {
45594
+ const key = id.toLowerCase();
45595
+ if (seen.has(key)) continue;
45596
+ seen.add(key);
45597
+ out.push(id);
45598
+ }
45599
+ return out;
45600
+ }
45601
+ function taskReference(raw) {
45602
+ const record = asRecord18(raw);
45603
+ const nested = firstRecord2(record, ["task", "data"]) ?? record;
45604
+ return firstString5(nested, ["taskRef", "reference", "identifier"]);
45605
+ }
45606
+ async function getAbleTimeIssueBlockedBy(id, opts) {
45607
+ const taskId = id.trim();
45608
+ if (!taskId) throw new Error("AbleTime task id is required");
45609
+ const raw = await callAbleTimeTool("get_task", { id: taskId, task_id: taskId }, opts);
45610
+ const out = [];
45611
+ for (const blockerId of ableTimeBlockedByIdentifiers(raw)) {
45612
+ if (!looksLikeAbleTimeUlid(blockerId)) {
45613
+ out.push(blockerId);
45614
+ continue;
45615
+ }
45616
+ const parent = await callAbleTimeTool("get_task", { id: blockerId, task_id: blockerId }, opts);
45617
+ const ref = taskReference(parent);
45618
+ if (!ref || looksLikeAbleTimeUlid(ref)) {
45619
+ throw new Error(`AbleTime blocker has no reference: ${blockerId}`);
45620
+ }
45621
+ out.push(ref);
45622
+ }
45623
+ return out;
45624
+ }
45625
+
45626
+ // src/integrations/github-blocked.ts
45627
+ init_run();
45628
+ function githubBlockedByIdentifiers(payload) {
45629
+ const root = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : null;
45630
+ const data = root?.data && typeof root.data === "object" && !Array.isArray(root.data) ? root.data : root;
45631
+ const repository = data?.repository && typeof data.repository === "object" ? data.repository : null;
45632
+ const issue = repository?.issue && typeof repository.issue === "object" ? repository.issue : null;
45633
+ const blocked = issue?.blockedBy && typeof issue.blockedBy === "object" ? issue.blockedBy : null;
45634
+ const nodes = Array.isArray(blocked?.nodes) ? blocked.nodes : Array.isArray(root?.blockedBy) ? root.blockedBy : [];
45635
+ const out = [];
45636
+ const seen = /* @__PURE__ */ new Set();
45637
+ for (const node of nodes) {
45638
+ const rec = node && typeof node === "object" && !Array.isArray(node) ? node : null;
45639
+ const number = Number(rec?.number ?? node);
45640
+ if (!Number.isFinite(number) || number <= 0) continue;
45641
+ const id = `#${number}`;
45642
+ const key = id.toLowerCase();
45643
+ if (seen.has(key)) continue;
45644
+ seen.add(key);
45645
+ out.push(id);
45646
+ }
45647
+ return out;
45648
+ }
45649
+ var GITHUB_BLOCKED_BY_QUERY = `
45650
+ query($owner: String!, $name: String!, $number: Int!) {
45651
+ repository(owner: $owner, name: $name) {
45652
+ issue(number: $number) {
45653
+ blockedBy(first: 25) { nodes { number } }
45654
+ }
45655
+ }
45656
+ }
45657
+ `;
45658
+ async function getGitHubIssueBlockedBy(id, opts) {
45659
+ const number = parseGitHubIssueNumber(id);
45660
+ const { cwd, slug } = await resolveGitHubIssueRepo(opts?.repoPath);
45661
+ const [owner, name] = (slug ?? "").split("/");
45662
+ if (!owner || !name) throw new Error("GitHub repo is not connected");
45663
+ const result = await gh(
45664
+ [
45665
+ "api",
45666
+ "graphql",
45667
+ "-f",
45668
+ `query=${GITHUB_BLOCKED_BY_QUERY}`,
45669
+ "-f",
45670
+ `owner=${owner}`,
45671
+ "-f",
45672
+ `name=${name}`,
45673
+ "-F",
45674
+ `number=${number}`
45675
+ ],
45676
+ cwd,
45677
+ { reject: false }
45678
+ );
45679
+ if (result.exitCode !== 0) {
45680
+ const detail = (result.stderr || result.stdout).trim() || "gh failed";
45681
+ throw new Error(`GitHub issue #${number} blocked-by: ${detail}`);
45682
+ }
45683
+ let parsed;
45684
+ try {
45685
+ parsed = JSON.parse(result.stdout);
45686
+ } catch {
45687
+ throw new Error(`GitHub issue #${number} blocked-by: gh returned non-JSON`);
45688
+ }
45689
+ const errors = parsed && typeof parsed === "object" && Array.isArray(parsed.errors) ? parsed.errors : [];
45690
+ if (errors.length > 0) {
45691
+ throw new Error(
45692
+ `GitHub issue #${number} blocked-by: ${errors.map((e) => e.message ?? "GitHub error").join("; ")}`
45693
+ );
45694
+ }
45695
+ const issue = parsed.data?.repository?.issue;
45696
+ if (!issue) throw new Error(`GitHub issue not found: #${number}`);
45697
+ return githubBlockedByIdentifiers(parsed);
45698
+ }
45699
+
45700
+ // src/integrations/linear-blocked.ts
45701
+ var ISSUE_BLOCKED_BY_QUERY = `
45702
+ query SideboardIssueBlockedBy($id: String!) {
45703
+ issue(id: $id) {
45704
+ identifier
45705
+ relations(first: 25) {
45706
+ nodes { id type relatedIssue { id identifier } }
45707
+ }
45708
+ inverseRelations(first: 25) {
45709
+ nodes { id type issue { id identifier } }
45710
+ }
45711
+ }
45712
+ }
45713
+ `;
45714
+ function linearBlockedByIdentifiers(relations) {
45715
+ const out = [];
45716
+ const seen = /* @__PURE__ */ new Set();
45717
+ for (const rel of relations) {
45718
+ const type = (rel.type ?? "").trim().toLowerCase().replace(/[\s_-]+/g, "");
45719
+ if (type !== "blockedby" && type !== "blocked") continue;
45720
+ const id = (rel.issue?.identifier || rel.issue?.id || "").trim();
45721
+ const key = id.toLowerCase();
45722
+ if (!id || seen.has(key)) continue;
45723
+ seen.add(key);
45724
+ out.push(id);
45725
+ }
45726
+ return out;
45727
+ }
45728
+ function linearBlockedByRelations(issue) {
45729
+ const out = [];
45730
+ for (const rel of issue.relations?.nodes ?? []) {
45731
+ out.push({ type: String(rel.type ?? "related"), issue: rel.relatedIssue });
45732
+ }
45733
+ for (const rel of issue.inverseRelations?.nodes ?? []) {
45734
+ out.push({
45735
+ type: flipLinearRelationType(String(rel.type ?? "related")),
45736
+ issue: rel.issue
45737
+ });
45738
+ }
45739
+ return out;
45740
+ }
45741
+ async function getLinearIssueBlockedBy(id, opts) {
45742
+ const issueId = id.trim();
45743
+ if (!issueId) throw new Error("Linear issue id is required (uuid or ENG-123)");
45744
+ const json = await linearGraphql(
45745
+ ISSUE_BLOCKED_BY_QUERY,
45746
+ { id: issueId },
45747
+ opts
45748
+ );
45749
+ if (!json.issue) throw new Error(`Linear issue not found: ${issueId}`);
45750
+ return linearBlockedByIdentifiers(linearBlockedByRelations(json.issue));
45751
+ }
45752
+
45753
+ // src/board/issue-blocked-sync.ts
45754
+ init_app_settings();
45755
+ init_thread_store();
45756
+ init_chat_tabs();
45757
+ function sourceTicketRef(thread) {
45758
+ if (thread.sourceType !== "ticket") return null;
45759
+ const ref = (thread.sourceRef ?? "").trim();
45760
+ return ref || null;
45761
+ }
45762
+ function sameIssueBlockedBy(current, next) {
45763
+ const left = new Set(
45764
+ (current ?? []).map((id) => id.trim().toLowerCase()).filter(Boolean)
45765
+ );
45766
+ const right = [...new Set(next.map((id) => id.trim().toLowerCase()).filter(Boolean))];
45767
+ if (left.size !== right.length) return false;
45768
+ return right.every((id) => left.has(id));
45769
+ }
45770
+ var TRACKER_KEY = /^[A-Za-z][A-Za-z0-9]+-\d+$/;
45771
+ function isGitHubIssueRef(ref) {
45772
+ return /^#?\d+$/.test(ref) || /^gh-\d+$/i.test(ref) || /github\.com\/[^/]+\/[^/]+\/issues\/\d+/i.test(ref);
45773
+ }
45774
+ function trackerForSourceTicket(ref, preferred, connected) {
45775
+ const trimmed = ref.trim();
45776
+ if (!trimmed) return null;
45777
+ if (isGitHubIssueRef(trimmed)) return "github";
45778
+ if (TRACKER_KEY.test(trimmed)) {
45779
+ if (preferred === "abletime" && connected.abletime) return "abletime";
45780
+ if (connected.linear) return "linear";
45781
+ if (connected.abletime) return "abletime";
45782
+ return null;
45783
+ }
45784
+ if (preferred === "linear" && connected.linear) return "linear";
45785
+ if (preferred === "abletime" && connected.abletime) return "abletime";
45786
+ if (preferred === "github") return "github";
45787
+ return null;
45788
+ }
45789
+ function defaultIssueBlockedLookup() {
45790
+ return {
45791
+ preferredSource: () => resolveEffectiveIssueSource(),
45792
+ linearConnected: () => isLinearConnected(),
45793
+ abletimeConnected: () => isAbleTimeConnected(),
45794
+ linear: (id) => getLinearIssueBlockedBy(id),
45795
+ github: (id, repoPath) => getGitHubIssueBlockedBy(id, { repoPath }),
45796
+ abletime: (id) => getAbleTimeIssueBlockedBy(id)
45797
+ };
45798
+ }
45799
+ async function lookupIssueBlockedBy(thread, lookup = defaultIssueBlockedLookup()) {
45800
+ const ref = sourceTicketRef(thread);
45801
+ if (!ref) return null;
45802
+ const tracker = trackerForSourceTicket(ref, lookup.preferredSource(), {
45803
+ linear: lookup.linearConnected(),
45804
+ abletime: lookup.abletimeConnected()
45805
+ });
45806
+ if (!tracker) return null;
45807
+ try {
45808
+ if (tracker === "linear") return await lookup.linear(ref);
45809
+ if (tracker === "abletime") return await lookup.abletime(ref);
45810
+ const repo = (thread.repoPath || thread.worktreePath || "").trim();
45811
+ return await lookup.github(ref, repo);
45812
+ } catch {
45813
+ return null;
45814
+ }
45815
+ }
45816
+ async function refreshWorktreeIssueBlocked(threadRef) {
45817
+ const thread = readThread(threadRef) ?? findThreadByRef(threadRef);
45818
+ if (!thread) throw new Error(`Thread not found: ${threadRef}`);
45819
+ const ids = await lookupIssueBlockedBy(thread);
45820
+ if (ids == null) return null;
45821
+ const siblings = thread.worktreePath ? threadsSharingWorktree(thread.worktreePath) : [];
45822
+ const targets = siblings.length > 0 ? siblings : [thread];
45823
+ for (const t of targets) {
45824
+ if (sameIssueBlockedBy(t.issueBlockedBy, ids)) continue;
45825
+ updateThread(t.id, { issueBlockedBy: ids });
45826
+ }
45827
+ return ids;
45828
+ }
45829
+
45830
+ // src/index.ts
45514
45831
  init_config();
45515
45832
 
45516
45833
  // src/brightsy/api.ts
@@ -48268,6 +48585,7 @@ async function pollSlackOutboundWatches(opts) {
48268
48585
  recordSlackOutboundWatch,
48269
48586
  refreshBrightsyAccessToken,
48270
48587
  refreshGitHubAuth,
48588
+ refreshWorktreeIssueBlocked,
48271
48589
  registerBrightsyOAuthClient,
48272
48590
  registerPackagedUserMcpClients,
48273
48591
  releaseCaffeinateHoldForThread,