@sideboard-ai/core 0.1.190 → 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.
package/dist/index.js CHANGED
@@ -238,7 +238,7 @@ import {
238
238
  worktreeCleanupSettings,
239
239
  wrapReviewSkillMarkdown,
240
240
  writeWorktreeFile
241
- } from "./chunk-Z6TW3A5P.js";
241
+ } from "./chunk-5RRAKGPK.js";
242
242
  import {
243
243
  LEGACY_PLAN_FILE_REL,
244
244
  PLAN_FILE_NAME,
@@ -514,9 +514,13 @@ import {
514
514
  } from "./chunk-AN3THU7F.js";
515
515
  import {
516
516
  ABLETIME_MCP_PATH,
517
+ ABLETIME_MCP_PM_PATH,
518
+ ABLETIME_PM_TOOL_NAMES,
517
519
  DEFAULT_ABLETIME_HOST,
520
+ ableTimeDependencyTarget,
518
521
  abletimeMcpRequest,
519
522
  abletimeMcpUrl,
523
+ abletimeToolUsesPm,
520
524
  callAbleTimeTool,
521
525
  commentAbleTimeTask,
522
526
  createAbleTimeTask,
@@ -535,7 +539,7 @@ import {
535
539
  toAbleTimeIssueInfo,
536
540
  updateAbleTimeTask,
537
541
  verifyAbleTimeConnection
538
- } from "./chunk-TEJU6UJY.js";
542
+ } from "./chunk-4M2AK3O5.js";
539
543
  import {
540
544
  formatFetchError,
541
545
  httpFetch,
@@ -1159,12 +1163,14 @@ var ISSUE_FIELDS = `
1159
1163
  children(first: 25) { nodes { ${ISSUE_REF_FIELDS} } }
1160
1164
  relations(first: 25) {
1161
1165
  nodes {
1166
+ id
1162
1167
  type
1163
1168
  relatedIssue { ${ISSUE_REF_FIELDS} }
1164
1169
  }
1165
1170
  }
1166
1171
  inverseRelations(first: 25) {
1167
1172
  nodes {
1173
+ id
1168
1174
  type
1169
1175
  issue { ${ISSUE_REF_FIELDS} }
1170
1176
  }
@@ -1284,6 +1290,35 @@ mutation SideboardCommentCreate($input: CommentCreateInput!) {
1284
1290
  }
1285
1291
  }
1286
1292
  `;
1293
+ var LABELS_QUERY = `
1294
+ query SideboardLabels($first: Int!) {
1295
+ issueLabels(first: $first) {
1296
+ nodes { id name }
1297
+ }
1298
+ }
1299
+ `;
1300
+ var PROJECTS_QUERY = `
1301
+ query SideboardProjects($first: Int!, $filter: ProjectFilter) {
1302
+ projects(first: $first, filter: $filter) {
1303
+ nodes { id name slugId }
1304
+ }
1305
+ }
1306
+ `;
1307
+ var ISSUE_RELATION_CREATE = `
1308
+ mutation SideboardIssueRelationCreate($input: IssueRelationCreateInput!) {
1309
+ issueRelationCreate(input: $input) {
1310
+ success
1311
+ issueRelation { id type }
1312
+ }
1313
+ }
1314
+ `;
1315
+ var ISSUE_RELATION_DELETE = `
1316
+ mutation SideboardIssueRelationDelete($id: String!) {
1317
+ issueRelationDelete(id: $id) {
1318
+ success
1319
+ }
1320
+ }
1321
+ `;
1287
1322
  function rewriteLinearError(message) {
1288
1323
  if (/scope|permission|not (authorized|allowed)|unauthorized|insufficient/i.test(message)) {
1289
1324
  return `${message} \u2014 Disconnect and Connect Linear in Account settings to grant write access.`;
@@ -1403,12 +1438,18 @@ function mapRelations(node) {
1403
1438
  for (const rel of node.relations?.nodes ?? []) {
1404
1439
  const issue = mapIssueRef(rel.relatedIssue);
1405
1440
  if (!issue) continue;
1406
- out.push({ type: String(rel.type ?? "related"), issue });
1441
+ const id = rel.id?.trim();
1442
+ out.push({ type: String(rel.type ?? "related"), issue, ...id ? { id } : {} });
1407
1443
  }
1408
1444
  for (const rel of node.inverseRelations?.nodes ?? []) {
1409
1445
  const issue = mapIssueRef(rel.issue);
1410
1446
  if (!issue) continue;
1411
- out.push({ type: flipLinearRelationType(String(rel.type ?? "related")), issue });
1447
+ const id = rel.id?.trim();
1448
+ out.push({
1449
+ type: flipLinearRelationType(String(rel.type ?? "related")),
1450
+ issue,
1451
+ ...id ? { id } : {}
1452
+ });
1412
1453
  }
1413
1454
  return out;
1414
1455
  }
@@ -1569,6 +1610,148 @@ function resolveLinearCycle(team, cycle) {
1569
1610
  }
1570
1611
  return found.id;
1571
1612
  }
1613
+ var LINEAR_NONE_RE = /^(none|clear|unschedule|null)$/i;
1614
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1615
+ function isLinearNoneToken(value) {
1616
+ if (value == null) return true;
1617
+ const s = value.trim();
1618
+ return !s || LINEAR_NONE_RE.test(s);
1619
+ }
1620
+ function resolveLinearLabel(labels, name) {
1621
+ const s = name.trim();
1622
+ if (!s) throw new Error("Linear label is empty");
1623
+ const lower = s.toLowerCase();
1624
+ const found = labels.find((x) => x.id === s) || labels.find((x) => x.name.toLowerCase() === lower);
1625
+ if (!found) {
1626
+ const available = labels.map((x) => x.name).filter(Boolean).slice(0, 20).join(", ") || "(none)";
1627
+ throw new Error(`Linear label not found: ${name}. Available: ${available}`);
1628
+ }
1629
+ return found;
1630
+ }
1631
+ function resolveLinearProject(projects, project) {
1632
+ if (isLinearNoneToken(project)) return null;
1633
+ const s = (project ?? "").trim();
1634
+ const lower = s.toLowerCase();
1635
+ const found = projects.find((x) => x.id === s) || projects.find((x) => x.name.toLowerCase() === lower) || projects.find((x) => x.slugId?.toLowerCase() === lower);
1636
+ if (!found) {
1637
+ const available = projects.map((x) => x.name).filter(Boolean).slice(0, 20).join(", ") || "(none)";
1638
+ throw new Error(`Linear project not found: ${project}. Available: ${available}`);
1639
+ }
1640
+ return found.id;
1641
+ }
1642
+ function normalizeLinearRelationType(type) {
1643
+ const s = type.trim().toLowerCase().replace(/[\s_-]+/g, "");
1644
+ if (s === "blocks" || s === "block") return "blocks";
1645
+ if (s === "blockedby" || s === "blocked") return "blockedBy";
1646
+ if (s === "related" || s === "relates" || s === "relatesto") return "related";
1647
+ if (s === "duplicate" || s === "dup") return "duplicate";
1648
+ if (s === "duplicateof" || s === "original") return "duplicateOf";
1649
+ if (s === "similar") return "similar";
1650
+ throw new Error(
1651
+ `Linear relation type must be blocks, blockedBy, related, duplicate, or duplicateOf (got ${type})`
1652
+ );
1653
+ }
1654
+ function linearRelationCreateInput(thisIssueId, type, relatedIssueId) {
1655
+ const view = normalizeLinearRelationType(type);
1656
+ if (view === "blockedBy") {
1657
+ return { issueId: relatedIssueId, relatedIssueId: thisIssueId, type: "blocks" };
1658
+ }
1659
+ if (view === "duplicateOf") {
1660
+ return { issueId: relatedIssueId, relatedIssueId: thisIssueId, type: "duplicate" };
1661
+ }
1662
+ return { issueId: thisIssueId, relatedIssueId, type: view };
1663
+ }
1664
+ function relationMatches(rel, type, issueRef) {
1665
+ if (rel.type.toLowerCase() !== type.toLowerCase()) return false;
1666
+ const ref = issueRef.trim().toLowerCase();
1667
+ return rel.issue.id.toLowerCase() === ref || rel.issue.identifier.toLowerCase() === ref;
1668
+ }
1669
+ function formatRelationList(relations) {
1670
+ if (!relations.length) return "(none)";
1671
+ return relations.map((r) => `${r.type} ${r.issue.identifier || r.issue.id}`).join(", ");
1672
+ }
1673
+ async function listLinearLabels(opts) {
1674
+ const json = await linearGraphql(LABELS_QUERY, { first: 250 }, opts);
1675
+ return (json.issueLabels?.nodes ?? []).flatMap((node) => {
1676
+ const id = String(node.id ?? "").trim();
1677
+ const name = String(node.name ?? "").trim();
1678
+ if (!id) return [];
1679
+ return [{ id, name }];
1680
+ });
1681
+ }
1682
+ async function listLinearProjects(filter, opts) {
1683
+ const json = await linearGraphql(PROJECTS_QUERY, { first: 50, filter: filter ?? {} }, opts);
1684
+ return (json.projects?.nodes ?? []).flatMap((node) => {
1685
+ const id = String(node.id ?? "").trim();
1686
+ if (!id) return [];
1687
+ return [
1688
+ {
1689
+ id,
1690
+ name: String(node.name ?? ""),
1691
+ ...node.slugId ? { slugId: String(node.slugId) } : {}
1692
+ }
1693
+ ];
1694
+ });
1695
+ }
1696
+ async function resolveLinearProjectId(project, opts) {
1697
+ if (isLinearNoneToken(project)) return null;
1698
+ const s = (project ?? "").trim();
1699
+ const filter = UUID_RE.test(s) ? { id: { eq: s } } : { name: { eqIgnoreCase: s } };
1700
+ const matched = await listLinearProjects(filter, opts);
1701
+ try {
1702
+ return resolveLinearProject(matched, s);
1703
+ } catch {
1704
+ if (UUID_RE.test(s)) return s;
1705
+ const listed = await listLinearProjects(void 0, opts);
1706
+ return resolveLinearProject(listed, s);
1707
+ }
1708
+ }
1709
+ async function resolveLinearLabelIds(names, opts) {
1710
+ if (names.length === 0) return [];
1711
+ const labels = await listLinearLabels(opts);
1712
+ return names.map((name) => {
1713
+ const s = name.trim();
1714
+ try {
1715
+ return resolveLinearLabel(labels, s).id;
1716
+ } catch (error) {
1717
+ if (UUID_RE.test(s)) return s;
1718
+ throw error;
1719
+ }
1720
+ });
1721
+ }
1722
+ async function applyLinearIssueRelations(issue, add, remove, opts) {
1723
+ const current = [...issue.relations];
1724
+ for (const rel of remove ?? []) {
1725
+ const type = normalizeLinearRelationType(rel.type);
1726
+ const issueRef = rel.issue.trim();
1727
+ if (!issueRef) throw new Error("Linear removeRelations.issue is required (uuid or ENG-123)");
1728
+ const match = current.find((r) => relationMatches(r, type, issueRef));
1729
+ if (!match?.id) {
1730
+ throw new Error(
1731
+ `Linear relation not found: ${type} ${issueRef}. Existing: ${formatRelationList(current)}`
1732
+ );
1733
+ }
1734
+ const json = await linearGraphql(ISSUE_RELATION_DELETE, { id: match.id }, opts);
1735
+ if (!json.issueRelationDelete?.success) {
1736
+ throw new Error(`Linear issueRelationDelete failed (${type} ${issueRef})`);
1737
+ }
1738
+ const idx = current.indexOf(match);
1739
+ if (idx >= 0) current.splice(idx, 1);
1740
+ }
1741
+ for (const rel of add ?? []) {
1742
+ const type = normalizeLinearRelationType(rel.type);
1743
+ const issueRef = rel.issue.trim();
1744
+ if (!issueRef) throw new Error("Linear relations.issue is required (uuid or ENG-123)");
1745
+ if (current.some((r) => relationMatches(r, type, issueRef))) continue;
1746
+ const related = await getLinearIssue(issueRef, opts);
1747
+ const input = linearRelationCreateInput(issue.id, type, related.id);
1748
+ const json = await linearGraphql(ISSUE_RELATION_CREATE, { input }, opts);
1749
+ if (!json.issueRelationCreate?.success) {
1750
+ throw new Error(`Linear issueRelationCreate failed (${type} ${related.identifier})`);
1751
+ }
1752
+ current.push({ type, issue: related, id: void 0 });
1753
+ }
1754
+ }
1572
1755
  function normalizePriority(priority) {
1573
1756
  if (priority == null) return void 0;
1574
1757
  if (!Number.isInteger(priority) || priority < 0 || priority > 4) {
@@ -1576,7 +1759,6 @@ function normalizePriority(priority) {
1576
1759
  }
1577
1760
  return priority;
1578
1761
  }
1579
- var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1580
1762
  function assigneeFilterKey(assignee) {
1581
1763
  return (assignee ?? "").trim();
1582
1764
  }
@@ -1789,8 +1971,10 @@ async function updateLinearIssue(input, opts) {
1789
1971
  if (input.description !== void 0) {
1790
1972
  mutationInput.description = input.description;
1791
1973
  }
1792
- const needsTeam = Boolean(input.state?.trim()) || input.cycle !== void 0;
1793
- const existing = needsTeam ? await getLinearIssue(issueId, opts) : void 0;
1974
+ const addRelations = input.relations?.length ? input.relations : void 0;
1975
+ const removeRelations = input.removeRelations?.length ? input.removeRelations : void 0;
1976
+ const needsIssue = Boolean(input.state?.trim()) || input.cycle !== void 0 || Boolean(addRelations) || Boolean(removeRelations);
1977
+ const existing = needsIssue ? await getLinearIssue(issueId, opts) : void 0;
1794
1978
  if (input.state?.trim()) {
1795
1979
  const { teams } = await listLinearTeams(opts);
1796
1980
  const teamRef = existing?.team?.key || existing?.team?.id;
@@ -1823,14 +2007,44 @@ async function updateLinearIssue(input, opts) {
1823
2007
  if (input.priority !== void 0) {
1824
2008
  mutationInput.priority = normalizePriority(input.priority);
1825
2009
  }
1826
- if (Object.keys(mutationInput).length === 0) {
1827
- throw new Error("linear_update_issue needs at least one of title, description, state, assignee, priority, cycle");
2010
+ if (input.labels !== void 0) {
2011
+ mutationInput.labelIds = await resolveLinearLabelIds(input.labels, opts);
2012
+ }
2013
+ if (input.project !== void 0) {
2014
+ mutationInput.projectId = await resolveLinearProjectId(input.project, opts);
1828
2015
  }
1829
- const json = await linearGraphql(ISSUE_UPDATE, { id: issueId, input: mutationInput }, opts);
1830
- if (!json.issueUpdate?.success || !json.issueUpdate.issue) {
1831
- throw new Error("Linear issueUpdate failed");
2016
+ if (input.parent !== void 0) {
2017
+ if (isLinearNoneToken(input.parent)) {
2018
+ mutationInput.parentId = null;
2019
+ } else {
2020
+ const parent = await getLinearIssue((input.parent ?? "").trim(), opts);
2021
+ mutationInput.parentId = parent.id;
2022
+ }
2023
+ }
2024
+ const hasIssueFields = Object.keys(mutationInput).length > 0;
2025
+ const hasRelationWrites = Boolean(addRelations || removeRelations);
2026
+ if (!hasIssueFields && !hasRelationWrites) {
2027
+ throw new Error(
2028
+ "linear_update_issue needs at least one of title, description, state, assignee, priority, cycle, labels, project, parent, relations, removeRelations"
2029
+ );
2030
+ }
2031
+ let updated;
2032
+ if (hasIssueFields) {
2033
+ const json = await linearGraphql(ISSUE_UPDATE, { id: issueId, input: mutationInput }, opts);
2034
+ if (!json.issueUpdate?.success || !json.issueUpdate.issue) {
2035
+ throw new Error("Linear issueUpdate failed");
2036
+ }
2037
+ updated = mapIssue(json.issueUpdate.issue);
1832
2038
  }
1833
- return mapIssue(json.issueUpdate.issue);
2039
+ if (hasRelationWrites) {
2040
+ const base = updated ?? existing;
2041
+ if (!base) {
2042
+ throw new Error(`Linear issue not found: ${issueId}`);
2043
+ }
2044
+ await applyLinearIssueRelations(base, addRelations, removeRelations, opts);
2045
+ return getLinearIssue(base.id || issueId, opts);
2046
+ }
2047
+ return updated;
1834
2048
  }
1835
2049
  async function commentLinearIssue(input, opts) {
1836
2050
  const issueId = input.id.trim();
@@ -2050,13 +2264,78 @@ async function commentGitHubIssue(input, opts) {
2050
2264
  const url = stdout.trim().split(/\s+/).find((part) => /^https?:\/\//i.test(part));
2051
2265
  return { body, url };
2052
2266
  }
2267
+ function isGitHubNoneToken(value) {
2268
+ if (value == null) return true;
2269
+ const s = value.trim();
2270
+ return !s || /^(none|clear|unschedule|null)$/i.test(s);
2271
+ }
2272
+ function githubRelationEditFlag(type, remove = false) {
2273
+ const s = type.trim().toLowerCase().replace(/[\s_-]+/g, "");
2274
+ if (s === "blockedby" || s === "blocked" || s === "dependson") {
2275
+ return remove ? "--remove-blocked-by" : "--add-blocked-by";
2276
+ }
2277
+ if (s === "blocks" || s === "blocking") {
2278
+ return remove ? "--remove-blocking" : "--add-blocking";
2279
+ }
2280
+ throw new Error(
2281
+ `GitHub relation type must be blocks or blockedBy (got ${type}). Parent is a separate parent= field.`
2282
+ );
2283
+ }
2284
+ function labelsEqual(a, b) {
2285
+ return a.trim().toLowerCase() === b.trim().toLowerCase();
2286
+ }
2287
+ function parseProjectTitles(raw) {
2288
+ const out = [];
2289
+ const seen = /* @__PURE__ */ new Set();
2290
+ const push = (name) => {
2291
+ const title = name.trim();
2292
+ const key = title.toLowerCase();
2293
+ if (!title || seen.has(key)) return;
2294
+ seen.add(key);
2295
+ out.push(title);
2296
+ };
2297
+ const fromItems = raw.projectItems ?? raw.projects ?? raw.projectItemsV2;
2298
+ if (Array.isArray(fromItems)) {
2299
+ for (const item of fromItems) {
2300
+ if (typeof item === "string") {
2301
+ push(item);
2302
+ continue;
2303
+ }
2304
+ if (!item || typeof item !== "object") continue;
2305
+ const rec = item;
2306
+ const nested = rec.project && typeof rec.project === "object" ? rec.project : rec;
2307
+ const title = String(nested.title ?? nested.name ?? rec.title ?? rec.name ?? "").trim();
2308
+ if (title) push(title);
2309
+ }
2310
+ }
2311
+ return out;
2312
+ }
2313
+ async function listGitHubIssueProjects(number, cwd, repoArgs) {
2314
+ const result = await gh(
2315
+ ["issue", "view", String(number), ...repoArgs, "--json", "projectItems,projects"],
2316
+ cwd,
2317
+ { reject: false }
2318
+ );
2319
+ if (result.exitCode !== 0 || !result.stdout.trim()) return [];
2320
+ try {
2321
+ const parsed = JSON.parse(result.stdout);
2322
+ return parseProjectTitles(parsed);
2323
+ } catch {
2324
+ return [];
2325
+ }
2326
+ }
2053
2327
  async function updateGitHubIssue(input, opts) {
2054
2328
  const number = parseGitHubIssueNumber(input.id);
2055
2329
  const title = input.title?.trim();
2056
2330
  const body = input.body;
2057
2331
  const state = input.state?.trim().toLowerCase();
2058
- if (!title && body === void 0 && !state) {
2059
- throw new Error("github_update_issue needs at least one of title, body, state");
2332
+ const addRelations = input.relations?.length ? input.relations : void 0;
2333
+ const removeRelations = input.removeRelations?.length ? input.removeRelations : void 0;
2334
+ const hasEditFields = Boolean(title) || body !== void 0 || input.labels !== void 0 || input.project !== void 0 || input.parent !== void 0 || Boolean(addRelations) || Boolean(removeRelations);
2335
+ if (!hasEditFields && !state) {
2336
+ throw new Error(
2337
+ "github_update_issue needs at least one of title, body, state, labels, project, parent, relations, removeRelations"
2338
+ );
2060
2339
  }
2061
2340
  const { cwd, repoArgs } = await resolveGitHubIssueRepo(opts?.repoPath);
2062
2341
  if (state === "closed" || state === "close") {
@@ -2072,10 +2351,46 @@ async function updateGitHubIssue(input, opts) {
2072
2351
  } else if (state) {
2073
2352
  throw new Error(`GitHub issue state must be open or closed (got ${input.state})`);
2074
2353
  }
2075
- if (title || body !== void 0) {
2076
- const args = ["issue", "edit", String(number), ...repoArgs];
2077
- if (title) args.push("--title", title);
2078
- if (body !== void 0) args.push("--body", body);
2354
+ const args = ["issue", "edit", String(number), ...repoArgs];
2355
+ if (title) args.push("--title", title);
2356
+ if (body !== void 0) args.push("--body", body);
2357
+ if (input.labels !== void 0) {
2358
+ const existing = await getGitHubIssue(String(number), opts);
2359
+ const desired = input.labels.map((name) => name.trim()).filter(Boolean);
2360
+ const add = desired.filter((name) => !existing.labels.some((cur) => labelsEqual(cur, name)));
2361
+ const remove = existing.labels.filter((cur) => !desired.some((name) => labelsEqual(cur, name)));
2362
+ if (add.length) args.push("--add-label", add.join(","));
2363
+ if (remove.length) args.push("--remove-label", remove.join(","));
2364
+ }
2365
+ if (input.project !== void 0) {
2366
+ const current = await listGitHubIssueProjects(number, cwd, repoArgs);
2367
+ if (isGitHubNoneToken(input.project)) {
2368
+ for (const name of current) args.push("--remove-project", name);
2369
+ } else {
2370
+ const wanted = (input.project ?? "").trim();
2371
+ if (!current.some((name) => labelsEqual(name, wanted))) {
2372
+ args.push("--add-project", wanted);
2373
+ }
2374
+ for (const name of current) {
2375
+ if (!labelsEqual(name, wanted)) args.push("--remove-project", name);
2376
+ }
2377
+ }
2378
+ }
2379
+ if (input.parent !== void 0) {
2380
+ if (isGitHubNoneToken(input.parent)) {
2381
+ args.push("--remove-parent");
2382
+ } else {
2383
+ args.push("--parent", String(parseGitHubIssueNumber(input.parent ?? "")));
2384
+ }
2385
+ }
2386
+ for (const rel of removeRelations ?? []) {
2387
+ args.push(githubRelationEditFlag(rel.type, true), String(parseGitHubIssueNumber(rel.issue)));
2388
+ }
2389
+ for (const rel of addRelations ?? []) {
2390
+ args.push(githubRelationEditFlag(rel.type, false), String(parseGitHubIssueNumber(rel.issue)));
2391
+ }
2392
+ const editChanged = args.length > 3 + repoArgs.length;
2393
+ if (editChanged) {
2079
2394
  requireGhOk(await gh(args, cwd, { reject: false }), `GitHub edit #${number}`);
2080
2395
  }
2081
2396
  return getGitHubIssue(String(number), opts);
@@ -3671,14 +3986,23 @@ function registerAbleTimeTools(server) {
3671
3986
  }
3672
3987
  }
3673
3988
  );
3989
+ const relationInputSchema = z4.object({
3990
+ type: z4.string().describe("blockedBy (this waits on issue) or blocks (issue waits on this)."),
3991
+ issue: z4.string().describe("Related task id or reference (CRM-232).")
3992
+ });
3674
3993
  server.tool(
3675
3994
  "abletime_update_task",
3676
- "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.",
3995
+ '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.',
3677
3996
  {
3678
3997
  id: z4.string(),
3679
3998
  title: z4.string().optional(),
3680
3999
  description: z4.string().optional(),
3681
- state: z4.string().optional()
4000
+ state: z4.string().optional(),
4001
+ labels: z4.array(z4.string()).optional().describe("Replace tags/labels. Empty array clears when the host accepts it."),
4002
+ project: z4.string().nullable().optional().describe("Project name or id. AbleTime tasks stay in a project."),
4003
+ parent: z4.string().nullable().optional().describe('Parent task id or reference (CRM-232), or "none" to unset.'),
4004
+ relations: z4.array(relationInputSchema).optional().describe("Set a wait-on dependency: blockedBy or blocks."),
4005
+ removeRelations: z4.array(relationInputSchema).optional().describe("Clear a wait-on dependency.")
3682
4006
  },
3683
4007
  async (args) => {
3684
4008
  try {
@@ -3812,14 +4136,23 @@ function registerGithubIssueTools(server) {
3812
4136
  }
3813
4137
  }
3814
4138
  );
4139
+ const relationInputSchema = z5.object({
4140
+ type: z5.string().describe("blocks or blockedBy (this issue's view)."),
4141
+ issue: z5.string().describe("Related issue #123 or URL.")
4142
+ });
3815
4143
  server.tool(
3816
4144
  "github_update_issue",
3817
- "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.",
4145
+ '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.',
3818
4146
  {
3819
4147
  id: z5.string(),
3820
4148
  title: z5.string().optional(),
3821
4149
  body: z5.string().optional(),
3822
4150
  state: z5.string().optional().describe("open or closed"),
4151
+ labels: z5.array(z5.string()).optional().describe("Replace all labels. Empty array clears. Call github_get_issue first to keep existing."),
4152
+ project: z5.string().nullable().optional().describe('GitHub Project title, or "none" to remove current projects.'),
4153
+ parent: z5.string().nullable().optional().describe('Parent issue #123 or URL, or "none" to unset.'),
4154
+ relations: z5.array(relationInputSchema).optional().describe("Add blockedBy or blocks relations (issue #123 or URL)."),
4155
+ removeRelations: z5.array(relationInputSchema).optional().describe("Remove blockedBy or blocks relations."),
3823
4156
  repoPath: repoPathSchema
3824
4157
  },
3825
4158
  async (args) => {
@@ -3949,9 +4282,13 @@ function registerLinearTools(server) {
3949
4282
  }
3950
4283
  }
3951
4284
  );
4285
+ const relationInputSchema = z6.object({
4286
+ type: z6.string().describe("blocks, blockedBy, related, duplicate, or duplicateOf (this issue's view)."),
4287
+ issue: z6.string().describe("Related issue uuid or identifier (ENG-123).")
4288
+ });
3952
4289
  server.tool(
3953
4290
  "linear_update_issue",
3954
- '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.',
4291
+ '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.',
3955
4292
  {
3956
4293
  id: z6.string(),
3957
4294
  title: z6.string().optional(),
@@ -3959,7 +4296,12 @@ function registerLinearTools(server) {
3959
4296
  state: z6.string().optional(),
3960
4297
  assignee: z6.string().nullable().optional(),
3961
4298
  priority: prioritySchema,
3962
- cycle: z6.string().nullable().optional().describe('Cycle name, number, "current"/"active", or "none" to unschedule.')
4299
+ cycle: z6.string().nullable().optional().describe('Cycle name, number, "current"/"active", or "none" to unschedule.'),
4300
+ labels: z6.array(z6.string()).optional().describe("Replace all labels (names or ids). Empty array clears. Call linear_get_issue first to keep existing."),
4301
+ project: z6.string().nullable().optional().describe('Project name, id, slug, or "none" to unassign.'),
4302
+ parent: z6.string().nullable().optional().describe('Parent issue uuid or identifier (ENG-123), or "none" to unset.'),
4303
+ relations: z6.array(relationInputSchema).optional().describe("Add relations from this issue's view (blocks, blockedBy, related, duplicate, duplicateOf)."),
4304
+ removeRelations: z6.array(relationInputSchema).optional().describe("Remove relations by type + related issue (uuid or ENG-123).")
3963
4305
  },
3964
4306
  async (args) => {
3965
4307
  try {
@@ -7997,6 +8339,8 @@ async function startSlackRelayServer(opts) {
7997
8339
  }
7998
8340
  export {
7999
8341
  ABLETIME_MCP_PATH,
8342
+ ABLETIME_MCP_PM_PATH,
8343
+ ABLETIME_PM_TOOL_NAMES,
8000
8344
  AGENT_GIT_ACTIONS,
8001
8345
  AGENT_RUNNER_MAX_OLD_SPACE_MB,
8002
8346
  ATTACHMENTS_DIR,
@@ -8086,8 +8430,10 @@ export {
8086
8430
  WORKTREE_GITHUB_MCP_TOOLS,
8087
8431
  WORKTREE_LINEAR_MCP_TOOLS,
8088
8432
  WORKTREE_MCP_TOOLS,
8433
+ ableTimeDependencyTarget,
8089
8434
  abletimeMcpRequest,
8090
8435
  abletimeMcpUrl,
8436
+ abletimeToolUsesPm,
8091
8437
  ackSlackInboundSeen,
8092
8438
  addBoardPin,
8093
8439
  addPrStackLayer,
@@ -8360,6 +8706,7 @@ export {
8360
8706
  git,
8361
8707
  githubAgentGitEnv,
8362
8708
  githubHttpsInsteadOfEntries,
8709
+ githubRelationEditFlag,
8363
8710
  globalAgentCwd,
8364
8711
  groupHomeBoardWorktrees,
8365
8712
  handleSlackInbound,
@@ -8409,6 +8756,7 @@ export {
8409
8756
  isInternalAgentStatusText,
8410
8757
  isIssueSourceConnected,
8411
8758
  isLinearConnected,
8759
+ isLinearNoneToken,
8412
8760
  isLinearOAuthCancelled,
8413
8761
  isOptionalServiceId,
8414
8762
  isOrchestratorCapableAgent,
@@ -8446,6 +8794,7 @@ export {
8446
8794
  linearGraphql,
8447
8795
  linearOAuthAuthorizeUrl,
8448
8796
  linearOAuthCredentials,
8797
+ linearRelationCreateInput,
8449
8798
  linearTicketIdFromThread,
8450
8799
  listAbleTimeAssignedIssues,
8451
8800
  listAbleTimeProjects,
@@ -8514,6 +8863,7 @@ export {
8514
8863
  nonInteractiveGitProcessEnv,
8515
8864
  normalizeAbleTimeHost,
8516
8865
  normalizeCodeSelection,
8866
+ normalizeLinearRelationType,
8517
8867
  normalizeParseResult,
8518
8868
  normalizeServiceOrigin,
8519
8869
  normalizeThinkingEffort,
@@ -8607,6 +8957,8 @@ export {
8607
8957
  resolveGithubAgentToken,
8608
8958
  resolveGithubRepoSlug,
8609
8959
  resolveLinearCycle,
8960
+ resolveLinearLabel,
8961
+ resolveLinearProject,
8610
8962
  resolveLinearState,
8611
8963
  resolveLinearTeam,
8612
8964
  resolveLoginCommand,