@sideboard-ai/core 0.1.184 → 0.1.185

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.
@@ -467,7 +467,7 @@ async function continueSourceThread(threadId, prompt) {
467
467
  await continueOnReply(threadId, prompt);
468
468
  return;
469
469
  }
470
- const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-DKVX67MH.js");
470
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-SVFF5CLQ.js");
471
471
  await getOrchestrator2().send(threadId, prompt);
472
472
  } catch {
473
473
  }
@@ -3193,7 +3193,7 @@ function classifyWorktreeOwnership(group, viewerLogin = "") {
3193
3193
  const reviewing = group.some((t) => {
3194
3194
  const author = normalizeViewerLogin(t.prAuthorLogin);
3195
3195
  if (author && me && author !== me) return true;
3196
- if (t.sourceType === "pr" && (!author || !me || author !== me)) return true;
3196
+ if (t.sourceType === "pr" && me && (!author || author !== me)) return true;
3197
3197
  if (me && loginsInclude(t.prReviewerLogins, me)) return true;
3198
3198
  return false;
3199
3199
  });
@@ -6766,7 +6766,7 @@ function formatScheduledPrompt(name, prompt) {
6766
6766
  ${prompt}`;
6767
6767
  }
6768
6768
  async function defaultDeps() {
6769
- const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-DKVX67MH.js");
6769
+ const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-SVFF5CLQ.js");
6770
6770
  const orch = getOrchestrator2();
6771
6771
  return {
6772
6772
  findThread: (id) => findThreadByRef(id),
@@ -402,7 +402,7 @@ async function continueSourceThread(threadId, prompt) {
402
402
  await continueOnReply(threadId, prompt);
403
403
  return;
404
404
  }
405
- const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-4GLAG7YH.js");
405
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-45PPV2LQ.js");
406
406
  await getOrchestrator2().send(threadId, prompt);
407
407
  } catch {
408
408
  }
@@ -3101,7 +3101,7 @@ function classifyWorktreeOwnership(group, viewerLogin = "") {
3101
3101
  const reviewing = group.some((t) => {
3102
3102
  const author = normalizeViewerLogin(t.prAuthorLogin);
3103
3103
  if (author && me && author !== me) return true;
3104
- if (t.sourceType === "pr" && (!author || !me || author !== me)) return true;
3104
+ if (t.sourceType === "pr" && me && (!author || author !== me)) return true;
3105
3105
  if (me && loginsInclude(t.prReviewerLogins, me)) return true;
3106
3106
  return false;
3107
3107
  });
@@ -6419,7 +6419,7 @@ function formatScheduledPrompt(name, prompt) {
6419
6419
  ${prompt}`;
6420
6420
  }
6421
6421
  async function defaultDeps() {
6422
- const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-4GLAG7YH.js");
6422
+ const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-45PPV2LQ.js");
6423
6423
  const orch = getOrchestrator2();
6424
6424
  return {
6425
6425
  findThread: (id) => findThreadByRef(id),
package/dist/index.cjs CHANGED
@@ -16349,7 +16349,7 @@ function classifyWorktreeOwnership(group, viewerLogin = "") {
16349
16349
  const reviewing = group.some((t) => {
16350
16350
  const author = normalizeViewerLogin(t.prAuthorLogin);
16351
16351
  if (author && me && author !== me) return true;
16352
- if (t.sourceType === "pr" && (!author || !me || author !== me)) return true;
16352
+ if (t.sourceType === "pr" && me && (!author || author !== me)) return true;
16353
16353
  if (me && loginsInclude(t.prReviewerLogins, me)) return true;
16354
16354
  return false;
16355
16355
  });
@@ -24622,12 +24622,27 @@ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
24622
24622
  function assigneeFilterKey(assignee) {
24623
24623
  return (assignee ?? "").trim();
24624
24624
  }
24625
+ function applyLinearIssueQueryFilter(filter, query) {
24626
+ const term = query?.trim();
24627
+ if (!term) return;
24628
+ const or = [
24629
+ { title: { containsIgnoreCase: term } },
24630
+ { description: { containsIgnoreCase: term } }
24631
+ ];
24632
+ const identifierMatch = term.match(/^[A-Za-z][\w]*-(\d+)$/);
24633
+ const asNumber = identifierMatch ? Number(identifierMatch[1]) : /^\d+$/.test(term) ? Number(term) : NaN;
24634
+ if (Number.isFinite(asNumber) && asNumber > 0) {
24635
+ or.push({ number: { eq: asNumber } });
24636
+ }
24637
+ filter.or = or;
24638
+ }
24625
24639
  function buildLinearIssueFilter(input) {
24626
24640
  const filter = {
24627
24641
  state: { type: { nin: ["completed", "canceled"] } }
24628
24642
  };
24629
24643
  const since = input?.updatedSince?.trim();
24630
24644
  if (since) filter.updatedAt = { gte: since };
24645
+ applyLinearIssueQueryFilter(filter, input?.query);
24631
24646
  const raw = assigneeFilterKey(input?.assignee) || "me";
24632
24647
  const key = raw.toLowerCase();
24633
24648
  if (key === "all" || key === "*") return filter;
@@ -24693,6 +24708,11 @@ async function listLinearIssuesFiltered(opts) {
24693
24708
  async function listLinearCommentsSince(opts) {
24694
24709
  const first = Math.max(1, Math.min(250, opts.limit ?? 200));
24695
24710
  const query = opts.query?.trim() ?? "";
24711
+ const restrictToIssues = opts.issueIdentifiers != null;
24712
+ const allowed = new Set(
24713
+ (opts.issueIdentifiers ?? []).map((id) => id.trim()).filter(Boolean)
24714
+ );
24715
+ if (restrictToIssues && allowed.size === 0) return [];
24696
24716
  const assignee = assigneeFilterKey(opts.assignee) || (query ? "all" : "me");
24697
24717
  const json = await linearGraphql(
24698
24718
  COMMENTS_SINCE_QUERY,
@@ -24700,7 +24720,7 @@ async function listLinearCommentsSince(opts) {
24700
24720
  first,
24701
24721
  filter: {
24702
24722
  createdAt: { gte: opts.since },
24703
- issue: buildLinearIssueFilter({ assignee })
24723
+ issue: buildLinearIssueFilter({ assignee, query: query || void 0 })
24704
24724
  }
24705
24725
  },
24706
24726
  opts
@@ -24708,6 +24728,7 @@ async function listLinearCommentsSince(opts) {
24708
24728
  const out = [];
24709
24729
  for (const node of json.comments?.nodes ?? []) {
24710
24730
  const identifier = String(node.issue?.identifier ?? "").trim();
24731
+ if (restrictToIssues && !allowed.has(identifier)) continue;
24711
24732
  const body = previewIssueCommentBody(String(node.body ?? ""));
24712
24733
  if (!identifier && !body) continue;
24713
24734
  out.push({
@@ -24939,10 +24960,32 @@ function issueNumberFromApiUrl(url) {
24939
24960
  const number = Number(match[1]);
24940
24961
  return Number.isFinite(number) && number > 0 ? number : null;
24941
24962
  }
24963
+ var GITHUB_ISSUE_COMMENTS_PAGE_SIZE = 100;
24964
+ var GITHUB_ISSUE_COMMENTS_MAX_PAGES = 5;
24965
+ function parseGitHubIssueCommentNode(item, allowed) {
24966
+ const rec = item && typeof item === "object" ? item : null;
24967
+ if (!rec) return null;
24968
+ const issueUrl = typeof rec.issue_url === "string" ? rec.issue_url : "";
24969
+ const number = issueNumberFromApiUrl(issueUrl);
24970
+ if (number == null) return null;
24971
+ if (allowed && !allowed.has(number)) return null;
24972
+ const body = previewIssueCommentBody(typeof rec.body === "string" ? rec.body : "");
24973
+ const user = rec.user && typeof rec.user === "object" ? String(rec.user.login ?? "").trim() : "";
24974
+ const createdAt = typeof rec.created_at === "string" ? rec.created_at : typeof rec.createdAt === "string" ? rec.createdAt : void 0;
24975
+ const url = typeof rec.html_url === "string" ? rec.html_url : void 0;
24976
+ return {
24977
+ identifier: `#${number}`,
24978
+ ...user ? { author: user } : {},
24979
+ ...createdAt ? { createdAt } : {},
24980
+ body,
24981
+ ...url ? { url } : {}
24982
+ };
24983
+ }
24942
24984
  async function listGitHubIssueCommentsSince(opts) {
24943
24985
  const limit = Math.max(1, Math.min(250, opts.limit ?? 200));
24944
24986
  const { cwd, slug } = await resolveGitHubIssueRepo(opts.repoPath);
24945
24987
  if (!slug) return [];
24988
+ const restrictToIssues = opts.issueIdentifiers != null;
24946
24989
  const allowed = new Set(
24947
24990
  (opts.issueIdentifiers ?? []).map((id) => {
24948
24991
  try {
@@ -24952,36 +24995,27 @@ async function listGitHubIssueCommentsSince(opts) {
24952
24995
  }
24953
24996
  }).filter((n) => n != null)
24954
24997
  );
24955
- const path2 = `repos/${slug}/issues/comments?since=${encodeURIComponent(opts.since)}&per_page=${limit + 1}`;
24956
- const result = await gh(["api", path2], cwd, { reject: false });
24957
- if (result.exitCode !== 0 || !result.stdout.trim()) return [];
24958
- let parsed;
24959
- try {
24960
- parsed = JSON.parse(result.stdout);
24961
- } catch {
24962
- return [];
24963
- }
24964
- if (!Array.isArray(parsed)) return [];
24998
+ if (restrictToIssues && allowed.size === 0) return [];
24999
+ const pageSize = Math.min(GITHUB_ISSUE_COMMENTS_PAGE_SIZE, Math.max(1, limit));
24965
25000
  const out = [];
24966
- for (const item of parsed) {
24967
- const rec = item && typeof item === "object" ? item : null;
24968
- if (!rec) continue;
24969
- const issueUrl = typeof rec.issue_url === "string" ? rec.issue_url : "";
24970
- const number = issueNumberFromApiUrl(issueUrl);
24971
- if (number == null) continue;
24972
- if (allowed.size > 0 && !allowed.has(number)) continue;
24973
- const body = previewIssueCommentBody(typeof rec.body === "string" ? rec.body : "");
24974
- const user = rec.user && typeof rec.user === "object" ? String(rec.user.login ?? "").trim() : "";
24975
- const createdAt = typeof rec.created_at === "string" ? rec.created_at : typeof rec.createdAt === "string" ? rec.createdAt : void 0;
24976
- const url = typeof rec.html_url === "string" ? rec.html_url : void 0;
24977
- out.push({
24978
- identifier: `#${number}`,
24979
- ...user ? { author: user } : {},
24980
- ...createdAt ? { createdAt } : {},
24981
- body,
24982
- ...url ? { url } : {}
24983
- });
24984
- if (out.length >= limit) break;
25001
+ for (let page = 1; page <= GITHUB_ISSUE_COMMENTS_MAX_PAGES; page++) {
25002
+ const path2 = `repos/${slug}/issues/comments?since=${encodeURIComponent(opts.since)}&sort=created&direction=desc&per_page=${pageSize}&page=${page}`;
25003
+ const result = await gh(["api", path2], cwd, { reject: false });
25004
+ if (result.exitCode !== 0 || !result.stdout.trim()) break;
25005
+ let parsed;
25006
+ try {
25007
+ parsed = JSON.parse(result.stdout);
25008
+ } catch {
25009
+ break;
25010
+ }
25011
+ if (!Array.isArray(parsed) || parsed.length === 0) break;
25012
+ for (const item of parsed) {
25013
+ const comment = parseGitHubIssueCommentNode(item, restrictToIssues ? allowed : null);
25014
+ if (!comment) continue;
25015
+ out.push(comment);
25016
+ if (out.length >= limit) return out;
25017
+ }
25018
+ if (parsed.length < pageSize) break;
24985
25019
  }
24986
25020
  return out;
24987
25021
  }
@@ -25195,20 +25229,19 @@ async function listIssues(repoPath, opts) {
25195
25229
  const assignee = opts?.assignee?.trim() || void 0;
25196
25230
  const since = opts?.updatedSince ? parseIssueSince(opts.updatedSince) : void 0;
25197
25231
  if (source === "linear") {
25198
- const [listed, comments2] = await Promise.all([
25199
- listLinearIssuesFiltered({
25200
- assignee,
25201
- query,
25202
- limit: opts?.limit,
25203
- updatedSince: since
25204
- }),
25205
- since ? listLinearCommentsSince({
25206
- since,
25207
- assignee,
25208
- query,
25209
- limit: opts?.limit
25210
- }) : Promise.resolve(void 0)
25211
- ]);
25232
+ const listed = await listLinearIssuesFiltered({
25233
+ assignee,
25234
+ query,
25235
+ limit: opts?.limit,
25236
+ updatedSince: since
25237
+ });
25238
+ const comments2 = since ? await listLinearCommentsSince({
25239
+ since,
25240
+ assignee,
25241
+ query,
25242
+ issueIdentifiers: query ? listed.issues.map((issue) => issue.identifier) : void 0,
25243
+ limit: opts?.limit
25244
+ }) : void 0;
25212
25245
  return {
25213
25246
  source,
25214
25247
  preferredSource,
@@ -26955,20 +26988,19 @@ function registerLinearTools(server) {
26955
26988
  try {
26956
26989
  const page = clampMcpIssueLimit(limit);
26957
26990
  const since = updatedSince ? parseIssueSince(updatedSince) : void 0;
26958
- const [listed, comments] = await Promise.all([
26959
- listLinearIssuesFiltered({
26960
- query,
26961
- assignee,
26962
- limit: page + 1,
26963
- updatedSince: since
26964
- }),
26965
- since ? listLinearCommentsSince({
26966
- since,
26967
- assignee,
26968
- query,
26969
- limit: page + 1
26970
- }) : Promise.resolve(void 0)
26971
- ]);
26991
+ const listed = await listLinearIssuesFiltered({
26992
+ query,
26993
+ assignee,
26994
+ limit: page + 1,
26995
+ updatedSince: since
26996
+ });
26997
+ const comments = since ? await listLinearCommentsSince({
26998
+ since,
26999
+ assignee,
27000
+ query,
27001
+ issueIdentifiers: query ? listed.issues.map((issue) => issue.identifier) : void 0,
27002
+ limit: page + 1
27003
+ }) : void 0;
26972
27004
  return mcpJson(
26973
27005
  formatListedIssuesForMcp(
26974
27006
  {
package/dist/index.d.cts CHANGED
@@ -2213,13 +2213,10 @@ type LinearAssignedIssuesResult = {
2213
2213
  };
2214
2214
  /** `me`, `unassigned`, `all`, a Linear user id, or a display name. */
2215
2215
  type LinearAssigneeFilter = string;
2216
- /**
2217
- * Linear `IssueFilter` for open issues. Default assignee is `me` so existing
2218
- * Home / Create-from callers stay assigned-to-you unless they ask otherwise.
2219
- */
2220
2216
  declare function buildLinearIssueFilter(input?: {
2221
2217
  assignee?: string | null;
2222
2218
  updatedSince?: string | null;
2219
+ query?: string | null;
2223
2220
  }): Record<string, unknown>;
2224
2221
  /**
2225
2222
  * List or search open Linear issues. `assignee` is `me` (default when there is
@@ -2234,13 +2231,16 @@ declare function listLinearIssuesFiltered(opts?: {
2234
2231
  updatedSince?: string | null;
2235
2232
  }): Promise<LinearAssignedIssuesResult>;
2236
2233
  /**
2237
- * Comments created at/after `since` on issues matching the same assignee filter.
2238
- * One GraphQL query — do not get_issue each ticket to check for new comments.
2234
+ * Comments created at/after `since` on issues matching the same assignee / search
2235
+ * filter. One GraphQL query — do not get_issue each ticket to check for new comments.
2236
+ * Pass `issueIdentifiers` (search hits) to keep only those tickets; an empty
2237
+ * allowlist means no comments.
2239
2238
  */
2240
2239
  declare function listLinearCommentsSince(opts: {
2241
2240
  since: string;
2242
2241
  assignee?: string | null;
2243
2242
  query?: string;
2243
+ issueIdentifiers?: string[];
2244
2244
  limit?: number;
2245
2245
  apiKey?: string | null;
2246
2246
  }): Promise<IssueActivityComment[]>;
@@ -2364,7 +2364,8 @@ declare function resolveGitHubIssueRepo(repoPath?: string | null): Promise<{
2364
2364
  declare function toGitHubIssueInfo(issue: GitHubIssue): IssueInfo;
2365
2365
  /**
2366
2366
  * Repo issue comments created at/after `since`. Pass `issueIdentifiers` (#12)
2367
- * to keep only comments on that inbox page — one REST call, not N issue views.
2367
+ * to keep only comments on that inbox page — paginated REST, not N issue views.
2368
+ * An empty allowlist means no comments (do not fall through to the whole repo).
2368
2369
  */
2369
2370
  declare function listGitHubIssueCommentsSince(opts: {
2370
2371
  since: string;
package/dist/index.d.ts CHANGED
@@ -2213,13 +2213,10 @@ type LinearAssignedIssuesResult = {
2213
2213
  };
2214
2214
  /** `me`, `unassigned`, `all`, a Linear user id, or a display name. */
2215
2215
  type LinearAssigneeFilter = string;
2216
- /**
2217
- * Linear `IssueFilter` for open issues. Default assignee is `me` so existing
2218
- * Home / Create-from callers stay assigned-to-you unless they ask otherwise.
2219
- */
2220
2216
  declare function buildLinearIssueFilter(input?: {
2221
2217
  assignee?: string | null;
2222
2218
  updatedSince?: string | null;
2219
+ query?: string | null;
2223
2220
  }): Record<string, unknown>;
2224
2221
  /**
2225
2222
  * List or search open Linear issues. `assignee` is `me` (default when there is
@@ -2234,13 +2231,16 @@ declare function listLinearIssuesFiltered(opts?: {
2234
2231
  updatedSince?: string | null;
2235
2232
  }): Promise<LinearAssignedIssuesResult>;
2236
2233
  /**
2237
- * Comments created at/after `since` on issues matching the same assignee filter.
2238
- * One GraphQL query — do not get_issue each ticket to check for new comments.
2234
+ * Comments created at/after `since` on issues matching the same assignee / search
2235
+ * filter. One GraphQL query — do not get_issue each ticket to check for new comments.
2236
+ * Pass `issueIdentifiers` (search hits) to keep only those tickets; an empty
2237
+ * allowlist means no comments.
2239
2238
  */
2240
2239
  declare function listLinearCommentsSince(opts: {
2241
2240
  since: string;
2242
2241
  assignee?: string | null;
2243
2242
  query?: string;
2243
+ issueIdentifiers?: string[];
2244
2244
  limit?: number;
2245
2245
  apiKey?: string | null;
2246
2246
  }): Promise<IssueActivityComment[]>;
@@ -2364,7 +2364,8 @@ declare function resolveGitHubIssueRepo(repoPath?: string | null): Promise<{
2364
2364
  declare function toGitHubIssueInfo(issue: GitHubIssue): IssueInfo;
2365
2365
  /**
2366
2366
  * Repo issue comments created at/after `since`. Pass `issueIdentifiers` (#12)
2367
- * to keep only comments on that inbox page — one REST call, not N issue views.
2367
+ * to keep only comments on that inbox page — paginated REST, not N issue views.
2368
+ * An empty allowlist means no comments (do not fall through to the whole repo).
2368
2369
  */
2369
2370
  declare function listGitHubIssueCommentsSince(opts: {
2370
2371
  since: string;
package/dist/index.js CHANGED
@@ -238,7 +238,7 @@ import {
238
238
  worktreeCleanupSettings,
239
239
  wrapReviewSkillMarkdown,
240
240
  writeWorktreeFile
241
- } from "./chunk-4IQQEJ2C.js";
241
+ } from "./chunk-G4VAMUCL.js";
242
242
  import {
243
243
  LEGACY_PLAN_FILE_REL,
244
244
  PLAN_FILE_NAME,
@@ -1518,12 +1518,27 @@ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1518
1518
  function assigneeFilterKey(assignee) {
1519
1519
  return (assignee ?? "").trim();
1520
1520
  }
1521
+ function applyLinearIssueQueryFilter(filter, query) {
1522
+ const term = query?.trim();
1523
+ if (!term) return;
1524
+ const or = [
1525
+ { title: { containsIgnoreCase: term } },
1526
+ { description: { containsIgnoreCase: term } }
1527
+ ];
1528
+ const identifierMatch = term.match(/^[A-Za-z][\w]*-(\d+)$/);
1529
+ const asNumber = identifierMatch ? Number(identifierMatch[1]) : /^\d+$/.test(term) ? Number(term) : NaN;
1530
+ if (Number.isFinite(asNumber) && asNumber > 0) {
1531
+ or.push({ number: { eq: asNumber } });
1532
+ }
1533
+ filter.or = or;
1534
+ }
1521
1535
  function buildLinearIssueFilter(input) {
1522
1536
  const filter = {
1523
1537
  state: { type: { nin: ["completed", "canceled"] } }
1524
1538
  };
1525
1539
  const since = input?.updatedSince?.trim();
1526
1540
  if (since) filter.updatedAt = { gte: since };
1541
+ applyLinearIssueQueryFilter(filter, input?.query);
1527
1542
  const raw = assigneeFilterKey(input?.assignee) || "me";
1528
1543
  const key = raw.toLowerCase();
1529
1544
  if (key === "all" || key === "*") return filter;
@@ -1589,6 +1604,11 @@ async function listLinearIssuesFiltered(opts) {
1589
1604
  async function listLinearCommentsSince(opts) {
1590
1605
  const first = Math.max(1, Math.min(250, opts.limit ?? 200));
1591
1606
  const query = opts.query?.trim() ?? "";
1607
+ const restrictToIssues = opts.issueIdentifiers != null;
1608
+ const allowed = new Set(
1609
+ (opts.issueIdentifiers ?? []).map((id) => id.trim()).filter(Boolean)
1610
+ );
1611
+ if (restrictToIssues && allowed.size === 0) return [];
1592
1612
  const assignee = assigneeFilterKey(opts.assignee) || (query ? "all" : "me");
1593
1613
  const json = await linearGraphql(
1594
1614
  COMMENTS_SINCE_QUERY,
@@ -1596,7 +1616,7 @@ async function listLinearCommentsSince(opts) {
1596
1616
  first,
1597
1617
  filter: {
1598
1618
  createdAt: { gte: opts.since },
1599
- issue: buildLinearIssueFilter({ assignee })
1619
+ issue: buildLinearIssueFilter({ assignee, query: query || void 0 })
1600
1620
  }
1601
1621
  },
1602
1622
  opts
@@ -1604,6 +1624,7 @@ async function listLinearCommentsSince(opts) {
1604
1624
  const out = [];
1605
1625
  for (const node of json.comments?.nodes ?? []) {
1606
1626
  const identifier = String(node.issue?.identifier ?? "").trim();
1627
+ if (restrictToIssues && !allowed.has(identifier)) continue;
1607
1628
  const body = previewIssueCommentBody(String(node.body ?? ""));
1608
1629
  if (!identifier && !body) continue;
1609
1630
  out.push({
@@ -1833,10 +1854,32 @@ function issueNumberFromApiUrl(url) {
1833
1854
  const number = Number(match[1]);
1834
1855
  return Number.isFinite(number) && number > 0 ? number : null;
1835
1856
  }
1857
+ var GITHUB_ISSUE_COMMENTS_PAGE_SIZE = 100;
1858
+ var GITHUB_ISSUE_COMMENTS_MAX_PAGES = 5;
1859
+ function parseGitHubIssueCommentNode(item, allowed) {
1860
+ const rec = item && typeof item === "object" ? item : null;
1861
+ if (!rec) return null;
1862
+ const issueUrl = typeof rec.issue_url === "string" ? rec.issue_url : "";
1863
+ const number = issueNumberFromApiUrl(issueUrl);
1864
+ if (number == null) return null;
1865
+ if (allowed && !allowed.has(number)) return null;
1866
+ const body = previewIssueCommentBody(typeof rec.body === "string" ? rec.body : "");
1867
+ const user = rec.user && typeof rec.user === "object" ? String(rec.user.login ?? "").trim() : "";
1868
+ const createdAt = typeof rec.created_at === "string" ? rec.created_at : typeof rec.createdAt === "string" ? rec.createdAt : void 0;
1869
+ const url = typeof rec.html_url === "string" ? rec.html_url : void 0;
1870
+ return {
1871
+ identifier: `#${number}`,
1872
+ ...user ? { author: user } : {},
1873
+ ...createdAt ? { createdAt } : {},
1874
+ body,
1875
+ ...url ? { url } : {}
1876
+ };
1877
+ }
1836
1878
  async function listGitHubIssueCommentsSince(opts) {
1837
1879
  const limit = Math.max(1, Math.min(250, opts.limit ?? 200));
1838
1880
  const { cwd, slug } = await resolveGitHubIssueRepo(opts.repoPath);
1839
1881
  if (!slug) return [];
1882
+ const restrictToIssues = opts.issueIdentifiers != null;
1840
1883
  const allowed = new Set(
1841
1884
  (opts.issueIdentifiers ?? []).map((id) => {
1842
1885
  try {
@@ -1846,36 +1889,27 @@ async function listGitHubIssueCommentsSince(opts) {
1846
1889
  }
1847
1890
  }).filter((n) => n != null)
1848
1891
  );
1849
- const path2 = `repos/${slug}/issues/comments?since=${encodeURIComponent(opts.since)}&per_page=${limit + 1}`;
1850
- const result = await gh(["api", path2], cwd, { reject: false });
1851
- if (result.exitCode !== 0 || !result.stdout.trim()) return [];
1852
- let parsed;
1853
- try {
1854
- parsed = JSON.parse(result.stdout);
1855
- } catch {
1856
- return [];
1857
- }
1858
- if (!Array.isArray(parsed)) return [];
1892
+ if (restrictToIssues && allowed.size === 0) return [];
1893
+ const pageSize = Math.min(GITHUB_ISSUE_COMMENTS_PAGE_SIZE, Math.max(1, limit));
1859
1894
  const out = [];
1860
- for (const item of parsed) {
1861
- const rec = item && typeof item === "object" ? item : null;
1862
- if (!rec) continue;
1863
- const issueUrl = typeof rec.issue_url === "string" ? rec.issue_url : "";
1864
- const number = issueNumberFromApiUrl(issueUrl);
1865
- if (number == null) continue;
1866
- if (allowed.size > 0 && !allowed.has(number)) continue;
1867
- const body = previewIssueCommentBody(typeof rec.body === "string" ? rec.body : "");
1868
- const user = rec.user && typeof rec.user === "object" ? String(rec.user.login ?? "").trim() : "";
1869
- const createdAt = typeof rec.created_at === "string" ? rec.created_at : typeof rec.createdAt === "string" ? rec.createdAt : void 0;
1870
- const url = typeof rec.html_url === "string" ? rec.html_url : void 0;
1871
- out.push({
1872
- identifier: `#${number}`,
1873
- ...user ? { author: user } : {},
1874
- ...createdAt ? { createdAt } : {},
1875
- body,
1876
- ...url ? { url } : {}
1877
- });
1878
- if (out.length >= limit) break;
1895
+ for (let page = 1; page <= GITHUB_ISSUE_COMMENTS_MAX_PAGES; page++) {
1896
+ const path2 = `repos/${slug}/issues/comments?since=${encodeURIComponent(opts.since)}&sort=created&direction=desc&per_page=${pageSize}&page=${page}`;
1897
+ const result = await gh(["api", path2], cwd, { reject: false });
1898
+ if (result.exitCode !== 0 || !result.stdout.trim()) break;
1899
+ let parsed;
1900
+ try {
1901
+ parsed = JSON.parse(result.stdout);
1902
+ } catch {
1903
+ break;
1904
+ }
1905
+ if (!Array.isArray(parsed) || parsed.length === 0) break;
1906
+ for (const item of parsed) {
1907
+ const comment = parseGitHubIssueCommentNode(item, restrictToIssues ? allowed : null);
1908
+ if (!comment) continue;
1909
+ out.push(comment);
1910
+ if (out.length >= limit) return out;
1911
+ }
1912
+ if (parsed.length < pageSize) break;
1879
1913
  }
1880
1914
  return out;
1881
1915
  }
@@ -2081,20 +2115,19 @@ async function listIssues(repoPath, opts) {
2081
2115
  const assignee = opts?.assignee?.trim() || void 0;
2082
2116
  const since = opts?.updatedSince ? parseIssueSince(opts.updatedSince) : void 0;
2083
2117
  if (source === "linear") {
2084
- const [listed, comments2] = await Promise.all([
2085
- listLinearIssuesFiltered({
2086
- assignee,
2087
- query,
2088
- limit: opts?.limit,
2089
- updatedSince: since
2090
- }),
2091
- since ? listLinearCommentsSince({
2092
- since,
2093
- assignee,
2094
- query,
2095
- limit: opts?.limit
2096
- }) : Promise.resolve(void 0)
2097
- ]);
2118
+ const listed = await listLinearIssuesFiltered({
2119
+ assignee,
2120
+ query,
2121
+ limit: opts?.limit,
2122
+ updatedSince: since
2123
+ });
2124
+ const comments2 = since ? await listLinearCommentsSince({
2125
+ since,
2126
+ assignee,
2127
+ query,
2128
+ issueIdentifiers: query ? listed.issues.map((issue) => issue.identifier) : void 0,
2129
+ limit: opts?.limit
2130
+ }) : void 0;
2098
2131
  return {
2099
2132
  source,
2100
2133
  preferredSource,
@@ -3762,20 +3795,19 @@ function registerLinearTools(server) {
3762
3795
  try {
3763
3796
  const page = clampMcpIssueLimit(limit);
3764
3797
  const since = updatedSince ? parseIssueSince(updatedSince) : void 0;
3765
- const [listed, comments] = await Promise.all([
3766
- listLinearIssuesFiltered({
3767
- query,
3768
- assignee,
3769
- limit: page + 1,
3770
- updatedSince: since
3771
- }),
3772
- since ? listLinearCommentsSince({
3773
- since,
3774
- assignee,
3775
- query,
3776
- limit: page + 1
3777
- }) : Promise.resolve(void 0)
3778
- ]);
3798
+ const listed = await listLinearIssuesFiltered({
3799
+ query,
3800
+ assignee,
3801
+ limit: page + 1,
3802
+ updatedSince: since
3803
+ });
3804
+ const comments = since ? await listLinearCommentsSince({
3805
+ since,
3806
+ assignee,
3807
+ query,
3808
+ issueIdentifiers: query ? listed.issues.map((issue) => issue.identifier) : void 0,
3809
+ limit: page + 1
3810
+ }) : void 0;
3779
3811
  return mcpJson(
3780
3812
  formatListedIssuesForMcp(
3781
3813
  {
@@ -14694,7 +14694,7 @@ function classifyWorktreeOwnership(group, viewerLogin = "") {
14694
14694
  const reviewing = group.some((t) => {
14695
14695
  const author = normalizeViewerLogin(t.prAuthorLogin);
14696
14696
  if (author && me && author !== me) return true;
14697
- if (t.sourceType === "pr" && (!author || !me || author !== me)) return true;
14697
+ if (t.sourceType === "pr" && me && (!author || author !== me)) return true;
14698
14698
  if (me && loginsInclude(t.prReviewerLogins, me)) return true;
14699
14699
  return false;
14700
14700
  });
@@ -21709,10 +21709,32 @@ function issueNumberFromApiUrl(url) {
21709
21709
  const number = Number(match[1]);
21710
21710
  return Number.isFinite(number) && number > 0 ? number : null;
21711
21711
  }
21712
+ var GITHUB_ISSUE_COMMENTS_PAGE_SIZE = 100;
21713
+ var GITHUB_ISSUE_COMMENTS_MAX_PAGES = 5;
21714
+ function parseGitHubIssueCommentNode(item, allowed) {
21715
+ const rec = item && typeof item === "object" ? item : null;
21716
+ if (!rec) return null;
21717
+ const issueUrl = typeof rec.issue_url === "string" ? rec.issue_url : "";
21718
+ const number = issueNumberFromApiUrl(issueUrl);
21719
+ if (number == null) return null;
21720
+ if (allowed && !allowed.has(number)) return null;
21721
+ const body = previewIssueCommentBody(typeof rec.body === "string" ? rec.body : "");
21722
+ const user = rec.user && typeof rec.user === "object" ? String(rec.user.login ?? "").trim() : "";
21723
+ const createdAt = typeof rec.created_at === "string" ? rec.created_at : typeof rec.createdAt === "string" ? rec.createdAt : void 0;
21724
+ const url = typeof rec.html_url === "string" ? rec.html_url : void 0;
21725
+ return {
21726
+ identifier: `#${number}`,
21727
+ ...user ? { author: user } : {},
21728
+ ...createdAt ? { createdAt } : {},
21729
+ body,
21730
+ ...url ? { url } : {}
21731
+ };
21732
+ }
21712
21733
  async function listGitHubIssueCommentsSince(opts) {
21713
21734
  const limit = Math.max(1, Math.min(250, opts.limit ?? 200));
21714
21735
  const { cwd, slug } = await resolveGitHubIssueRepo(opts.repoPath);
21715
21736
  if (!slug) return [];
21737
+ const restrictToIssues = opts.issueIdentifiers != null;
21716
21738
  const allowed = new Set(
21717
21739
  (opts.issueIdentifiers ?? []).map((id) => {
21718
21740
  try {
@@ -21722,36 +21744,27 @@ async function listGitHubIssueCommentsSince(opts) {
21722
21744
  }
21723
21745
  }).filter((n) => n != null)
21724
21746
  );
21725
- const path = `repos/${slug}/issues/comments?since=${encodeURIComponent(opts.since)}&per_page=${limit + 1}`;
21726
- const result = await gh(["api", path], cwd, { reject: false });
21727
- if (result.exitCode !== 0 || !result.stdout.trim()) return [];
21728
- let parsed;
21729
- try {
21730
- parsed = JSON.parse(result.stdout);
21731
- } catch {
21732
- return [];
21733
- }
21734
- if (!Array.isArray(parsed)) return [];
21747
+ if (restrictToIssues && allowed.size === 0) return [];
21748
+ const pageSize = Math.min(GITHUB_ISSUE_COMMENTS_PAGE_SIZE, Math.max(1, limit));
21735
21749
  const out = [];
21736
- for (const item of parsed) {
21737
- const rec = item && typeof item === "object" ? item : null;
21738
- if (!rec) continue;
21739
- const issueUrl = typeof rec.issue_url === "string" ? rec.issue_url : "";
21740
- const number = issueNumberFromApiUrl(issueUrl);
21741
- if (number == null) continue;
21742
- if (allowed.size > 0 && !allowed.has(number)) continue;
21743
- const body = previewIssueCommentBody(typeof rec.body === "string" ? rec.body : "");
21744
- const user = rec.user && typeof rec.user === "object" ? String(rec.user.login ?? "").trim() : "";
21745
- const createdAt = typeof rec.created_at === "string" ? rec.created_at : typeof rec.createdAt === "string" ? rec.createdAt : void 0;
21746
- const url = typeof rec.html_url === "string" ? rec.html_url : void 0;
21747
- out.push({
21748
- identifier: `#${number}`,
21749
- ...user ? { author: user } : {},
21750
- ...createdAt ? { createdAt } : {},
21751
- body,
21752
- ...url ? { url } : {}
21753
- });
21754
- if (out.length >= limit) break;
21750
+ for (let page = 1; page <= GITHUB_ISSUE_COMMENTS_MAX_PAGES; page++) {
21751
+ const path = `repos/${slug}/issues/comments?since=${encodeURIComponent(opts.since)}&sort=created&direction=desc&per_page=${pageSize}&page=${page}`;
21752
+ const result = await gh(["api", path], cwd, { reject: false });
21753
+ if (result.exitCode !== 0 || !result.stdout.trim()) break;
21754
+ let parsed;
21755
+ try {
21756
+ parsed = JSON.parse(result.stdout);
21757
+ } catch {
21758
+ break;
21759
+ }
21760
+ if (!Array.isArray(parsed) || parsed.length === 0) break;
21761
+ for (const item of parsed) {
21762
+ const comment = parseGitHubIssueCommentNode(item, restrictToIssues ? allowed : null);
21763
+ if (!comment) continue;
21764
+ out.push(comment);
21765
+ if (out.length >= limit) return out;
21766
+ }
21767
+ if (parsed.length < pageSize) break;
21755
21768
  }
21756
21769
  return out;
21757
21770
  }
@@ -22361,12 +22374,27 @@ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
22361
22374
  function assigneeFilterKey(assignee) {
22362
22375
  return (assignee ?? "").trim();
22363
22376
  }
22377
+ function applyLinearIssueQueryFilter(filter, query) {
22378
+ const term = query?.trim();
22379
+ if (!term) return;
22380
+ const or = [
22381
+ { title: { containsIgnoreCase: term } },
22382
+ { description: { containsIgnoreCase: term } }
22383
+ ];
22384
+ const identifierMatch = term.match(/^[A-Za-z][\w]*-(\d+)$/);
22385
+ const asNumber = identifierMatch ? Number(identifierMatch[1]) : /^\d+$/.test(term) ? Number(term) : NaN;
22386
+ if (Number.isFinite(asNumber) && asNumber > 0) {
22387
+ or.push({ number: { eq: asNumber } });
22388
+ }
22389
+ filter.or = or;
22390
+ }
22364
22391
  function buildLinearIssueFilter(input) {
22365
22392
  const filter = {
22366
22393
  state: { type: { nin: ["completed", "canceled"] } }
22367
22394
  };
22368
22395
  const since = input?.updatedSince?.trim();
22369
22396
  if (since) filter.updatedAt = { gte: since };
22397
+ applyLinearIssueQueryFilter(filter, input?.query);
22370
22398
  const raw = assigneeFilterKey(input?.assignee) || "me";
22371
22399
  const key = raw.toLowerCase();
22372
22400
  if (key === "all" || key === "*") return filter;
@@ -22432,6 +22460,11 @@ async function listLinearIssuesFiltered(opts) {
22432
22460
  async function listLinearCommentsSince(opts) {
22433
22461
  const first = Math.max(1, Math.min(250, opts.limit ?? 200));
22434
22462
  const query = opts.query?.trim() ?? "";
22463
+ const restrictToIssues = opts.issueIdentifiers != null;
22464
+ const allowed = new Set(
22465
+ (opts.issueIdentifiers ?? []).map((id) => id.trim()).filter(Boolean)
22466
+ );
22467
+ if (restrictToIssues && allowed.size === 0) return [];
22435
22468
  const assignee = assigneeFilterKey(opts.assignee) || (query ? "all" : "me");
22436
22469
  const json = await linearGraphql(
22437
22470
  COMMENTS_SINCE_QUERY,
@@ -22439,7 +22472,7 @@ async function listLinearCommentsSince(opts) {
22439
22472
  first,
22440
22473
  filter: {
22441
22474
  createdAt: { gte: opts.since },
22442
- issue: buildLinearIssueFilter({ assignee })
22475
+ issue: buildLinearIssueFilter({ assignee, query: query || void 0 })
22443
22476
  }
22444
22477
  },
22445
22478
  opts
@@ -22447,6 +22480,7 @@ async function listLinearCommentsSince(opts) {
22447
22480
  const out = [];
22448
22481
  for (const node of json.comments?.nodes ?? []) {
22449
22482
  const identifier = String(node.issue?.identifier ?? "").trim();
22483
+ if (restrictToIssues && !allowed.has(identifier)) continue;
22450
22484
  const body = previewIssueCommentBody(String(node.body ?? ""));
22451
22485
  if (!identifier && !body) continue;
22452
22486
  out.push({
@@ -22672,20 +22706,19 @@ async function listIssues(repoPath, opts) {
22672
22706
  const assignee = opts?.assignee?.trim() || void 0;
22673
22707
  const since = opts?.updatedSince ? parseIssueSince(opts.updatedSince) : void 0;
22674
22708
  if (source === "linear") {
22675
- const [listed, comments2] = await Promise.all([
22676
- listLinearIssuesFiltered({
22677
- assignee,
22678
- query,
22679
- limit: opts?.limit,
22680
- updatedSince: since
22681
- }),
22682
- since ? listLinearCommentsSince({
22683
- since,
22684
- assignee,
22685
- query,
22686
- limit: opts?.limit
22687
- }) : Promise.resolve(void 0)
22688
- ]);
22709
+ const listed = await listLinearIssuesFiltered({
22710
+ assignee,
22711
+ query,
22712
+ limit: opts?.limit,
22713
+ updatedSince: since
22714
+ });
22715
+ const comments2 = since ? await listLinearCommentsSince({
22716
+ since,
22717
+ assignee,
22718
+ query,
22719
+ issueIdentifiers: query ? listed.issues.map((issue) => issue.identifier) : void 0,
22720
+ limit: opts?.limit
22721
+ }) : void 0;
22689
22722
  return {
22690
22723
  source,
22691
22724
  preferredSource,
@@ -24040,20 +24073,19 @@ function registerLinearTools(server) {
24040
24073
  try {
24041
24074
  const page = clampMcpIssueLimit(limit);
24042
24075
  const since = updatedSince ? parseIssueSince(updatedSince) : void 0;
24043
- const [listed, comments] = await Promise.all([
24044
- listLinearIssuesFiltered({
24045
- query,
24046
- assignee,
24047
- limit: page + 1,
24048
- updatedSince: since
24049
- }),
24050
- since ? listLinearCommentsSince({
24051
- since,
24052
- assignee,
24053
- query,
24054
- limit: page + 1
24055
- }) : Promise.resolve(void 0)
24056
- ]);
24076
+ const listed = await listLinearIssuesFiltered({
24077
+ query,
24078
+ assignee,
24079
+ limit: page + 1,
24080
+ updatedSince: since
24081
+ });
24082
+ const comments = since ? await listLinearCommentsSince({
24083
+ since,
24084
+ assignee,
24085
+ query,
24086
+ issueIdentifiers: query ? listed.issues.map((issue) => issue.identifier) : void 0,
24087
+ limit: page + 1
24088
+ }) : void 0;
24057
24089
  return mcpJson(
24058
24090
  formatListedIssuesForMcp(
24059
24091
  {
@@ -37,7 +37,7 @@ import {
37
37
  syncBoardPins,
38
38
  updateSchedule,
39
39
  waitForDetachedJob
40
- } from "../chunk-IMCIHSIF.js";
40
+ } from "../chunk-IQC35TVQ.js";
41
41
  import {
42
42
  applySystemCaEnv,
43
43
  isCursorApiKeyConfigured,
@@ -262,10 +262,32 @@ function issueNumberFromApiUrl(url) {
262
262
  const number = Number(match[1]);
263
263
  return Number.isFinite(number) && number > 0 ? number : null;
264
264
  }
265
+ var GITHUB_ISSUE_COMMENTS_PAGE_SIZE = 100;
266
+ var GITHUB_ISSUE_COMMENTS_MAX_PAGES = 5;
267
+ function parseGitHubIssueCommentNode(item, allowed) {
268
+ const rec = item && typeof item === "object" ? item : null;
269
+ if (!rec) return null;
270
+ const issueUrl = typeof rec.issue_url === "string" ? rec.issue_url : "";
271
+ const number = issueNumberFromApiUrl(issueUrl);
272
+ if (number == null) return null;
273
+ if (allowed && !allowed.has(number)) return null;
274
+ const body = previewIssueCommentBody(typeof rec.body === "string" ? rec.body : "");
275
+ const user = rec.user && typeof rec.user === "object" ? String(rec.user.login ?? "").trim() : "";
276
+ const createdAt = typeof rec.created_at === "string" ? rec.created_at : typeof rec.createdAt === "string" ? rec.createdAt : void 0;
277
+ const url = typeof rec.html_url === "string" ? rec.html_url : void 0;
278
+ return {
279
+ identifier: `#${number}`,
280
+ ...user ? { author: user } : {},
281
+ ...createdAt ? { createdAt } : {},
282
+ body,
283
+ ...url ? { url } : {}
284
+ };
285
+ }
265
286
  async function listGitHubIssueCommentsSince(opts) {
266
287
  const limit = Math.max(1, Math.min(250, opts.limit ?? 200));
267
288
  const { cwd, slug } = await resolveGitHubIssueRepo(opts.repoPath);
268
289
  if (!slug) return [];
290
+ const restrictToIssues = opts.issueIdentifiers != null;
269
291
  const allowed = new Set(
270
292
  (opts.issueIdentifiers ?? []).map((id) => {
271
293
  try {
@@ -275,36 +297,27 @@ async function listGitHubIssueCommentsSince(opts) {
275
297
  }
276
298
  }).filter((n) => n != null)
277
299
  );
278
- const path = `repos/${slug}/issues/comments?since=${encodeURIComponent(opts.since)}&per_page=${limit + 1}`;
279
- const result = await gh(["api", path], cwd, { reject: false });
280
- if (result.exitCode !== 0 || !result.stdout.trim()) return [];
281
- let parsed;
282
- try {
283
- parsed = JSON.parse(result.stdout);
284
- } catch {
285
- return [];
286
- }
287
- if (!Array.isArray(parsed)) return [];
300
+ if (restrictToIssues && allowed.size === 0) return [];
301
+ const pageSize = Math.min(GITHUB_ISSUE_COMMENTS_PAGE_SIZE, Math.max(1, limit));
288
302
  const out = [];
289
- for (const item of parsed) {
290
- const rec = item && typeof item === "object" ? item : null;
291
- if (!rec) continue;
292
- const issueUrl = typeof rec.issue_url === "string" ? rec.issue_url : "";
293
- const number = issueNumberFromApiUrl(issueUrl);
294
- if (number == null) continue;
295
- if (allowed.size > 0 && !allowed.has(number)) continue;
296
- const body = previewIssueCommentBody(typeof rec.body === "string" ? rec.body : "");
297
- const user = rec.user && typeof rec.user === "object" ? String(rec.user.login ?? "").trim() : "";
298
- const createdAt = typeof rec.created_at === "string" ? rec.created_at : typeof rec.createdAt === "string" ? rec.createdAt : void 0;
299
- const url = typeof rec.html_url === "string" ? rec.html_url : void 0;
300
- out.push({
301
- identifier: `#${number}`,
302
- ...user ? { author: user } : {},
303
- ...createdAt ? { createdAt } : {},
304
- body,
305
- ...url ? { url } : {}
306
- });
307
- if (out.length >= limit) break;
303
+ for (let page = 1; page <= GITHUB_ISSUE_COMMENTS_MAX_PAGES; page++) {
304
+ const path = `repos/${slug}/issues/comments?since=${encodeURIComponent(opts.since)}&sort=created&direction=desc&per_page=${pageSize}&page=${page}`;
305
+ const result = await gh(["api", path], cwd, { reject: false });
306
+ if (result.exitCode !== 0 || !result.stdout.trim()) break;
307
+ let parsed;
308
+ try {
309
+ parsed = JSON.parse(result.stdout);
310
+ } catch {
311
+ break;
312
+ }
313
+ if (!Array.isArray(parsed) || parsed.length === 0) break;
314
+ for (const item of parsed) {
315
+ const comment = parseGitHubIssueCommentNode(item, restrictToIssues ? allowed : null);
316
+ if (!comment) continue;
317
+ out.push(comment);
318
+ if (out.length >= limit) return out;
319
+ }
320
+ if (parsed.length < pageSize) break;
308
321
  }
309
322
  return out;
310
323
  }
@@ -909,12 +922,27 @@ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
909
922
  function assigneeFilterKey(assignee) {
910
923
  return (assignee ?? "").trim();
911
924
  }
925
+ function applyLinearIssueQueryFilter(filter, query) {
926
+ const term = query?.trim();
927
+ if (!term) return;
928
+ const or = [
929
+ { title: { containsIgnoreCase: term } },
930
+ { description: { containsIgnoreCase: term } }
931
+ ];
932
+ const identifierMatch = term.match(/^[A-Za-z][\w]*-(\d+)$/);
933
+ const asNumber = identifierMatch ? Number(identifierMatch[1]) : /^\d+$/.test(term) ? Number(term) : NaN;
934
+ if (Number.isFinite(asNumber) && asNumber > 0) {
935
+ or.push({ number: { eq: asNumber } });
936
+ }
937
+ filter.or = or;
938
+ }
912
939
  function buildLinearIssueFilter(input) {
913
940
  const filter = {
914
941
  state: { type: { nin: ["completed", "canceled"] } }
915
942
  };
916
943
  const since = input?.updatedSince?.trim();
917
944
  if (since) filter.updatedAt = { gte: since };
945
+ applyLinearIssueQueryFilter(filter, input?.query);
918
946
  const raw = assigneeFilterKey(input?.assignee) || "me";
919
947
  const key = raw.toLowerCase();
920
948
  if (key === "all" || key === "*") return filter;
@@ -980,6 +1008,11 @@ async function listLinearIssuesFiltered(opts) {
980
1008
  async function listLinearCommentsSince(opts) {
981
1009
  const first = Math.max(1, Math.min(250, opts.limit ?? 200));
982
1010
  const query = opts.query?.trim() ?? "";
1011
+ const restrictToIssues = opts.issueIdentifiers != null;
1012
+ const allowed = new Set(
1013
+ (opts.issueIdentifiers ?? []).map((id) => id.trim()).filter(Boolean)
1014
+ );
1015
+ if (restrictToIssues && allowed.size === 0) return [];
983
1016
  const assignee = assigneeFilterKey(opts.assignee) || (query ? "all" : "me");
984
1017
  const json = await linearGraphql(
985
1018
  COMMENTS_SINCE_QUERY,
@@ -987,7 +1020,7 @@ async function listLinearCommentsSince(opts) {
987
1020
  first,
988
1021
  filter: {
989
1022
  createdAt: { gte: opts.since },
990
- issue: buildLinearIssueFilter({ assignee })
1023
+ issue: buildLinearIssueFilter({ assignee, query: query || void 0 })
991
1024
  }
992
1025
  },
993
1026
  opts
@@ -995,6 +1028,7 @@ async function listLinearCommentsSince(opts) {
995
1028
  const out = [];
996
1029
  for (const node of json.comments?.nodes ?? []) {
997
1030
  const identifier = String(node.issue?.identifier ?? "").trim();
1031
+ if (restrictToIssues && !allowed.has(identifier)) continue;
998
1032
  const body = previewIssueCommentBody(String(node.body ?? ""));
999
1033
  if (!identifier && !body) continue;
1000
1034
  out.push({
@@ -1220,20 +1254,19 @@ async function listIssues(repoPath, opts) {
1220
1254
  const assignee = opts?.assignee?.trim() || void 0;
1221
1255
  const since = opts?.updatedSince ? parseIssueSince(opts.updatedSince) : void 0;
1222
1256
  if (source === "linear") {
1223
- const [listed, comments2] = await Promise.all([
1224
- listLinearIssuesFiltered({
1225
- assignee,
1226
- query,
1227
- limit: opts?.limit,
1228
- updatedSince: since
1229
- }),
1230
- since ? listLinearCommentsSince({
1231
- since,
1232
- assignee,
1233
- query,
1234
- limit: opts?.limit
1235
- }) : Promise.resolve(void 0)
1236
- ]);
1257
+ const listed = await listLinearIssuesFiltered({
1258
+ assignee,
1259
+ query,
1260
+ limit: opts?.limit,
1261
+ updatedSince: since
1262
+ });
1263
+ const comments2 = since ? await listLinearCommentsSince({
1264
+ since,
1265
+ assignee,
1266
+ query,
1267
+ issueIdentifiers: query ? listed.issues.map((issue) => issue.identifier) : void 0,
1268
+ limit: opts?.limit
1269
+ }) : void 0;
1237
1270
  return {
1238
1271
  source,
1239
1272
  preferredSource,
@@ -2563,20 +2596,19 @@ function registerLinearTools(server) {
2563
2596
  try {
2564
2597
  const page = clampMcpIssueLimit(limit);
2565
2598
  const since = updatedSince ? parseIssueSince(updatedSince) : void 0;
2566
- const [listed, comments] = await Promise.all([
2567
- listLinearIssuesFiltered({
2568
- query,
2569
- assignee,
2570
- limit: page + 1,
2571
- updatedSince: since
2572
- }),
2573
- since ? listLinearCommentsSince({
2574
- since,
2575
- assignee,
2576
- query,
2577
- limit: page + 1
2578
- }) : Promise.resolve(void 0)
2579
- ]);
2599
+ const listed = await listLinearIssuesFiltered({
2600
+ query,
2601
+ assignee,
2602
+ limit: page + 1,
2603
+ updatedSince: since
2604
+ });
2605
+ const comments = since ? await listLinearCommentsSince({
2606
+ since,
2607
+ assignee,
2608
+ query,
2609
+ issueIdentifiers: query ? listed.issues.map((issue) => issue.identifier) : void 0,
2610
+ limit: page + 1
2611
+ }) : void 0;
2580
2612
  return mcpJson(
2581
2613
  formatListedIssuesForMcp(
2582
2614
  {
@@ -8,7 +8,7 @@ import {
8
8
  resolveSendFollowUp,
9
9
  startOrchestration,
10
10
  waitForPidExit
11
- } from "./chunk-IMCIHSIF.js";
11
+ } from "./chunk-IQC35TVQ.js";
12
12
  import "./chunk-AHV36NL3.js";
13
13
  import "./chunk-HVIEYTUK.js";
14
14
  import "./chunk-Y4MIXGPA.js";
@@ -6,7 +6,7 @@ import {
6
6
  resolveSendFollowUp,
7
7
  startOrchestration,
8
8
  waitForPidExit
9
- } from "./chunk-4IQQEJ2C.js";
9
+ } from "./chunk-G4VAMUCL.js";
10
10
  import "./chunk-DJU54RYW.js";
11
11
  import "./chunk-5FTIOINZ.js";
12
12
  import "./chunk-VIODP5PT.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sideboard-ai/core",
3
- "version": "0.1.184",
3
+ "version": "0.1.185",
4
4
  "description": "Sideboard core — orchestration, agents, git worktrees, MCP server",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",