@sideboard-ai/core 0.1.189 → 0.1.191

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
 
@@ -15185,13 +15193,17 @@ var init_home_board = __esm({
15185
15193
  });
15186
15194
 
15187
15195
  // src/integrations/abletime-mcp.ts
15196
+ function abletimeToolUsesPm(name) {
15197
+ return ABLETIME_PM_TOOL_NAMES.includes(name);
15198
+ }
15188
15199
  function normalizeAbleTimeHost(raw) {
15189
15200
  const trimmed = raw?.trim() || DEFAULT_ABLETIME_HOST;
15190
15201
  const withScheme = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
15191
15202
  return withScheme.replace(/\/+$/, "");
15192
15203
  }
15193
- function abletimeMcpUrl(host) {
15194
- return `${normalizeAbleTimeHost(host)}${ABLETIME_MCP_PATH}`;
15204
+ function abletimeMcpUrl(host, opts) {
15205
+ const path = opts?.pm ? ABLETIME_MCP_PM_PATH : ABLETIME_MCP_PATH;
15206
+ return `${normalizeAbleTimeHost(host)}${path}`;
15195
15207
  }
15196
15208
  function rewriteAbleTimeError(message) {
15197
15209
  if (/INTEGRATION_AGENT_ACCESS_DISABLED|agent access/i.test(message)) {
@@ -15269,7 +15281,8 @@ async function abletimeMcpRequest(method, params, opts) {
15269
15281
  throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
15270
15282
  }
15271
15283
  const url = abletimeMcpUrl(
15272
- opts?.host ?? (opts?.token ? DEFAULT_ABLETIME_HOST : getAbleTimeHost())
15284
+ opts?.host ?? (opts?.token ? DEFAULT_ABLETIME_HOST : getAbleTimeHost()),
15285
+ { pm: opts?.pm }
15273
15286
  );
15274
15287
  const res = await httpFetch(url, {
15275
15288
  method: "POST",
@@ -15333,7 +15346,8 @@ async function callAbleTimeTool(name, args = {}, opts) {
15333
15346
  if (!opts?.token && !isAbleTimeConnected()) {
15334
15347
  throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
15335
15348
  }
15336
- const call = () => abletimeMcpRequest("tools/call", { name, arguments: args }, opts).then((result) => {
15349
+ const requestOpts = { ...opts, pm: opts?.pm ?? abletimeToolUsesPm(name) };
15350
+ const call = () => abletimeMcpRequest("tools/call", { name, arguments: args }, requestOpts).then((result) => {
15337
15351
  return unwrapToolResult(result);
15338
15352
  });
15339
15353
  try {
@@ -15341,13 +15355,13 @@ async function callAbleTimeTool(name, args = {}, opts) {
15341
15355
  } catch (err) {
15342
15356
  const message = err instanceof Error ? err.message : String(err);
15343
15357
  if (/initialize|session|not initialized|-32000/i.test(message)) {
15344
- await initializeIfNeeded(opts);
15358
+ await initializeIfNeeded(requestOpts);
15345
15359
  return await call();
15346
15360
  }
15347
15361
  throw err instanceof Error ? new Error(rewriteAbleTimeError(err.message)) : err;
15348
15362
  }
15349
15363
  }
15350
- var DEFAULT_ABLETIME_HOST, ABLETIME_MCP_PATH, MCP_PROTOCOL_VERSION;
15364
+ var DEFAULT_ABLETIME_HOST, ABLETIME_MCP_PATH, ABLETIME_MCP_PM_PATH, MCP_PROTOCOL_VERSION, ABLETIME_PM_TOOL_NAMES;
15351
15365
  var init_abletime_mcp = __esm({
15352
15366
  "src/integrations/abletime-mcp.ts"() {
15353
15367
  "use strict";
@@ -15355,13 +15369,27 @@ var init_abletime_mcp = __esm({
15355
15369
  init_app_settings();
15356
15370
  DEFAULT_ABLETIME_HOST = "https://track.abletime.com";
15357
15371
  ABLETIME_MCP_PATH = "/api/public/v2/mcp";
15372
+ ABLETIME_MCP_PM_PATH = "/api/public/v2/mcp/pm";
15358
15373
  MCP_PROTOCOL_VERSION = "2025-03-26";
15374
+ ABLETIME_PM_TOOL_NAMES = [
15375
+ "set_task_assignee",
15376
+ "set_task_priority",
15377
+ "set_task_stage",
15378
+ "set_task_dependency",
15379
+ "set_task_blocked",
15380
+ "set_task_locked",
15381
+ "set_task_schedule",
15382
+ "clear_task_schedule",
15383
+ "archive_task",
15384
+ "delete_task"
15385
+ ];
15359
15386
  }
15360
15387
  });
15361
15388
 
15362
15389
  // src/integrations/abletime.ts
15363
15390
  var abletime_exports = {};
15364
15391
  __export(abletime_exports, {
15392
+ ableTimeDependencyTarget: () => ableTimeDependencyTarget,
15365
15393
  commentAbleTimeTask: () => commentAbleTimeTask,
15366
15394
  createAbleTimeTask: () => createAbleTimeTask,
15367
15395
  ensureAbleTimeTask: () => ensureAbleTimeTask,
@@ -15618,39 +15646,122 @@ async function commentAbleTimeTask(input, opts) {
15618
15646
  body: rec ? firstString(rec, ["body", "text", "comment"]) || body : body
15619
15647
  };
15620
15648
  }
15649
+ function isAbleTimeNoneToken(value) {
15650
+ if (value == null) return true;
15651
+ const s = value.trim();
15652
+ return !s || /^(none|clear|unschedule|null)$/i.test(s);
15653
+ }
15654
+ function taskIdArgs(id) {
15655
+ return { id, task_id: id, task: id };
15656
+ }
15657
+ function ableTimeDependencyTarget(thisId, type, relatedId) {
15658
+ const s = type.trim().toLowerCase().replace(/[\s_-]+/g, "");
15659
+ if (s === "none" || s === "clear" || s === "null") {
15660
+ return { taskId: thisId, dependsOn: null };
15661
+ }
15662
+ if (s === "blockedby" || s === "blocked" || s === "dependson" || s === "waiton") {
15663
+ return { taskId: thisId, dependsOn: relatedId };
15664
+ }
15665
+ if (s === "blocks" || s === "blocking") {
15666
+ return { taskId: relatedId, dependsOn: thisId };
15667
+ }
15668
+ throw new Error(
15669
+ `AbleTime relation type must be blocks or blockedBy (got ${type}). AbleTime models one wait-on dependency.`
15670
+ );
15671
+ }
15672
+ async function setAbleTimeTaskDependency(taskId, dependsOn, opts) {
15673
+ const args = {
15674
+ ...taskIdArgs(taskId)
15675
+ };
15676
+ if (dependsOn) {
15677
+ args.depends_on = dependsOn;
15678
+ args.depends_on_id = dependsOn;
15679
+ args.dependency = dependsOn;
15680
+ args.dependency_id = dependsOn;
15681
+ args.blocked_by = dependsOn;
15682
+ args.wait_on = dependsOn;
15683
+ args.related_task_id = dependsOn;
15684
+ } else {
15685
+ args.depends_on = null;
15686
+ args.dependency = null;
15687
+ args.blocked_by = null;
15688
+ args.clear = true;
15689
+ }
15690
+ await callAbleTimeTool("set_task_dependency", args, opts);
15691
+ }
15621
15692
  async function updateAbleTimeTask(input, opts) {
15622
15693
  const id = input.id.trim();
15623
15694
  if (!id) throw new Error("AbleTime task id is required");
15624
15695
  const title = input.title?.trim();
15625
15696
  const description = input.description;
15626
15697
  const state = input.state?.trim();
15627
- if (!title && description === void 0 && !state) {
15628
- throw new Error("abletime_update_task needs at least one of title, description, state");
15698
+ const addRelations = input.relations?.length ? input.relations : void 0;
15699
+ const removeRelations = input.removeRelations?.length ? input.removeRelations : void 0;
15700
+ const updateArgs = { ...taskIdArgs(id) };
15701
+ if (title) updateArgs.title = title;
15702
+ if (description !== void 0) updateArgs.description = description;
15703
+ if (input.labels !== void 0) {
15704
+ updateArgs.tags = input.labels;
15705
+ updateArgs.labels = input.labels;
15706
+ updateArgs.tag_names = input.labels;
15707
+ }
15708
+ if (input.project !== void 0) {
15709
+ const projectName = (input.project ?? "").trim();
15710
+ if (isAbleTimeNoneToken(projectName)) {
15711
+ throw new Error("AbleTime tasks stay in a project \u2014 pass a project name or id");
15712
+ }
15713
+ const project = await resolveAbleTimeProject(projectName, opts);
15714
+ updateArgs.project = project.id;
15715
+ updateArgs.project_id = project.id;
15716
+ }
15717
+ if (input.parent !== void 0) {
15718
+ if (isAbleTimeNoneToken(input.parent)) {
15719
+ updateArgs.parent = null;
15720
+ updateArgs.parent_id = null;
15721
+ updateArgs.related_task_id = null;
15722
+ } else {
15723
+ const parent = (input.parent ?? "").trim();
15724
+ updateArgs.parent = parent;
15725
+ updateArgs.parent_id = parent;
15726
+ updateArgs.related_task_id = parent;
15727
+ }
15728
+ }
15729
+ const hasUpdateFields = Boolean(title) || description !== void 0 || input.labels !== void 0 || input.project !== void 0 || input.parent !== void 0;
15730
+ if (!hasUpdateFields && !state && !addRelations && !removeRelations) {
15731
+ throw new Error(
15732
+ "abletime_update_task needs at least one of title, description, state, labels, project, parent, relations, removeRelations"
15733
+ );
15629
15734
  }
15630
15735
  if (state) {
15631
15736
  await callAbleTimeTool(
15632
15737
  "set_task_state",
15633
- { id, task_id: id, task: id, state },
15738
+ { ...taskIdArgs(id), state },
15634
15739
  opts
15635
15740
  ).catch(async () => {
15636
15741
  await callAbleTimeTool(
15637
15742
  "update_task",
15638
- { id, task_id: id, title, description, state },
15743
+ { ...updateArgs, state },
15639
15744
  opts
15640
15745
  );
15641
15746
  });
15642
15747
  }
15643
- if (title || description !== void 0) {
15644
- await callAbleTimeTool(
15645
- "update_task",
15646
- {
15647
- id,
15648
- task_id: id,
15649
- ...title ? { title } : {},
15650
- ...description !== void 0 ? { description } : {}
15651
- },
15652
- opts
15653
- );
15748
+ if (hasUpdateFields) {
15749
+ await callAbleTimeTool("update_task", updateArgs, opts);
15750
+ }
15751
+ for (const rel of removeRelations ?? []) {
15752
+ const related = rel.issue.trim() || id;
15753
+ const target = ableTimeDependencyTarget(id, rel.type, related);
15754
+ await setAbleTimeTaskDependency(target.taskId, null, opts);
15755
+ }
15756
+ for (const rel of addRelations ?? []) {
15757
+ const related = rel.issue.trim();
15758
+ if (!related) throw new Error("AbleTime relations.issue is required (id or CRM-232)");
15759
+ const target = ableTimeDependencyTarget(id, rel.type, related);
15760
+ if (!target.dependsOn) {
15761
+ await setAbleTimeTaskDependency(target.taskId, null, opts);
15762
+ continue;
15763
+ }
15764
+ await setAbleTimeTaskDependency(target.taskId, target.dependsOn, opts);
15654
15765
  }
15655
15766
  return getAbleTimeTask(id, opts);
15656
15767
  }
@@ -18299,17 +18410,17 @@ function formatIssueToolsDirective(opts) {
18299
18410
  ];
18300
18411
  if (opts.linear) {
18301
18412
  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, cycle), `linear_create_issue` (pass `parent` for spin-offs; call `linear_list_teams` first). Scope errors: reconnect Linear in Settings \u2192 Issues."
18413
+ "- 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, labels, project, parent, relations), `linear_create_issue` (pass `parent` for spin-offs; call `linear_list_teams` first). Scope errors: reconnect Linear in Settings \u2192 Issues."
18303
18414
  );
18304
18415
  }
18305
18416
  if (github) {
18306
18417
  lines.push(
18307
- "- GitHub: `github_search_issues` (assignee=me; `updatedSince` for inbox updates + comments), `github_get_issue` (comments; pass `include=full` if crush.truncated), `github_comment`, `github_update_issue` (state open|closed), `github_create_issue` (pass `parent` for spin-offs). Uses Account `gh`."
18418
+ "- GitHub: `github_search_issues` (assignee=me; `updatedSince` for inbox updates + comments), `github_get_issue` (comments; pass `include=full` if crush.truncated), `github_comment`, `github_update_issue` (state open|closed, labels, project, parent, relations), `github_create_issue` (pass `parent` for spin-offs). Uses Account `gh`."
18308
18419
  );
18309
18420
  }
18310
18421
  if (opts.abletime) {
18311
18422
  lines.push(
18312
- "- AbleTime: `abletime_list_tasks` / `abletime_search_tasks` (`updatedSince` for inbox updates + comments), `abletime_get_task` (comments; pass `include=full` if crush.truncated), `abletime_comment`, `abletime_update_task` (state), `abletime_create_task` (pass `parent` for spin-offs; `abletime_list_projects` if needed)."
18423
+ "- AbleTime: `abletime_list_tasks` / `abletime_search_tasks` (`updatedSince` for inbox updates + comments), `abletime_get_task` (comments; pass `include=full` if crush.truncated), `abletime_comment`, `abletime_update_task` (state, labels, project, parent, relations), `abletime_create_task` (pass `parent` for spin-offs; `abletime_list_projects` if needed)."
18313
18424
  );
18314
18425
  }
18315
18426
  lines.push(
@@ -21827,7 +21938,7 @@ async function listGitHubIssueCommentsSince(opts) {
21827
21938
  }).filter((n) => n != null)
21828
21939
  );
21829
21940
  if (restrictToIssues && allowed.size === 0) return [];
21830
- const pageSize = Math.min(GITHUB_ISSUE_COMMENTS_PAGE_SIZE, Math.max(1, limit));
21941
+ const pageSize = GITHUB_ISSUE_COMMENTS_PAGE_SIZE;
21831
21942
  const out = [];
21832
21943
  for (let page = 1; page <= GITHUB_ISSUE_COMMENTS_MAX_PAGES; page++) {
21833
21944
  const path = `repos/${slug}/issues/comments?since=${encodeURIComponent(opts.since)}&sort=created&direction=desc&per_page=${pageSize}&page=${page}`;
@@ -21895,13 +22006,78 @@ async function commentGitHubIssue(input, opts) {
21895
22006
  const url = stdout.trim().split(/\s+/).find((part) => /^https?:\/\//i.test(part));
21896
22007
  return { body, url };
21897
22008
  }
22009
+ function isGitHubNoneToken(value) {
22010
+ if (value == null) return true;
22011
+ const s = value.trim();
22012
+ return !s || /^(none|clear|unschedule|null)$/i.test(s);
22013
+ }
22014
+ function githubRelationEditFlag(type, remove = false) {
22015
+ const s = type.trim().toLowerCase().replace(/[\s_-]+/g, "");
22016
+ if (s === "blockedby" || s === "blocked" || s === "dependson") {
22017
+ return remove ? "--remove-blocked-by" : "--add-blocked-by";
22018
+ }
22019
+ if (s === "blocks" || s === "blocking") {
22020
+ return remove ? "--remove-blocking" : "--add-blocking";
22021
+ }
22022
+ throw new Error(
22023
+ `GitHub relation type must be blocks or blockedBy (got ${type}). Parent is a separate parent= field.`
22024
+ );
22025
+ }
22026
+ function labelsEqual(a, b) {
22027
+ return a.trim().toLowerCase() === b.trim().toLowerCase();
22028
+ }
22029
+ function parseProjectTitles(raw) {
22030
+ const out = [];
22031
+ const seen = /* @__PURE__ */ new Set();
22032
+ const push = (name) => {
22033
+ const title = name.trim();
22034
+ const key = title.toLowerCase();
22035
+ if (!title || seen.has(key)) return;
22036
+ seen.add(key);
22037
+ out.push(title);
22038
+ };
22039
+ const fromItems = raw.projectItems ?? raw.projects ?? raw.projectItemsV2;
22040
+ if (Array.isArray(fromItems)) {
22041
+ for (const item of fromItems) {
22042
+ if (typeof item === "string") {
22043
+ push(item);
22044
+ continue;
22045
+ }
22046
+ if (!item || typeof item !== "object") continue;
22047
+ const rec = item;
22048
+ const nested = rec.project && typeof rec.project === "object" ? rec.project : rec;
22049
+ const title = String(nested.title ?? nested.name ?? rec.title ?? rec.name ?? "").trim();
22050
+ if (title) push(title);
22051
+ }
22052
+ }
22053
+ return out;
22054
+ }
22055
+ async function listGitHubIssueProjects(number, cwd, repoArgs) {
22056
+ const result = await gh(
22057
+ ["issue", "view", String(number), ...repoArgs, "--json", "projectItems,projects"],
22058
+ cwd,
22059
+ { reject: false }
22060
+ );
22061
+ if (result.exitCode !== 0 || !result.stdout.trim()) return [];
22062
+ try {
22063
+ const parsed = JSON.parse(result.stdout);
22064
+ return parseProjectTitles(parsed);
22065
+ } catch {
22066
+ return [];
22067
+ }
22068
+ }
21898
22069
  async function updateGitHubIssue(input, opts) {
21899
22070
  const number = parseGitHubIssueNumber(input.id);
21900
22071
  const title = input.title?.trim();
21901
22072
  const body = input.body;
21902
22073
  const state = input.state?.trim().toLowerCase();
21903
- if (!title && body === void 0 && !state) {
21904
- throw new Error("github_update_issue needs at least one of title, body, state");
22074
+ const addRelations = input.relations?.length ? input.relations : void 0;
22075
+ const removeRelations = input.removeRelations?.length ? input.removeRelations : void 0;
22076
+ const hasEditFields = Boolean(title) || body !== void 0 || input.labels !== void 0 || input.project !== void 0 || input.parent !== void 0 || Boolean(addRelations) || Boolean(removeRelations);
22077
+ if (!hasEditFields && !state) {
22078
+ throw new Error(
22079
+ "github_update_issue needs at least one of title, body, state, labels, project, parent, relations, removeRelations"
22080
+ );
21905
22081
  }
21906
22082
  const { cwd, repoArgs } = await resolveGitHubIssueRepo(opts?.repoPath);
21907
22083
  if (state === "closed" || state === "close") {
@@ -21917,10 +22093,46 @@ async function updateGitHubIssue(input, opts) {
21917
22093
  } else if (state) {
21918
22094
  throw new Error(`GitHub issue state must be open or closed (got ${input.state})`);
21919
22095
  }
21920
- if (title || body !== void 0) {
21921
- const args = ["issue", "edit", String(number), ...repoArgs];
21922
- if (title) args.push("--title", title);
21923
- if (body !== void 0) args.push("--body", body);
22096
+ const args = ["issue", "edit", String(number), ...repoArgs];
22097
+ if (title) args.push("--title", title);
22098
+ if (body !== void 0) args.push("--body", body);
22099
+ if (input.labels !== void 0) {
22100
+ const existing = await getGitHubIssue(String(number), opts);
22101
+ const desired = input.labels.map((name) => name.trim()).filter(Boolean);
22102
+ const add = desired.filter((name) => !existing.labels.some((cur) => labelsEqual(cur, name)));
22103
+ const remove = existing.labels.filter((cur) => !desired.some((name) => labelsEqual(cur, name)));
22104
+ if (add.length) args.push("--add-label", add.join(","));
22105
+ if (remove.length) args.push("--remove-label", remove.join(","));
22106
+ }
22107
+ if (input.project !== void 0) {
22108
+ const current = await listGitHubIssueProjects(number, cwd, repoArgs);
22109
+ if (isGitHubNoneToken(input.project)) {
22110
+ for (const name of current) args.push("--remove-project", name);
22111
+ } else {
22112
+ const wanted = (input.project ?? "").trim();
22113
+ if (!current.some((name) => labelsEqual(name, wanted))) {
22114
+ args.push("--add-project", wanted);
22115
+ }
22116
+ for (const name of current) {
22117
+ if (!labelsEqual(name, wanted)) args.push("--remove-project", name);
22118
+ }
22119
+ }
22120
+ }
22121
+ if (input.parent !== void 0) {
22122
+ if (isGitHubNoneToken(input.parent)) {
22123
+ args.push("--remove-parent");
22124
+ } else {
22125
+ args.push("--parent", String(parseGitHubIssueNumber(input.parent ?? "")));
22126
+ }
22127
+ }
22128
+ for (const rel of removeRelations ?? []) {
22129
+ args.push(githubRelationEditFlag(rel.type, true), String(parseGitHubIssueNumber(rel.issue)));
22130
+ }
22131
+ for (const rel of addRelations ?? []) {
22132
+ args.push(githubRelationEditFlag(rel.type, false), String(parseGitHubIssueNumber(rel.issue)));
22133
+ }
22134
+ const editChanged = args.length > 3 + repoArgs.length;
22135
+ if (editChanged) {
21924
22136
  requireGhOk(await gh(args, cwd, { reject: false }), `GitHub edit #${number}`);
21925
22137
  }
21926
22138
  return getGitHubIssue(String(number), opts);
@@ -22097,12 +22309,14 @@ var ISSUE_FIELDS = `
22097
22309
  children(first: 25) { nodes { ${ISSUE_REF_FIELDS} } }
22098
22310
  relations(first: 25) {
22099
22311
  nodes {
22312
+ id
22100
22313
  type
22101
22314
  relatedIssue { ${ISSUE_REF_FIELDS} }
22102
22315
  }
22103
22316
  }
22104
22317
  inverseRelations(first: 25) {
22105
22318
  nodes {
22319
+ id
22106
22320
  type
22107
22321
  issue { ${ISSUE_REF_FIELDS} }
22108
22322
  }
@@ -22222,6 +22436,35 @@ mutation SideboardCommentCreate($input: CommentCreateInput!) {
22222
22436
  }
22223
22437
  }
22224
22438
  `;
22439
+ var LABELS_QUERY = `
22440
+ query SideboardLabels($first: Int!) {
22441
+ issueLabels(first: $first) {
22442
+ nodes { id name }
22443
+ }
22444
+ }
22445
+ `;
22446
+ var PROJECTS_QUERY = `
22447
+ query SideboardProjects($first: Int!, $filter: ProjectFilter) {
22448
+ projects(first: $first, filter: $filter) {
22449
+ nodes { id name slugId }
22450
+ }
22451
+ }
22452
+ `;
22453
+ var ISSUE_RELATION_CREATE = `
22454
+ mutation SideboardIssueRelationCreate($input: IssueRelationCreateInput!) {
22455
+ issueRelationCreate(input: $input) {
22456
+ success
22457
+ issueRelation { id type }
22458
+ }
22459
+ }
22460
+ `;
22461
+ var ISSUE_RELATION_DELETE = `
22462
+ mutation SideboardIssueRelationDelete($id: String!) {
22463
+ issueRelationDelete(id: $id) {
22464
+ success
22465
+ }
22466
+ }
22467
+ `;
22225
22468
  function rewriteLinearError(message) {
22226
22469
  if (/scope|permission|not (authorized|allowed)|unauthorized|insufficient/i.test(message)) {
22227
22470
  return `${message} \u2014 Disconnect and Connect Linear in Account settings to grant write access.`;
@@ -22341,12 +22584,18 @@ function mapRelations(node) {
22341
22584
  for (const rel of node.relations?.nodes ?? []) {
22342
22585
  const issue = mapIssueRef(rel.relatedIssue);
22343
22586
  if (!issue) continue;
22344
- out.push({ type: String(rel.type ?? "related"), issue });
22587
+ const id = rel.id?.trim();
22588
+ out.push({ type: String(rel.type ?? "related"), issue, ...id ? { id } : {} });
22345
22589
  }
22346
22590
  for (const rel of node.inverseRelations?.nodes ?? []) {
22347
22591
  const issue = mapIssueRef(rel.issue);
22348
22592
  if (!issue) continue;
22349
- out.push({ type: flipLinearRelationType(String(rel.type ?? "related")), issue });
22593
+ const id = rel.id?.trim();
22594
+ out.push({
22595
+ type: flipLinearRelationType(String(rel.type ?? "related")),
22596
+ issue,
22597
+ ...id ? { id } : {}
22598
+ });
22350
22599
  }
22351
22600
  return out;
22352
22601
  }
@@ -22492,7 +22741,14 @@ function resolveLinearCycle(team, cycle) {
22492
22741
  }
22493
22742
  const lower = s.toLowerCase();
22494
22743
  const asNumber = Number.parseInt(s, 10);
22495
- const cycles = team.cycles ?? [];
22744
+ const seen = /* @__PURE__ */ new Set();
22745
+ const cycles = [team.activeCycle, ...team.cycles ?? []].filter(
22746
+ (c) => {
22747
+ if (!c?.id || seen.has(c.id)) return false;
22748
+ seen.add(c.id);
22749
+ return true;
22750
+ }
22751
+ );
22496
22752
  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);
22497
22753
  if (!found) {
22498
22754
  const available = cycles.map((c) => c.number != null ? `${c.name} (${c.number})` : c.name).join(", ") || "(none)";
@@ -22500,6 +22756,148 @@ function resolveLinearCycle(team, cycle) {
22500
22756
  }
22501
22757
  return found.id;
22502
22758
  }
22759
+ var LINEAR_NONE_RE = /^(none|clear|unschedule|null)$/i;
22760
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
22761
+ function isLinearNoneToken(value) {
22762
+ if (value == null) return true;
22763
+ const s = value.trim();
22764
+ return !s || LINEAR_NONE_RE.test(s);
22765
+ }
22766
+ function resolveLinearLabel(labels, name) {
22767
+ const s = name.trim();
22768
+ if (!s) throw new Error("Linear label is empty");
22769
+ const lower = s.toLowerCase();
22770
+ const found = labels.find((x) => x.id === s) || labels.find((x) => x.name.toLowerCase() === lower);
22771
+ if (!found) {
22772
+ const available = labels.map((x) => x.name).filter(Boolean).slice(0, 20).join(", ") || "(none)";
22773
+ throw new Error(`Linear label not found: ${name}. Available: ${available}`);
22774
+ }
22775
+ return found;
22776
+ }
22777
+ function resolveLinearProject(projects, project) {
22778
+ if (isLinearNoneToken(project)) return null;
22779
+ const s = (project ?? "").trim();
22780
+ const lower = s.toLowerCase();
22781
+ const found = projects.find((x) => x.id === s) || projects.find((x) => x.name.toLowerCase() === lower) || projects.find((x) => x.slugId?.toLowerCase() === lower);
22782
+ if (!found) {
22783
+ const available = projects.map((x) => x.name).filter(Boolean).slice(0, 20).join(", ") || "(none)";
22784
+ throw new Error(`Linear project not found: ${project}. Available: ${available}`);
22785
+ }
22786
+ return found.id;
22787
+ }
22788
+ function normalizeLinearRelationType(type) {
22789
+ const s = type.trim().toLowerCase().replace(/[\s_-]+/g, "");
22790
+ if (s === "blocks" || s === "block") return "blocks";
22791
+ if (s === "blockedby" || s === "blocked") return "blockedBy";
22792
+ if (s === "related" || s === "relates" || s === "relatesto") return "related";
22793
+ if (s === "duplicate" || s === "dup") return "duplicate";
22794
+ if (s === "duplicateof" || s === "original") return "duplicateOf";
22795
+ if (s === "similar") return "similar";
22796
+ throw new Error(
22797
+ `Linear relation type must be blocks, blockedBy, related, duplicate, or duplicateOf (got ${type})`
22798
+ );
22799
+ }
22800
+ function linearRelationCreateInput(thisIssueId, type, relatedIssueId) {
22801
+ const view = normalizeLinearRelationType(type);
22802
+ if (view === "blockedBy") {
22803
+ return { issueId: relatedIssueId, relatedIssueId: thisIssueId, type: "blocks" };
22804
+ }
22805
+ if (view === "duplicateOf") {
22806
+ return { issueId: relatedIssueId, relatedIssueId: thisIssueId, type: "duplicate" };
22807
+ }
22808
+ return { issueId: thisIssueId, relatedIssueId, type: view };
22809
+ }
22810
+ function relationMatches(rel, type, issueRef) {
22811
+ if (rel.type.toLowerCase() !== type.toLowerCase()) return false;
22812
+ const ref = issueRef.trim().toLowerCase();
22813
+ return rel.issue.id.toLowerCase() === ref || rel.issue.identifier.toLowerCase() === ref;
22814
+ }
22815
+ function formatRelationList(relations) {
22816
+ if (!relations.length) return "(none)";
22817
+ return relations.map((r) => `${r.type} ${r.issue.identifier || r.issue.id}`).join(", ");
22818
+ }
22819
+ async function listLinearLabels(opts) {
22820
+ const json = await linearGraphql(LABELS_QUERY, { first: 250 }, opts);
22821
+ return (json.issueLabels?.nodes ?? []).flatMap((node) => {
22822
+ const id = String(node.id ?? "").trim();
22823
+ const name = String(node.name ?? "").trim();
22824
+ if (!id) return [];
22825
+ return [{ id, name }];
22826
+ });
22827
+ }
22828
+ async function listLinearProjects(filter, opts) {
22829
+ const json = await linearGraphql(PROJECTS_QUERY, { first: 50, filter: filter ?? {} }, opts);
22830
+ return (json.projects?.nodes ?? []).flatMap((node) => {
22831
+ const id = String(node.id ?? "").trim();
22832
+ if (!id) return [];
22833
+ return [
22834
+ {
22835
+ id,
22836
+ name: String(node.name ?? ""),
22837
+ ...node.slugId ? { slugId: String(node.slugId) } : {}
22838
+ }
22839
+ ];
22840
+ });
22841
+ }
22842
+ async function resolveLinearProjectId(project, opts) {
22843
+ if (isLinearNoneToken(project)) return null;
22844
+ const s = (project ?? "").trim();
22845
+ const filter = UUID_RE.test(s) ? { id: { eq: s } } : { name: { eqIgnoreCase: s } };
22846
+ const matched = await listLinearProjects(filter, opts);
22847
+ try {
22848
+ return resolveLinearProject(matched, s);
22849
+ } catch {
22850
+ if (UUID_RE.test(s)) return s;
22851
+ const listed = await listLinearProjects(void 0, opts);
22852
+ return resolveLinearProject(listed, s);
22853
+ }
22854
+ }
22855
+ async function resolveLinearLabelIds(names, opts) {
22856
+ if (names.length === 0) return [];
22857
+ const labels = await listLinearLabels(opts);
22858
+ return names.map((name) => {
22859
+ const s = name.trim();
22860
+ try {
22861
+ return resolveLinearLabel(labels, s).id;
22862
+ } catch (error) {
22863
+ if (UUID_RE.test(s)) return s;
22864
+ throw error;
22865
+ }
22866
+ });
22867
+ }
22868
+ async function applyLinearIssueRelations(issue, add, remove, opts) {
22869
+ const current = [...issue.relations];
22870
+ for (const rel of remove ?? []) {
22871
+ const type = normalizeLinearRelationType(rel.type);
22872
+ const issueRef = rel.issue.trim();
22873
+ if (!issueRef) throw new Error("Linear removeRelations.issue is required (uuid or ENG-123)");
22874
+ const match = current.find((r) => relationMatches(r, type, issueRef));
22875
+ if (!match?.id) {
22876
+ throw new Error(
22877
+ `Linear relation not found: ${type} ${issueRef}. Existing: ${formatRelationList(current)}`
22878
+ );
22879
+ }
22880
+ const json = await linearGraphql(ISSUE_RELATION_DELETE, { id: match.id }, opts);
22881
+ if (!json.issueRelationDelete?.success) {
22882
+ throw new Error(`Linear issueRelationDelete failed (${type} ${issueRef})`);
22883
+ }
22884
+ const idx = current.indexOf(match);
22885
+ if (idx >= 0) current.splice(idx, 1);
22886
+ }
22887
+ for (const rel of add ?? []) {
22888
+ const type = normalizeLinearRelationType(rel.type);
22889
+ const issueRef = rel.issue.trim();
22890
+ if (!issueRef) throw new Error("Linear relations.issue is required (uuid or ENG-123)");
22891
+ if (current.some((r) => relationMatches(r, type, issueRef))) continue;
22892
+ const related = await getLinearIssue(issueRef, opts);
22893
+ const input = linearRelationCreateInput(issue.id, type, related.id);
22894
+ const json = await linearGraphql(ISSUE_RELATION_CREATE, { input }, opts);
22895
+ if (!json.issueRelationCreate?.success) {
22896
+ throw new Error(`Linear issueRelationCreate failed (${type} ${related.identifier})`);
22897
+ }
22898
+ current.push({ type, issue: related, id: void 0 });
22899
+ }
22900
+ }
22503
22901
  function normalizePriority(priority) {
22504
22902
  if (priority == null) return void 0;
22505
22903
  if (!Number.isInteger(priority) || priority < 0 || priority > 4) {
@@ -22507,7 +22905,6 @@ function normalizePriority(priority) {
22507
22905
  }
22508
22906
  return priority;
22509
22907
  }
22510
- var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
22511
22908
  function assigneeFilterKey(assignee) {
22512
22909
  return (assignee ?? "").trim();
22513
22910
  }
@@ -22609,7 +23006,12 @@ async function listLinearCommentsSince(opts) {
22609
23006
  first,
22610
23007
  filter: {
22611
23008
  createdAt: { gte: opts.since },
22612
- issue: buildLinearIssueFilter({ assignee, query: query || void 0 })
23009
+ issue: buildLinearIssueFilter({
23010
+ assignee,
23011
+ // Search hits may match identifier/comments — do not also require
23012
+ // title/description contains when an allowlist is already applied.
23013
+ query: restrictToIssues ? void 0 : query || void 0
23014
+ })
22613
23015
  }
22614
23016
  },
22615
23017
  opts
@@ -22709,8 +23111,10 @@ async function updateLinearIssue(input, opts) {
22709
23111
  if (input.description !== void 0) {
22710
23112
  mutationInput.description = input.description;
22711
23113
  }
22712
- const needsTeam = Boolean(input.state?.trim()) || input.cycle !== void 0;
22713
- const existing = needsTeam ? await getLinearIssue(issueId, opts) : void 0;
23114
+ const addRelations = input.relations?.length ? input.relations : void 0;
23115
+ const removeRelations = input.removeRelations?.length ? input.removeRelations : void 0;
23116
+ const needsIssue = Boolean(input.state?.trim()) || input.cycle !== void 0 || Boolean(addRelations) || Boolean(removeRelations);
23117
+ const existing = needsIssue ? await getLinearIssue(issueId, opts) : void 0;
22714
23118
  if (input.state?.trim()) {
22715
23119
  const { teams } = await listLinearTeams(opts);
22716
23120
  const teamRef = existing?.team?.key || existing?.team?.id;
@@ -22743,14 +23147,44 @@ async function updateLinearIssue(input, opts) {
22743
23147
  if (input.priority !== void 0) {
22744
23148
  mutationInput.priority = normalizePriority(input.priority);
22745
23149
  }
22746
- if (Object.keys(mutationInput).length === 0) {
22747
- throw new Error("linear_update_issue needs at least one of title, description, state, assignee, priority, cycle");
23150
+ if (input.labels !== void 0) {
23151
+ mutationInput.labelIds = await resolveLinearLabelIds(input.labels, opts);
22748
23152
  }
22749
- const json = await linearGraphql(ISSUE_UPDATE, { id: issueId, input: mutationInput }, opts);
22750
- if (!json.issueUpdate?.success || !json.issueUpdate.issue) {
22751
- throw new Error("Linear issueUpdate failed");
23153
+ if (input.project !== void 0) {
23154
+ mutationInput.projectId = await resolveLinearProjectId(input.project, opts);
22752
23155
  }
22753
- return mapIssue(json.issueUpdate.issue);
23156
+ if (input.parent !== void 0) {
23157
+ if (isLinearNoneToken(input.parent)) {
23158
+ mutationInput.parentId = null;
23159
+ } else {
23160
+ const parent = await getLinearIssue((input.parent ?? "").trim(), opts);
23161
+ mutationInput.parentId = parent.id;
23162
+ }
23163
+ }
23164
+ const hasIssueFields = Object.keys(mutationInput).length > 0;
23165
+ const hasRelationWrites = Boolean(addRelations || removeRelations);
23166
+ if (!hasIssueFields && !hasRelationWrites) {
23167
+ throw new Error(
23168
+ "linear_update_issue needs at least one of title, description, state, assignee, priority, cycle, labels, project, parent, relations, removeRelations"
23169
+ );
23170
+ }
23171
+ let updated;
23172
+ if (hasIssueFields) {
23173
+ const json = await linearGraphql(ISSUE_UPDATE, { id: issueId, input: mutationInput }, opts);
23174
+ if (!json.issueUpdate?.success || !json.issueUpdate.issue) {
23175
+ throw new Error("Linear issueUpdate failed");
23176
+ }
23177
+ updated = mapIssue(json.issueUpdate.issue);
23178
+ }
23179
+ if (hasRelationWrites) {
23180
+ const base = updated ?? existing;
23181
+ if (!base) {
23182
+ throw new Error(`Linear issue not found: ${issueId}`);
23183
+ }
23184
+ await applyLinearIssueRelations(base, addRelations, removeRelations, opts);
23185
+ return getLinearIssue(base.id || issueId, opts);
23186
+ }
23187
+ return updated;
22754
23188
  }
22755
23189
  async function commentLinearIssue(input, opts) {
22756
23190
  const issueId = input.id.trim();
@@ -24018,14 +24452,23 @@ function registerAbleTimeTools(server) {
24018
24452
  }
24019
24453
  }
24020
24454
  );
24455
+ const relationInputSchema = import_zod4.z.object({
24456
+ type: import_zod4.z.string().describe("blockedBy (this waits on issue) or blocks (issue waits on this)."),
24457
+ issue: import_zod4.z.string().describe("Related task id or reference (CRM-232).")
24458
+ });
24021
24459
  server.tool(
24022
24460
  "abletime_update_task",
24023
- "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.",
24461
+ 'Update an AbleTime task (id or CRM-232). Pass title, description, state, labels, project, parent, and/or relations. labels replaces tags. project is name or id (tasks stay in a project). parent is CRM-232 or "none". relations sets the wait-on dependency (blockedBy/blocks); removeRelations clears it. 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.',
24024
24462
  {
24025
24463
  id: import_zod4.z.string(),
24026
24464
  title: import_zod4.z.string().optional(),
24027
24465
  description: import_zod4.z.string().optional(),
24028
- state: import_zod4.z.string().optional()
24466
+ state: import_zod4.z.string().optional(),
24467
+ labels: import_zod4.z.array(import_zod4.z.string()).optional().describe("Replace tags/labels. Empty array clears when the host accepts it."),
24468
+ project: import_zod4.z.string().nullable().optional().describe("Project name or id. AbleTime tasks stay in a project."),
24469
+ parent: import_zod4.z.string().nullable().optional().describe('Parent task id or reference (CRM-232), or "none" to unset.'),
24470
+ relations: import_zod4.z.array(relationInputSchema).optional().describe("Set a wait-on dependency: blockedBy or blocks."),
24471
+ removeRelations: import_zod4.z.array(relationInputSchema).optional().describe("Clear a wait-on dependency.")
24029
24472
  },
24030
24473
  async (args) => {
24031
24474
  try {
@@ -24160,14 +24603,23 @@ function registerGithubIssueTools(server) {
24160
24603
  }
24161
24604
  }
24162
24605
  );
24606
+ const relationInputSchema = import_zod5.z.object({
24607
+ type: import_zod5.z.string().describe("blocks or blockedBy (this issue's view)."),
24608
+ issue: import_zod5.z.string().describe("Related issue #123 or URL.")
24609
+ });
24163
24610
  server.tool(
24164
24611
  "github_update_issue",
24165
- "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.",
24612
+ 'Update a GitHub issue (#123). Pass title, body, state (open|closed), labels, project, parent, and/or relations. labels replaces all names (pass [] to clear). project is a Project title, or "none" to remove. parent is #123 or "none". relations adds blockedBy/blocks; removeRelations drops them (`gh` 2.94+). 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.',
24166
24613
  {
24167
24614
  id: import_zod5.z.string(),
24168
24615
  title: import_zod5.z.string().optional(),
24169
24616
  body: import_zod5.z.string().optional(),
24170
24617
  state: import_zod5.z.string().optional().describe("open or closed"),
24618
+ labels: import_zod5.z.array(import_zod5.z.string()).optional().describe("Replace all labels. Empty array clears. Call github_get_issue first to keep existing."),
24619
+ project: import_zod5.z.string().nullable().optional().describe('GitHub Project title, or "none" to remove current projects.'),
24620
+ parent: import_zod5.z.string().nullable().optional().describe('Parent issue #123 or URL, or "none" to unset.'),
24621
+ relations: import_zod5.z.array(relationInputSchema).optional().describe("Add blockedBy or blocks relations (issue #123 or URL)."),
24622
+ removeRelations: import_zod5.z.array(relationInputSchema).optional().describe("Remove blockedBy or blocks relations."),
24171
24623
  repoPath: repoPathSchema
24172
24624
  },
24173
24625
  async (args) => {
@@ -24297,9 +24749,13 @@ function registerLinearTools(server) {
24297
24749
  }
24298
24750
  }
24299
24751
  );
24752
+ const relationInputSchema = import_zod6.z.object({
24753
+ type: import_zod6.z.string().describe("blocks, blockedBy, related, duplicate, or duplicateOf (this issue's view)."),
24754
+ issue: import_zod6.z.string().describe("Related issue uuid or identifier (ENG-123).")
24755
+ });
24300
24756
  server.tool(
24301
24757
  "linear_update_issue",
24302
- '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.',
24758
+ 'Update a Linear issue (uuid or ENG-123). Pass title, description, state, assignee, priority, cycle, labels, project, parent, and/or relations. Cycle/project/parent: name or id, or "none" to clear. labels replaces all names (pass [] to clear; get the issue first to keep existing). relations adds; removeRelations removes by type + issue. 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.',
24303
24759
  {
24304
24760
  id: import_zod6.z.string(),
24305
24761
  title: import_zod6.z.string().optional(),
@@ -24307,7 +24763,12 @@ function registerLinearTools(server) {
24307
24763
  state: import_zod6.z.string().optional(),
24308
24764
  assignee: import_zod6.z.string().nullable().optional(),
24309
24765
  priority: prioritySchema,
24310
- cycle: import_zod6.z.string().nullable().optional().describe('Cycle name, number, "current"/"active", or "none" to unschedule.')
24766
+ cycle: import_zod6.z.string().nullable().optional().describe('Cycle name, number, "current"/"active", or "none" to unschedule.'),
24767
+ labels: import_zod6.z.array(import_zod6.z.string()).optional().describe("Replace all labels (names or ids). Empty array clears. Call linear_get_issue first to keep existing."),
24768
+ project: import_zod6.z.string().nullable().optional().describe('Project name, id, slug, or "none" to unassign.'),
24769
+ parent: import_zod6.z.string().nullable().optional().describe('Parent issue uuid or identifier (ENG-123), or "none" to unset.'),
24770
+ relations: import_zod6.z.array(relationInputSchema).optional().describe("Add relations from this issue's view (blocks, blockedBy, related, duplicate, duplicateOf)."),
24771
+ removeRelations: import_zod6.z.array(relationInputSchema).optional().describe("Remove relations by type + related issue (uuid or ENG-123).")
24311
24772
  },
24312
24773
  async (args) => {
24313
24774
  try {