@sideboard-ai/core 0.1.183 → 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.
@@ -37,7 +37,7 @@ import {
37
37
  syncBoardPins,
38
38
  updateSchedule,
39
39
  waitForDetachedJob
40
- } from "../chunk-B4IENDRW.js";
40
+ } from "../chunk-IQC35TVQ.js";
41
41
  import {
42
42
  applySystemCaEnv,
43
43
  isCursorApiKeyConfigured,
@@ -46,7 +46,7 @@ import {
46
46
  materializeSystemCaBundle,
47
47
  sideboardMcpProfile,
48
48
  trustSystemCertificates
49
- } from "../chunk-TPOODE27.js";
49
+ } from "../chunk-AHV36NL3.js";
50
50
  import "../chunk-HVIEYTUK.js";
51
51
  import "../chunk-Y4MIXGPA.js";
52
52
  import {
@@ -70,7 +70,7 @@ import {
70
70
  GLOBAL_WORKSPACE_ID,
71
71
  isCloudCoordinatorThread,
72
72
  listWorkspaces
73
- } from "../chunk-JA7TKTQ4.js";
73
+ } from "../chunk-XXEIF2IL.js";
74
74
  import {
75
75
  canonicalizeRepoPath,
76
76
  formatGhLandError,
@@ -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,
@@ -2337,7 +2370,7 @@ function registerAbleTimeTools(server) {
2337
2370
  );
2338
2371
  server.tool(
2339
2372
  "abletime_comment",
2340
- "Add a markdown comment on an AbleTime task (id or CRM-232). When reviewing a PR or ticket, show the draft in chat and ask_user (Post this review / Keep it in chat) first \u2014 the author sees this immediately.",
2373
+ "Add a markdown comment on an AbleTime task (id or CRM-232). When reviewing a PR or ticket, show the draft in chat and wait until they type a next step \u2014 do not ask_user after the review. The author sees this immediately.",
2341
2374
  { id: z4.string(), body: z4.string() },
2342
2375
  async (args) => {
2343
2376
  try {
@@ -2349,7 +2382,7 @@ function registerAbleTimeTools(server) {
2349
2382
  );
2350
2383
  server.tool(
2351
2384
  "abletime_update_task",
2352
- "Update an AbleTime task (id or CRM-232). Pass title, description, and/or state. When reviewing a PR or ticket, ask_user (Post this review / Keep it in chat) before changing the task \u2014 the author is notified.",
2385
+ "Update an AbleTime task (id or CRM-232). Pass title, description, and/or state. When reviewing a PR or ticket, wait until they type a next step before changing the task \u2014 do not ask_user after the review. The author is notified.",
2353
2386
  {
2354
2387
  id: z4.string(),
2355
2388
  title: z4.string().optional(),
@@ -2478,7 +2511,7 @@ function registerGithubIssueTools(server) {
2478
2511
  );
2479
2512
  server.tool(
2480
2513
  "github_comment",
2481
- "Add a markdown comment on a GitHub issue (#123 or URL). Uses Account gh. When reviewing a PR or ticket, show the draft in chat and ask_user (Post this review / Keep it in chat) first \u2014 the author sees this immediately.",
2514
+ "Add a markdown comment on a GitHub issue (#123 or URL). Uses Account gh. When reviewing a PR or ticket, show the draft in chat and wait until they type a next step \u2014 do not ask_user after the review. The author sees this immediately.",
2482
2515
  { id: z5.string(), body: z5.string(), repoPath: repoPathSchema },
2483
2516
  async ({ id, body, repoPath }) => {
2484
2517
  try {
@@ -2490,7 +2523,7 @@ function registerGithubIssueTools(server) {
2490
2523
  );
2491
2524
  server.tool(
2492
2525
  "github_update_issue",
2493
- "Update a GitHub issue (#123). Pass title, body, and/or state (open|closed). When reviewing a PR or ticket, ask_user (Post this review / Keep it in chat) before changing the issue \u2014 the author is notified.",
2526
+ "Update a GitHub issue (#123). Pass title, body, and/or state (open|closed). When reviewing a PR or ticket, wait until they type a next step before changing the issue \u2014 do not ask_user after the review. The author is notified.",
2494
2527
  {
2495
2528
  id: z5.string(),
2496
2529
  title: z5.string().optional(),
@@ -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
  {
@@ -2628,7 +2660,7 @@ function registerLinearTools(server) {
2628
2660
  );
2629
2661
  server.tool(
2630
2662
  "linear_update_issue",
2631
- "Update a Linear issue (uuid or ENG-123). Pass title, description, state, assignee, and/or priority. When reviewing a PR or ticket, ask_user (Post this review / Keep it in chat) before changing the ticket \u2014 the author is notified.",
2663
+ "Update a Linear issue (uuid or ENG-123). Pass title, description, state, assignee, and/or priority. When reviewing a PR or ticket, wait until they type a next step before changing the ticket \u2014 do not ask_user after the review. The author is notified.",
2632
2664
  {
2633
2665
  id: z6.string(),
2634
2666
  title: z6.string().optional(),
@@ -2647,7 +2679,7 @@ function registerLinearTools(server) {
2647
2679
  );
2648
2680
  server.tool(
2649
2681
  "linear_comment",
2650
- "Add a markdown comment on a Linear issue (uuid or ENG-123). When reviewing a PR or ticket, show the draft in chat and ask_user (Post this review / Keep it in chat) first \u2014 the author sees this immediately.",
2682
+ "Add a markdown comment on a Linear issue (uuid or ENG-123). When reviewing a PR or ticket, show the draft in chat and wait until they type a next step \u2014 do not ask_user after the review. The author sees this immediately.",
2651
2683
  {
2652
2684
  id: z6.string(),
2653
2685
  body: z6.string()
@@ -4012,7 +4044,7 @@ async function startMcpServer() {
4012
4044
  );
4013
4045
  server.tool(
4014
4046
  "request_review",
4015
- 'Start a merge-readiness Review on a worktree agent thread (same as the desktop Review button). Opens a new Review chat tab, attaches .claude/skills/review/SKILL.md when present (else copies .sideboard/review.md into .context/review.md, or seeds that file from the stock template), and sends "Review changes in this workspace." Expect Approve / Approve with nits / Request changes / Needs more information in that chat. Do not comment on or update the PR or ticket until the user confirms (ask_user: Post this review / Keep it in chat) \u2014 they work through the feedback in chat first. Pass a worktree thread ref \u2014 not the orchestrator. Then wait_for_turn (loop while stillRunning) / get_turn_result on the returned review tab id.',
4047
+ 'Start a merge-readiness Review on a worktree agent thread (same as the desktop Review button). Opens a new Review chat tab, attaches .claude/skills/review/SKILL.md when present (else copies .sideboard/review.md into .context/review.md, or seeds that file from the stock template), and sends "Review changes in this workspace." Expect Approve / Approve with nits / Request changes / Needs more information in that chat. The review stays there so the user can read it and type next steps \u2014 do not comment on or update the PR or ticket, and do not ask_user after the review, until they ask. Pass a worktree thread ref \u2014 not the orchestrator. Then wait_for_turn (loop while stillRunning) / get_turn_result on the returned review tab id.',
4016
4048
  { ref: z9.string().describe("Worktree thread id/ref to review") },
4017
4049
  async ({ ref }) => {
4018
4050
  try {
@@ -8,14 +8,14 @@ import {
8
8
  resolveSendFollowUp,
9
9
  startOrchestration,
10
10
  waitForPidExit
11
- } from "./chunk-B4IENDRW.js";
12
- import "./chunk-TPOODE27.js";
11
+ } from "./chunk-IQC35TVQ.js";
12
+ import "./chunk-AHV36NL3.js";
13
13
  import "./chunk-HVIEYTUK.js";
14
14
  import "./chunk-Y4MIXGPA.js";
15
15
  import "./chunk-VKQGAWHY.js";
16
16
  import "./chunk-JFOPOMK7.js";
17
17
  import "./chunk-OCQPTUR7.js";
18
- import "./chunk-JA7TKTQ4.js";
18
+ import "./chunk-XXEIF2IL.js";
19
19
  import "./chunk-XBLN52RZ.js";
20
20
  import "./chunk-D6JWYJ4P.js";
21
21
  import "./chunk-GOZ6T3AG.js";
@@ -6,10 +6,10 @@ import {
6
6
  resolveSendFollowUp,
7
7
  startOrchestration,
8
8
  waitForPidExit
9
- } from "./chunk-26N6LKLE.js";
9
+ } from "./chunk-G4VAMUCL.js";
10
10
  import "./chunk-DJU54RYW.js";
11
- import "./chunk-VA76U3BU.js";
12
- import "./chunk-JFDCHOU7.js";
11
+ import "./chunk-5FTIOINZ.js";
12
+ import "./chunk-VIODP5PT.js";
13
13
  import "./chunk-WXKXSZN4.js";
14
14
  import "./chunk-6K64FJDD.js";
15
15
  import "./chunk-IEEAVWAP.js";
@@ -4,7 +4,7 @@ import {
4
4
  listWorkspaces,
5
5
  removeWorkspace,
6
6
  syncWorkspacesFromThreads
7
- } from "./chunk-JFDCHOU7.js";
7
+ } from "./chunk-VIODP5PT.js";
8
8
  import "./chunk-WXKXSZN4.js";
9
9
  import "./chunk-6K64FJDD.js";
10
10
  import "./chunk-J7LQVLFQ.js";
@@ -6,7 +6,7 @@ import {
6
6
  listWorkspaces,
7
7
  removeWorkspace,
8
8
  syncWorkspacesFromThreads
9
- } from "./chunk-JA7TKTQ4.js";
9
+ } from "./chunk-XXEIF2IL.js";
10
10
  import "./chunk-XBLN52RZ.js";
11
11
  import "./chunk-D6JWYJ4P.js";
12
12
  import "./chunk-GOZ6T3AG.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sideboard-ai/core",
3
- "version": "0.1.183",
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",