@sideboard-ai/core 0.1.188 → 0.1.190

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.
@@ -1610,13 +1610,21 @@ function looksLikeHugeToolResultDump(text7) {
1610
1610
  if (t.length < 4e3) return false;
1611
1611
  return looksLikeMinifiedJsDump(t);
1612
1612
  }
1613
+ function looksLikeJobWaitResult(rec) {
1614
+ if (typeof rec.delta === "string") {
1615
+ return typeof rec.status === "string" || typeof rec.id === "string" || typeof rec.stillRunning === "boolean";
1616
+ }
1617
+ if (typeof rec.stopped === "boolean") {
1618
+ return typeof rec.id === "string" || typeof rec.status === "string";
1619
+ }
1620
+ return rec.started === true || rec.reason === "already-running";
1621
+ }
1613
1622
  function clipJobWaitResultJson(content) {
1614
1623
  const trimmed = content.trim();
1615
1624
  if (!trimmed.startsWith("{")) return void 0;
1616
1625
  try {
1617
1626
  const rec = JSON.parse(trimmed);
1618
- const isJob = typeof rec.stillRunning === "boolean" || typeof rec.stopped === "boolean" || typeof rec.delta === "string" && typeof rec.status === "string";
1619
- if (!isJob) return void 0;
1627
+ if (!looksLikeJobWaitResult(rec)) return void 0;
1620
1628
  if (typeof rec.delta === "string" && rec.delta.length > 3500) {
1621
1629
  rec.delta = `\u2026(truncated ${rec.delta.length} chars)
1622
1630
  ${rec.delta.slice(-3500)}`;
@@ -1782,7 +1790,7 @@ function humanizeAgentFailDetail(detail) {
1782
1790
  return /ran out of memory/i.test(raw) ? raw : V8_OOM_SUMMARY;
1783
1791
  }
1784
1792
  if (/homebrew node \+ shared libuv|Cellar\/(?:libuv|node)|libuv\.\d+\.dylib/i.test(raw)) {
1785
- return /brew install node@22/i.test(raw) ? raw : `${raw} \u2014 install Node 22 LTS (\`brew install node@22\`) and retry.`;
1793
+ return /brew install node@(?:22|24)/i.test(raw) ? raw : `${raw} \u2014 install Node 24 LTS (\`brew install node@24\`) and retry.`;
1786
1794
  }
1787
1795
  if (/uv_run|spineventloopinternal|uv__io_poll|cursor runner crashed in node/i.test(lower)) {
1788
1796
  return /retry the turn/i.test(raw) ? raw : BUNDLED_NODE_CRASH_SUMMARY;
@@ -1860,7 +1868,7 @@ var init_error_detail = __esm({
1860
1868
  "use strict";
1861
1869
  NODE_VERSION_FOOTER = /^Node\.js v\d+/i;
1862
1870
  NESTED_ELECTRON_SUMMARY = "Cursor local agent crashed at Electron startup (nested Chromium / HasCustomHostObject)";
1863
- HOMEBREW_LIBUV_SUMMARY = "Cursor runner crashed in Node (Homebrew Node + shared libuv). Install Node 22 LTS (`brew install node@22`) and retry.";
1871
+ HOMEBREW_LIBUV_SUMMARY = "Cursor runner crashed in Node (Homebrew Node + shared libuv). Install Node 24 LTS (`brew install node@24`) and retry.";
1864
1872
  BUNDLED_NODE_CRASH_SUMMARY = "Cursor runner crashed in Node. Retry the turn.";
1865
1873
  V8_OOM_SUMMARY = "Agent ran out of memory (JavaScript heap). If it keeps happening, exclude large files from the project folder.";
1866
1874
  MINIFIED_DUMP_SUMMARY = "Cursor local agent crashed during startup (truncated crash dump)";
@@ -9615,7 +9623,7 @@ function parseNodeMajor(version) {
9615
9623
  function scoreNodeForAgentRuntime(candidate) {
9616
9624
  const major = parseNodeMajor(candidate.version);
9617
9625
  if (major == null) return Number.NEGATIVE_INFINITY;
9618
- if (major < 20) return major - 100;
9626
+ if (major < 22) return major - 100;
9619
9627
  const posix = candidate.path.replace(/\\/g, "/");
9620
9628
  let score = 0;
9621
9629
  if (major % 2 === 0) {
@@ -9776,7 +9784,7 @@ var init_node_launch = __esm({
9776
9784
  init_packaged_runtime();
9777
9785
  AGENT_RUNNER_MAX_OLD_SPACE_MB = 8192;
9778
9786
  MAX_OLD_SPACE_FLAG = /(?:^|\s)(--max[-_]old[-_]space[-_]size)(?:[= ](\d+))?(?=\s|$)/;
9779
- PREFERRED_LTS_MAJORS = [24, 22, 20];
9787
+ PREFERRED_LTS_MAJORS = [24, 22];
9780
9788
  }
9781
9789
  });
9782
9790
 
@@ -18299,7 +18307,7 @@ function formatIssueToolsDirective(opts) {
18299
18307
  ];
18300
18308
  if (opts.linear) {
18301
18309
  lines.push(
18302
- "- Linear: `linear_search_issues` (assignee=me; pass `updatedSince` for \u201Cany updates since yesterday?\u201D \u2014 new tickets, edits, and comments), `linear_get_issue` (comments; pass `include=full` if crush.truncated), `linear_comment`, `linear_update_issue` (state), `linear_create_issue` (pass `parent` for spin-offs; call `linear_list_teams` first). Scope errors: reconnect Linear in Settings \u2192 Issues."
18310
+ "- Linear: `linear_search_issues` (assignee=me; pass `updatedSince` for \u201Cany updates since yesterday?\u201D \u2014 new tickets, edits, and comments), `linear_get_issue` (comments; pass `include=full` if crush.truncated), `linear_comment`, `linear_update_issue` (state, cycle), `linear_create_issue` (pass `parent` for spin-offs; call `linear_list_teams` first). Scope errors: reconnect Linear in Settings \u2192 Issues."
18303
18311
  );
18304
18312
  }
18305
18313
  if (github) {
@@ -21827,7 +21835,7 @@ async function listGitHubIssueCommentsSince(opts) {
21827
21835
  }).filter((n) => n != null)
21828
21836
  );
21829
21837
  if (restrictToIssues && allowed.size === 0) return [];
21830
- const pageSize = Math.min(GITHUB_ISSUE_COMMENTS_PAGE_SIZE, Math.max(1, limit));
21838
+ const pageSize = GITHUB_ISSUE_COMMENTS_PAGE_SIZE;
21831
21839
  const out = [];
21832
21840
  for (let page = 1; page <= GITHUB_ISSUE_COMMENTS_MAX_PAGES; page++) {
21833
21841
  const path = `repos/${slug}/issues/comments?since=${encodeURIComponent(opts.since)}&sort=created&direction=desc&per_page=${pageSize}&page=${page}`;
@@ -22065,7 +22073,7 @@ var LIST_ISSUE_FIELDS = `
22065
22073
  assignee { id name }
22066
22074
  team { id key }
22067
22075
  labels(first: 10) { nodes { name } }
22068
- cycle { name number startsAt endsAt completedAt }
22076
+ cycle { id name number startsAt endsAt completedAt }
22069
22077
  `;
22070
22078
  var ISSUE_REF_FIELDS = `
22071
22079
  id
@@ -22091,7 +22099,7 @@ var ISSUE_FIELDS = `
22091
22099
  creator { id name }
22092
22100
  team { id key name }
22093
22101
  labels(first: 50) { nodes { name } }
22094
- cycle { name number startsAt endsAt completedAt }
22102
+ cycle { id name number startsAt endsAt completedAt }
22095
22103
  project { id name }
22096
22104
  parent { ${ISSUE_REF_FIELDS} }
22097
22105
  children(first: 25) { nodes { ${ISSUE_REF_FIELDS} } }
@@ -22176,6 +22184,19 @@ query SideboardTeams {
22176
22184
  key
22177
22185
  name
22178
22186
  states(first: 50) { nodes { id name type } }
22187
+ activeCycle { id name number startsAt endsAt completedAt }
22188
+ }
22189
+ }
22190
+ }
22191
+ `;
22192
+ var TEAM_CYCLES_QUERY = `
22193
+ query SideboardTeamCycles($id: String!) {
22194
+ team(id: $id) {
22195
+ id
22196
+ key
22197
+ activeCycle { id name number startsAt endsAt completedAt }
22198
+ cycles(first: 40) {
22199
+ nodes { id name number startsAt endsAt completedAt }
22179
22200
  }
22180
22201
  }
22181
22202
  }
@@ -22268,11 +22289,26 @@ function linearCycleIsActive(cycle, now = Date.now()) {
22268
22289
  if (Number.isFinite(end) && now > end) return false;
22269
22290
  return true;
22270
22291
  }
22292
+ function cycleDisplayName(node) {
22293
+ return String(node?.name ?? (node?.number != null ? `Cycle ${node.number}` : "")).trim();
22294
+ }
22295
+ function mapNamedCycle(node) {
22296
+ if (!node?.id) return null;
22297
+ const name = cycleDisplayName(node);
22298
+ if (!name) return null;
22299
+ return {
22300
+ id: String(node.id),
22301
+ name,
22302
+ number: typeof node.number === "number" ? node.number : void 0,
22303
+ isActive: linearCycleIsActive(node)
22304
+ };
22305
+ }
22271
22306
  function mapCycle(node) {
22272
22307
  if (!node?.name && node?.number == null) return null;
22273
- const name = String(node.name ?? (node.number != null ? `Cycle ${node.number}` : "")).trim();
22308
+ const name = cycleDisplayName(node);
22274
22309
  if (!name) return null;
22275
22310
  return {
22311
+ ...node.id ? { id: String(node.id) } : {},
22276
22312
  name,
22277
22313
  number: typeof node.number === "number" ? node.number : void 0,
22278
22314
  isActive: linearCycleIsActive(node)
@@ -22411,6 +22447,10 @@ function toIssueInfo(issue) {
22411
22447
  };
22412
22448
  }
22413
22449
  function mapTeam(node) {
22450
+ const cycles = (node.cycles?.nodes ?? []).flatMap((c) => {
22451
+ const cycle = mapNamedCycle(c);
22452
+ return cycle ? [cycle] : [];
22453
+ });
22414
22454
  return {
22415
22455
  id: String(node.id ?? ""),
22416
22456
  key: String(node.key ?? ""),
@@ -22418,7 +22458,9 @@ function mapTeam(node) {
22418
22458
  states: (node.states?.nodes ?? []).flatMap((s) => {
22419
22459
  const state = mapState(s);
22420
22460
  return state ? [state] : [];
22421
- })
22461
+ }),
22462
+ activeCycle: mapNamedCycle(node.activeCycle),
22463
+ ...cycles.length ? { cycles } : {}
22422
22464
  };
22423
22465
  }
22424
22466
  function resolveLinearTeam(teams, team) {
@@ -22445,6 +22487,34 @@ function resolveLinearState(team, state) {
22445
22487
  }
22446
22488
  return found;
22447
22489
  }
22490
+ function resolveLinearCycle(team, cycle) {
22491
+ if (cycle == null) return null;
22492
+ const s = cycle.trim();
22493
+ if (!s || /^(none|clear|unschedule|null)$/i.test(s)) return null;
22494
+ if (/^(current|active|this)$/i.test(s)) {
22495
+ const active = team.activeCycle ?? team.cycles?.find((c) => c.isActive);
22496
+ if (!active?.id) {
22497
+ throw new Error(`Linear team ${team.key} has no active cycle`);
22498
+ }
22499
+ return active.id;
22500
+ }
22501
+ const lower = s.toLowerCase();
22502
+ const asNumber = Number.parseInt(s, 10);
22503
+ const seen = /* @__PURE__ */ new Set();
22504
+ const cycles = [team.activeCycle, ...team.cycles ?? []].filter(
22505
+ (c) => {
22506
+ if (!c?.id || seen.has(c.id)) return false;
22507
+ seen.add(c.id);
22508
+ return true;
22509
+ }
22510
+ );
22511
+ const found = cycles.find((c) => c.id === s) || cycles.find((c) => c.name.toLowerCase() === lower) || (Number.isInteger(asNumber) ? cycles.find((c) => c.number === asNumber) : void 0);
22512
+ if (!found) {
22513
+ const available = cycles.map((c) => c.number != null ? `${c.name} (${c.number})` : c.name).join(", ") || "(none)";
22514
+ throw new Error(`Linear cycle not found on ${team.key}: ${cycle}. Available: ${available}`);
22515
+ }
22516
+ return found.id;
22517
+ }
22448
22518
  function normalizePriority(priority) {
22449
22519
  if (priority == null) return void 0;
22450
22520
  if (!Number.isInteger(priority) || priority < 0 || priority > 4) {
@@ -22554,7 +22624,12 @@ async function listLinearCommentsSince(opts) {
22554
22624
  first,
22555
22625
  filter: {
22556
22626
  createdAt: { gte: opts.since },
22557
- issue: buildLinearIssueFilter({ assignee, query: query || void 0 })
22627
+ issue: buildLinearIssueFilter({
22628
+ assignee,
22629
+ // Search hits may match identifier/comments — do not also require
22630
+ // title/description contains when an allowlist is already applied.
22631
+ query: restrictToIssues ? void 0 : query || void 0
22632
+ })
22558
22633
  }
22559
22634
  },
22560
22635
  opts
@@ -22586,6 +22661,19 @@ async function listLinearTeams(opts) {
22586
22661
  teams: (json.teams?.nodes ?? []).map(mapTeam)
22587
22662
  };
22588
22663
  }
22664
+ async function listLinearTeamCycles(teamId, opts) {
22665
+ const json = await linearGraphql(TEAM_CYCLES_QUERY, { id: teamId }, opts);
22666
+ if (!json.team?.id) {
22667
+ throw new Error(`Linear team not found: ${teamId}`);
22668
+ }
22669
+ const mapped = mapTeam(json.team);
22670
+ return {
22671
+ id: mapped.id,
22672
+ key: mapped.key,
22673
+ activeCycle: mapped.activeCycle,
22674
+ cycles: mapped.cycles ?? []
22675
+ };
22676
+ }
22589
22677
  async function getLinearIssue(id, opts) {
22590
22678
  const issueId = id.trim();
22591
22679
  if (!issueId) throw new Error("Linear issue id is required (uuid or ENG-123)");
@@ -22641,16 +22729,28 @@ async function updateLinearIssue(input, opts) {
22641
22729
  if (input.description !== void 0) {
22642
22730
  mutationInput.description = input.description;
22643
22731
  }
22732
+ const needsTeam = Boolean(input.state?.trim()) || input.cycle !== void 0;
22733
+ const existing = needsTeam ? await getLinearIssue(issueId, opts) : void 0;
22644
22734
  if (input.state?.trim()) {
22645
- const existing = await getLinearIssue(issueId, opts);
22646
22735
  const { teams } = await listLinearTeams(opts);
22647
- const teamRef = existing.team?.key || existing.team?.id;
22736
+ const teamRef = existing?.team?.key || existing?.team?.id;
22648
22737
  const team = teamRef ? resolveLinearTeam(teams, teamRef) : void 0;
22649
22738
  if (!team?.states.length) {
22650
- throw new Error(`Linear issue ${existing.identifier} has no workflow states to resolve "${input.state}"`);
22739
+ throw new Error(`Linear issue ${existing?.identifier ?? issueId} has no workflow states to resolve "${input.state}"`);
22651
22740
  }
22652
22741
  mutationInput.stateId = resolveLinearState(team, input.state).id;
22653
22742
  }
22743
+ if (input.cycle !== void 0) {
22744
+ const teamId = existing?.team?.id;
22745
+ if (!teamId) {
22746
+ throw new Error(`Linear issue ${existing?.identifier ?? issueId} has no team to resolve a cycle`);
22747
+ }
22748
+ const cycles = await listLinearTeamCycles(teamId, opts);
22749
+ mutationInput.cycleId = resolveLinearCycle(
22750
+ { ...cycles, key: cycles.key || existing?.team?.key || teamId },
22751
+ input.cycle
22752
+ );
22753
+ }
22654
22754
  if (input.assignee !== void 0) {
22655
22755
  const assignee = input.assignee?.trim() || null;
22656
22756
  if (assignee === "me") {
@@ -22664,7 +22764,7 @@ async function updateLinearIssue(input, opts) {
22664
22764
  mutationInput.priority = normalizePriority(input.priority);
22665
22765
  }
22666
22766
  if (Object.keys(mutationInput).length === 0) {
22667
- throw new Error("linear_update_issue needs at least one of title, description, state, assignee, priority");
22767
+ throw new Error("linear_update_issue needs at least one of title, description, state, assignee, priority, cycle");
22668
22768
  }
22669
22769
  const json = await linearGraphql(ISSUE_UPDATE, { id: issueId, input: mutationInput }, opts);
22670
22770
  if (!json.issueUpdate?.success || !json.issueUpdate.issue) {
@@ -24132,7 +24232,7 @@ var prioritySchema = import_zod6.z.number().int().min(0).max(4).optional().descr
24132
24232
  function registerLinearTools(server) {
24133
24233
  server.tool(
24134
24234
  "linear_list_teams",
24135
- "List Linear teams (id, key, name) and workflow states. Use team key on create; state name or type on create/update.",
24235
+ 'List Linear teams (id, key, name), workflow states, and the active cycle. Use team key on create; state name or type on create/update; cycle name/"current"/"none" on update.',
24136
24236
  {},
24137
24237
  async () => {
24138
24238
  try {
@@ -24219,14 +24319,15 @@ function registerLinearTools(server) {
24219
24319
  );
24220
24320
  server.tool(
24221
24321
  "linear_update_issue",
24222
- "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.",
24322
+ 'Update a Linear issue (uuid or ENG-123). Pass title, description, state, assignee, priority, and/or cycle. Cycle is name, number, "current"/"active", or "none" to unschedule. 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.',
24223
24323
  {
24224
24324
  id: import_zod6.z.string(),
24225
24325
  title: import_zod6.z.string().optional(),
24226
24326
  description: import_zod6.z.string().optional(),
24227
24327
  state: import_zod6.z.string().optional(),
24228
24328
  assignee: import_zod6.z.string().nullable().optional(),
24229
- priority: prioritySchema
24329
+ priority: prioritySchema,
24330
+ cycle: import_zod6.z.string().nullable().optional().describe('Cycle name, number, "current"/"active", or "none" to unschedule.')
24230
24331
  },
24231
24332
  async (args) => {
24232
24333
  try {
@@ -37,7 +37,7 @@ import {
37
37
  syncBoardPins,
38
38
  updateSchedule,
39
39
  waitForDetachedJob
40
- } from "../chunk-72FO6OLT.js";
40
+ } from "../chunk-IAOXW6RI.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-QU663NPO.js";
49
+ } from "../chunk-GBWKMWUQ.js";
50
50
  import "../chunk-HVIEYTUK.js";
51
51
  import "../chunk-Y4MIXGPA.js";
52
52
  import {
@@ -298,7 +298,7 @@ async function listGitHubIssueCommentsSince(opts) {
298
298
  }).filter((n) => n != null)
299
299
  );
300
300
  if (restrictToIssues && allowed.size === 0) return [];
301
- const pageSize = Math.min(GITHUB_ISSUE_COMMENTS_PAGE_SIZE, Math.max(1, limit));
301
+ const pageSize = GITHUB_ISSUE_COMMENTS_PAGE_SIZE;
302
302
  const out = [];
303
303
  for (let page = 1; page <= GITHUB_ISSUE_COMMENTS_MAX_PAGES; page++) {
304
304
  const path = `repos/${slug}/issues/comments?since=${encodeURIComponent(opts.since)}&sort=created&direction=desc&per_page=${pageSize}&page=${page}`;
@@ -531,7 +531,7 @@ var LIST_ISSUE_FIELDS = `
531
531
  assignee { id name }
532
532
  team { id key }
533
533
  labels(first: 10) { nodes { name } }
534
- cycle { name number startsAt endsAt completedAt }
534
+ cycle { id name number startsAt endsAt completedAt }
535
535
  `;
536
536
  var ISSUE_REF_FIELDS = `
537
537
  id
@@ -557,7 +557,7 @@ var ISSUE_FIELDS = `
557
557
  creator { id name }
558
558
  team { id key name }
559
559
  labels(first: 50) { nodes { name } }
560
- cycle { name number startsAt endsAt completedAt }
560
+ cycle { id name number startsAt endsAt completedAt }
561
561
  project { id name }
562
562
  parent { ${ISSUE_REF_FIELDS} }
563
563
  children(first: 25) { nodes { ${ISSUE_REF_FIELDS} } }
@@ -642,6 +642,19 @@ query SideboardTeams {
642
642
  key
643
643
  name
644
644
  states(first: 50) { nodes { id name type } }
645
+ activeCycle { id name number startsAt endsAt completedAt }
646
+ }
647
+ }
648
+ }
649
+ `;
650
+ var TEAM_CYCLES_QUERY = `
651
+ query SideboardTeamCycles($id: String!) {
652
+ team(id: $id) {
653
+ id
654
+ key
655
+ activeCycle { id name number startsAt endsAt completedAt }
656
+ cycles(first: 40) {
657
+ nodes { id name number startsAt endsAt completedAt }
645
658
  }
646
659
  }
647
660
  }
@@ -734,11 +747,26 @@ function linearCycleIsActive(cycle, now = Date.now()) {
734
747
  if (Number.isFinite(end) && now > end) return false;
735
748
  return true;
736
749
  }
750
+ function cycleDisplayName(node) {
751
+ return String(node?.name ?? (node?.number != null ? `Cycle ${node.number}` : "")).trim();
752
+ }
753
+ function mapNamedCycle(node) {
754
+ if (!node?.id) return null;
755
+ const name = cycleDisplayName(node);
756
+ if (!name) return null;
757
+ return {
758
+ id: String(node.id),
759
+ name,
760
+ number: typeof node.number === "number" ? node.number : void 0,
761
+ isActive: linearCycleIsActive(node)
762
+ };
763
+ }
737
764
  function mapCycle(node) {
738
765
  if (!node?.name && node?.number == null) return null;
739
- const name = String(node.name ?? (node.number != null ? `Cycle ${node.number}` : "")).trim();
766
+ const name = cycleDisplayName(node);
740
767
  if (!name) return null;
741
768
  return {
769
+ ...node.id ? { id: String(node.id) } : {},
742
770
  name,
743
771
  number: typeof node.number === "number" ? node.number : void 0,
744
772
  isActive: linearCycleIsActive(node)
@@ -877,6 +905,10 @@ function toIssueInfo(issue) {
877
905
  };
878
906
  }
879
907
  function mapTeam(node) {
908
+ const cycles = (node.cycles?.nodes ?? []).flatMap((c) => {
909
+ const cycle = mapNamedCycle(c);
910
+ return cycle ? [cycle] : [];
911
+ });
880
912
  return {
881
913
  id: String(node.id ?? ""),
882
914
  key: String(node.key ?? ""),
@@ -884,7 +916,9 @@ function mapTeam(node) {
884
916
  states: (node.states?.nodes ?? []).flatMap((s) => {
885
917
  const state = mapState(s);
886
918
  return state ? [state] : [];
887
- })
919
+ }),
920
+ activeCycle: mapNamedCycle(node.activeCycle),
921
+ ...cycles.length ? { cycles } : {}
888
922
  };
889
923
  }
890
924
  function resolveLinearTeam(teams, team) {
@@ -911,6 +945,34 @@ function resolveLinearState(team, state) {
911
945
  }
912
946
  return found;
913
947
  }
948
+ function resolveLinearCycle(team, cycle) {
949
+ if (cycle == null) return null;
950
+ const s = cycle.trim();
951
+ if (!s || /^(none|clear|unschedule|null)$/i.test(s)) return null;
952
+ if (/^(current|active|this)$/i.test(s)) {
953
+ const active = team.activeCycle ?? team.cycles?.find((c) => c.isActive);
954
+ if (!active?.id) {
955
+ throw new Error(`Linear team ${team.key} has no active cycle`);
956
+ }
957
+ return active.id;
958
+ }
959
+ const lower = s.toLowerCase();
960
+ const asNumber = Number.parseInt(s, 10);
961
+ const seen = /* @__PURE__ */ new Set();
962
+ const cycles = [team.activeCycle, ...team.cycles ?? []].filter(
963
+ (c) => {
964
+ if (!c?.id || seen.has(c.id)) return false;
965
+ seen.add(c.id);
966
+ return true;
967
+ }
968
+ );
969
+ const found = cycles.find((c) => c.id === s) || cycles.find((c) => c.name.toLowerCase() === lower) || (Number.isInteger(asNumber) ? cycles.find((c) => c.number === asNumber) : void 0);
970
+ if (!found) {
971
+ const available = cycles.map((c) => c.number != null ? `${c.name} (${c.number})` : c.name).join(", ") || "(none)";
972
+ throw new Error(`Linear cycle not found on ${team.key}: ${cycle}. Available: ${available}`);
973
+ }
974
+ return found.id;
975
+ }
914
976
  function normalizePriority(priority) {
915
977
  if (priority == null) return void 0;
916
978
  if (!Number.isInteger(priority) || priority < 0 || priority > 4) {
@@ -1020,7 +1082,12 @@ async function listLinearCommentsSince(opts) {
1020
1082
  first,
1021
1083
  filter: {
1022
1084
  createdAt: { gte: opts.since },
1023
- issue: buildLinearIssueFilter({ assignee, query: query || void 0 })
1085
+ issue: buildLinearIssueFilter({
1086
+ assignee,
1087
+ // Search hits may match identifier/comments — do not also require
1088
+ // title/description contains when an allowlist is already applied.
1089
+ query: restrictToIssues ? void 0 : query || void 0
1090
+ })
1024
1091
  }
1025
1092
  },
1026
1093
  opts
@@ -1052,6 +1119,19 @@ async function listLinearTeams(opts) {
1052
1119
  teams: (json.teams?.nodes ?? []).map(mapTeam)
1053
1120
  };
1054
1121
  }
1122
+ async function listLinearTeamCycles(teamId, opts) {
1123
+ const json = await linearGraphql(TEAM_CYCLES_QUERY, { id: teamId }, opts);
1124
+ if (!json.team?.id) {
1125
+ throw new Error(`Linear team not found: ${teamId}`);
1126
+ }
1127
+ const mapped = mapTeam(json.team);
1128
+ return {
1129
+ id: mapped.id,
1130
+ key: mapped.key,
1131
+ activeCycle: mapped.activeCycle,
1132
+ cycles: mapped.cycles ?? []
1133
+ };
1134
+ }
1055
1135
  async function getLinearIssue(id, opts) {
1056
1136
  const issueId = id.trim();
1057
1137
  if (!issueId) throw new Error("Linear issue id is required (uuid or ENG-123)");
@@ -1107,16 +1187,28 @@ async function updateLinearIssue(input, opts) {
1107
1187
  if (input.description !== void 0) {
1108
1188
  mutationInput.description = input.description;
1109
1189
  }
1190
+ const needsTeam = Boolean(input.state?.trim()) || input.cycle !== void 0;
1191
+ const existing = needsTeam ? await getLinearIssue(issueId, opts) : void 0;
1110
1192
  if (input.state?.trim()) {
1111
- const existing = await getLinearIssue(issueId, opts);
1112
1193
  const { teams } = await listLinearTeams(opts);
1113
- const teamRef = existing.team?.key || existing.team?.id;
1194
+ const teamRef = existing?.team?.key || existing?.team?.id;
1114
1195
  const team = teamRef ? resolveLinearTeam(teams, teamRef) : void 0;
1115
1196
  if (!team?.states.length) {
1116
- throw new Error(`Linear issue ${existing.identifier} has no workflow states to resolve "${input.state}"`);
1197
+ throw new Error(`Linear issue ${existing?.identifier ?? issueId} has no workflow states to resolve "${input.state}"`);
1117
1198
  }
1118
1199
  mutationInput.stateId = resolveLinearState(team, input.state).id;
1119
1200
  }
1201
+ if (input.cycle !== void 0) {
1202
+ const teamId = existing?.team?.id;
1203
+ if (!teamId) {
1204
+ throw new Error(`Linear issue ${existing?.identifier ?? issueId} has no team to resolve a cycle`);
1205
+ }
1206
+ const cycles = await listLinearTeamCycles(teamId, opts);
1207
+ mutationInput.cycleId = resolveLinearCycle(
1208
+ { ...cycles, key: cycles.key || existing?.team?.key || teamId },
1209
+ input.cycle
1210
+ );
1211
+ }
1120
1212
  if (input.assignee !== void 0) {
1121
1213
  const assignee = input.assignee?.trim() || null;
1122
1214
  if (assignee === "me") {
@@ -1130,7 +1222,7 @@ async function updateLinearIssue(input, opts) {
1130
1222
  mutationInput.priority = normalizePriority(input.priority);
1131
1223
  }
1132
1224
  if (Object.keys(mutationInput).length === 0) {
1133
- throw new Error("linear_update_issue needs at least one of title, description, state, assignee, priority");
1225
+ throw new Error("linear_update_issue needs at least one of title, description, state, assignee, priority, cycle");
1134
1226
  }
1135
1227
  const json = await linearGraphql(ISSUE_UPDATE, { id: issueId, input: mutationInput }, opts);
1136
1228
  if (!json.issueUpdate?.success || !json.issueUpdate.issue) {
@@ -2573,7 +2665,7 @@ var prioritySchema = z6.number().int().min(0).max(4).optional().describe("0 none
2573
2665
  function registerLinearTools(server) {
2574
2666
  server.tool(
2575
2667
  "linear_list_teams",
2576
- "List Linear teams (id, key, name) and workflow states. Use team key on create; state name or type on create/update.",
2668
+ 'List Linear teams (id, key, name), workflow states, and the active cycle. Use team key on create; state name or type on create/update; cycle name/"current"/"none" on update.',
2577
2669
  {},
2578
2670
  async () => {
2579
2671
  try {
@@ -2660,14 +2752,15 @@ function registerLinearTools(server) {
2660
2752
  );
2661
2753
  server.tool(
2662
2754
  "linear_update_issue",
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.",
2755
+ 'Update a Linear issue (uuid or ENG-123). Pass title, description, state, assignee, priority, and/or cycle. Cycle is name, number, "current"/"active", or "none" to unschedule. 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.',
2664
2756
  {
2665
2757
  id: z6.string(),
2666
2758
  title: z6.string().optional(),
2667
2759
  description: z6.string().optional(),
2668
2760
  state: z6.string().optional(),
2669
2761
  assignee: z6.string().nullable().optional(),
2670
- priority: prioritySchema
2762
+ priority: prioritySchema,
2763
+ cycle: z6.string().nullable().optional().describe('Cycle name, number, "current"/"active", or "none" to unschedule.')
2671
2764
  },
2672
2765
  async (args) => {
2673
2766
  try {
@@ -8,8 +8,8 @@ import {
8
8
  resolveSendFollowUp,
9
9
  startOrchestration,
10
10
  waitForPidExit
11
- } from "./chunk-72FO6OLT.js";
12
- import "./chunk-QU663NPO.js";
11
+ } from "./chunk-IAOXW6RI.js";
12
+ import "./chunk-GBWKMWUQ.js";
13
13
  import "./chunk-HVIEYTUK.js";
14
14
  import "./chunk-Y4MIXGPA.js";
15
15
  import "./chunk-VKQGAWHY.js";
@@ -6,9 +6,9 @@ import {
6
6
  resolveSendFollowUp,
7
7
  startOrchestration,
8
8
  waitForPidExit
9
- } from "./chunk-TBG2Z3ZV.js";
9
+ } from "./chunk-Z6TW3A5P.js";
10
10
  import "./chunk-DJU54RYW.js";
11
- import "./chunk-ISOCLQEX.js";
11
+ import "./chunk-OLDZXO53.js";
12
12
  import "./chunk-VIODP5PT.js";
13
13
  import "./chunk-WXKXSZN4.js";
14
14
  import "./chunk-6K64FJDD.js";
@@ -16,7 +16,7 @@ import "./chunk-IEEAVWAP.js";
16
16
  import "./chunk-AN3THU7F.js";
17
17
  import "./chunk-N7WZ6NIN.js";
18
18
  import "./chunk-AXQ4ZWHK.js";
19
- import "./chunk-ZSFQLR7Y.js";
19
+ import "./chunk-FY64F2ZQ.js";
20
20
  import "./chunk-J7LQVLFQ.js";
21
21
  import "./chunk-4TR3HZFT.js";
22
22
  import "./chunk-AO35MGOE.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sideboard-ai/core",
3
- "version": "0.1.188",
3
+ "version": "0.1.190",
4
4
  "description": "Sideboard core — orchestration, agents, git worktrees, MCP server",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",