@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/{abletime-J3EGHIAV.js → abletime-53T7G667.js} +3 -1
- package/dist/{abletime-CAFQ4GEG.js → abletime-JFMBCSIQ.js} +3 -1
- package/dist/{chunk-TEJU6UJY.js → chunk-4M2AK3O5.js} +126 -20
- package/dist/{chunk-Z6TW3A5P.js → chunk-5RRAKGPK.js} +6 -6
- package/dist/{chunk-IAOXW6RI.js → chunk-GPSIZGEZ.js} +6 -6
- package/dist/{chunk-KEVY4GEK.js → chunk-UBQIELFS.js} +123 -20
- package/dist/index.cjs +507 -46
- package/dist/index.d.cts +88 -3
- package/dist/index.d.ts +88 -3
- package/dist/index.js +376 -24
- package/dist/mcp/run-stdio.cjs +487 -46
- package/dist/mcp/run-stdio.js +362 -24
- package/dist/{orchestrator-BHJELEFM.js → orchestrator-2X6LYBQI.js} +1 -1
- package/dist/{orchestrator-HWNGCDGW.js → orchestrator-F3CXDBZB.js} +1 -1
- package/package.json +1 -1
package/dist/mcp/run-stdio.cjs
CHANGED
|
@@ -15193,13 +15193,17 @@ var init_home_board = __esm({
|
|
|
15193
15193
|
});
|
|
15194
15194
|
|
|
15195
15195
|
// src/integrations/abletime-mcp.ts
|
|
15196
|
+
function abletimeToolUsesPm(name) {
|
|
15197
|
+
return ABLETIME_PM_TOOL_NAMES.includes(name);
|
|
15198
|
+
}
|
|
15196
15199
|
function normalizeAbleTimeHost(raw) {
|
|
15197
15200
|
const trimmed = raw?.trim() || DEFAULT_ABLETIME_HOST;
|
|
15198
15201
|
const withScheme = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
15199
15202
|
return withScheme.replace(/\/+$/, "");
|
|
15200
15203
|
}
|
|
15201
|
-
function abletimeMcpUrl(host) {
|
|
15202
|
-
|
|
15204
|
+
function abletimeMcpUrl(host, opts) {
|
|
15205
|
+
const path = opts?.pm ? ABLETIME_MCP_PM_PATH : ABLETIME_MCP_PATH;
|
|
15206
|
+
return `${normalizeAbleTimeHost(host)}${path}`;
|
|
15203
15207
|
}
|
|
15204
15208
|
function rewriteAbleTimeError(message) {
|
|
15205
15209
|
if (/INTEGRATION_AGENT_ACCESS_DISABLED|agent access/i.test(message)) {
|
|
@@ -15277,7 +15281,8 @@ async function abletimeMcpRequest(method, params, opts) {
|
|
|
15277
15281
|
throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
|
|
15278
15282
|
}
|
|
15279
15283
|
const url = abletimeMcpUrl(
|
|
15280
|
-
opts?.host ?? (opts?.token ? DEFAULT_ABLETIME_HOST : getAbleTimeHost())
|
|
15284
|
+
opts?.host ?? (opts?.token ? DEFAULT_ABLETIME_HOST : getAbleTimeHost()),
|
|
15285
|
+
{ pm: opts?.pm }
|
|
15281
15286
|
);
|
|
15282
15287
|
const res = await httpFetch(url, {
|
|
15283
15288
|
method: "POST",
|
|
@@ -15341,7 +15346,8 @@ async function callAbleTimeTool(name, args = {}, opts) {
|
|
|
15341
15346
|
if (!opts?.token && !isAbleTimeConnected()) {
|
|
15342
15347
|
throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
|
|
15343
15348
|
}
|
|
15344
|
-
const
|
|
15349
|
+
const requestOpts = { ...opts, pm: opts?.pm ?? abletimeToolUsesPm(name) };
|
|
15350
|
+
const call = () => abletimeMcpRequest("tools/call", { name, arguments: args }, requestOpts).then((result) => {
|
|
15345
15351
|
return unwrapToolResult(result);
|
|
15346
15352
|
});
|
|
15347
15353
|
try {
|
|
@@ -15349,13 +15355,13 @@ async function callAbleTimeTool(name, args = {}, opts) {
|
|
|
15349
15355
|
} catch (err) {
|
|
15350
15356
|
const message = err instanceof Error ? err.message : String(err);
|
|
15351
15357
|
if (/initialize|session|not initialized|-32000/i.test(message)) {
|
|
15352
|
-
await initializeIfNeeded(
|
|
15358
|
+
await initializeIfNeeded(requestOpts);
|
|
15353
15359
|
return await call();
|
|
15354
15360
|
}
|
|
15355
15361
|
throw err instanceof Error ? new Error(rewriteAbleTimeError(err.message)) : err;
|
|
15356
15362
|
}
|
|
15357
15363
|
}
|
|
15358
|
-
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;
|
|
15359
15365
|
var init_abletime_mcp = __esm({
|
|
15360
15366
|
"src/integrations/abletime-mcp.ts"() {
|
|
15361
15367
|
"use strict";
|
|
@@ -15363,13 +15369,27 @@ var init_abletime_mcp = __esm({
|
|
|
15363
15369
|
init_app_settings();
|
|
15364
15370
|
DEFAULT_ABLETIME_HOST = "https://track.abletime.com";
|
|
15365
15371
|
ABLETIME_MCP_PATH = "/api/public/v2/mcp";
|
|
15372
|
+
ABLETIME_MCP_PM_PATH = "/api/public/v2/mcp/pm";
|
|
15366
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
|
+
];
|
|
15367
15386
|
}
|
|
15368
15387
|
});
|
|
15369
15388
|
|
|
15370
15389
|
// src/integrations/abletime.ts
|
|
15371
15390
|
var abletime_exports = {};
|
|
15372
15391
|
__export(abletime_exports, {
|
|
15392
|
+
ableTimeDependencyTarget: () => ableTimeDependencyTarget,
|
|
15373
15393
|
commentAbleTimeTask: () => commentAbleTimeTask,
|
|
15374
15394
|
createAbleTimeTask: () => createAbleTimeTask,
|
|
15375
15395
|
ensureAbleTimeTask: () => ensureAbleTimeTask,
|
|
@@ -15626,39 +15646,122 @@ async function commentAbleTimeTask(input, opts) {
|
|
|
15626
15646
|
body: rec ? firstString(rec, ["body", "text", "comment"]) || body : body
|
|
15627
15647
|
};
|
|
15628
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
|
+
}
|
|
15629
15692
|
async function updateAbleTimeTask(input, opts) {
|
|
15630
15693
|
const id = input.id.trim();
|
|
15631
15694
|
if (!id) throw new Error("AbleTime task id is required");
|
|
15632
15695
|
const title = input.title?.trim();
|
|
15633
15696
|
const description = input.description;
|
|
15634
15697
|
const state = input.state?.trim();
|
|
15635
|
-
|
|
15636
|
-
|
|
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
|
+
);
|
|
15637
15734
|
}
|
|
15638
15735
|
if (state) {
|
|
15639
15736
|
await callAbleTimeTool(
|
|
15640
15737
|
"set_task_state",
|
|
15641
|
-
{ id,
|
|
15738
|
+
{ ...taskIdArgs(id), state },
|
|
15642
15739
|
opts
|
|
15643
15740
|
).catch(async () => {
|
|
15644
15741
|
await callAbleTimeTool(
|
|
15645
15742
|
"update_task",
|
|
15646
|
-
{
|
|
15743
|
+
{ ...updateArgs, state },
|
|
15647
15744
|
opts
|
|
15648
15745
|
);
|
|
15649
15746
|
});
|
|
15650
15747
|
}
|
|
15651
|
-
if (
|
|
15652
|
-
await callAbleTimeTool(
|
|
15653
|
-
|
|
15654
|
-
|
|
15655
|
-
|
|
15656
|
-
|
|
15657
|
-
|
|
15658
|
-
|
|
15659
|
-
|
|
15660
|
-
|
|
15661
|
-
);
|
|
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);
|
|
15662
15765
|
}
|
|
15663
15766
|
return getAbleTimeTask(id, opts);
|
|
15664
15767
|
}
|
|
@@ -18307,17 +18410,17 @@ function formatIssueToolsDirective(opts) {
|
|
|
18307
18410
|
];
|
|
18308
18411
|
if (opts.linear) {
|
|
18309
18412
|
lines.push(
|
|
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."
|
|
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."
|
|
18311
18414
|
);
|
|
18312
18415
|
}
|
|
18313
18416
|
if (github) {
|
|
18314
18417
|
lines.push(
|
|
18315
|
-
"- 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`."
|
|
18316
18419
|
);
|
|
18317
18420
|
}
|
|
18318
18421
|
if (opts.abletime) {
|
|
18319
18422
|
lines.push(
|
|
18320
|
-
"- 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)."
|
|
18321
18424
|
);
|
|
18322
18425
|
}
|
|
18323
18426
|
lines.push(
|
|
@@ -21903,13 +22006,78 @@ async function commentGitHubIssue(input, opts) {
|
|
|
21903
22006
|
const url = stdout.trim().split(/\s+/).find((part) => /^https?:\/\//i.test(part));
|
|
21904
22007
|
return { body, url };
|
|
21905
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
|
+
}
|
|
21906
22069
|
async function updateGitHubIssue(input, opts) {
|
|
21907
22070
|
const number = parseGitHubIssueNumber(input.id);
|
|
21908
22071
|
const title = input.title?.trim();
|
|
21909
22072
|
const body = input.body;
|
|
21910
22073
|
const state = input.state?.trim().toLowerCase();
|
|
21911
|
-
|
|
21912
|
-
|
|
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
|
+
);
|
|
21913
22081
|
}
|
|
21914
22082
|
const { cwd, repoArgs } = await resolveGitHubIssueRepo(opts?.repoPath);
|
|
21915
22083
|
if (state === "closed" || state === "close") {
|
|
@@ -21925,10 +22093,46 @@ async function updateGitHubIssue(input, opts) {
|
|
|
21925
22093
|
} else if (state) {
|
|
21926
22094
|
throw new Error(`GitHub issue state must be open or closed (got ${input.state})`);
|
|
21927
22095
|
}
|
|
21928
|
-
|
|
21929
|
-
|
|
21930
|
-
|
|
21931
|
-
|
|
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) {
|
|
21932
22136
|
requireGhOk(await gh(args, cwd, { reject: false }), `GitHub edit #${number}`);
|
|
21933
22137
|
}
|
|
21934
22138
|
return getGitHubIssue(String(number), opts);
|
|
@@ -22105,12 +22309,14 @@ var ISSUE_FIELDS = `
|
|
|
22105
22309
|
children(first: 25) { nodes { ${ISSUE_REF_FIELDS} } }
|
|
22106
22310
|
relations(first: 25) {
|
|
22107
22311
|
nodes {
|
|
22312
|
+
id
|
|
22108
22313
|
type
|
|
22109
22314
|
relatedIssue { ${ISSUE_REF_FIELDS} }
|
|
22110
22315
|
}
|
|
22111
22316
|
}
|
|
22112
22317
|
inverseRelations(first: 25) {
|
|
22113
22318
|
nodes {
|
|
22319
|
+
id
|
|
22114
22320
|
type
|
|
22115
22321
|
issue { ${ISSUE_REF_FIELDS} }
|
|
22116
22322
|
}
|
|
@@ -22230,6 +22436,35 @@ mutation SideboardCommentCreate($input: CommentCreateInput!) {
|
|
|
22230
22436
|
}
|
|
22231
22437
|
}
|
|
22232
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
|
+
`;
|
|
22233
22468
|
function rewriteLinearError(message) {
|
|
22234
22469
|
if (/scope|permission|not (authorized|allowed)|unauthorized|insufficient/i.test(message)) {
|
|
22235
22470
|
return `${message} \u2014 Disconnect and Connect Linear in Account settings to grant write access.`;
|
|
@@ -22349,12 +22584,18 @@ function mapRelations(node) {
|
|
|
22349
22584
|
for (const rel of node.relations?.nodes ?? []) {
|
|
22350
22585
|
const issue = mapIssueRef(rel.relatedIssue);
|
|
22351
22586
|
if (!issue) continue;
|
|
22352
|
-
|
|
22587
|
+
const id = rel.id?.trim();
|
|
22588
|
+
out.push({ type: String(rel.type ?? "related"), issue, ...id ? { id } : {} });
|
|
22353
22589
|
}
|
|
22354
22590
|
for (const rel of node.inverseRelations?.nodes ?? []) {
|
|
22355
22591
|
const issue = mapIssueRef(rel.issue);
|
|
22356
22592
|
if (!issue) continue;
|
|
22357
|
-
|
|
22593
|
+
const id = rel.id?.trim();
|
|
22594
|
+
out.push({
|
|
22595
|
+
type: flipLinearRelationType(String(rel.type ?? "related")),
|
|
22596
|
+
issue,
|
|
22597
|
+
...id ? { id } : {}
|
|
22598
|
+
});
|
|
22358
22599
|
}
|
|
22359
22600
|
return out;
|
|
22360
22601
|
}
|
|
@@ -22515,6 +22756,148 @@ function resolveLinearCycle(team, cycle) {
|
|
|
22515
22756
|
}
|
|
22516
22757
|
return found.id;
|
|
22517
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
|
+
}
|
|
22518
22901
|
function normalizePriority(priority) {
|
|
22519
22902
|
if (priority == null) return void 0;
|
|
22520
22903
|
if (!Number.isInteger(priority) || priority < 0 || priority > 4) {
|
|
@@ -22522,7 +22905,6 @@ function normalizePriority(priority) {
|
|
|
22522
22905
|
}
|
|
22523
22906
|
return priority;
|
|
22524
22907
|
}
|
|
22525
|
-
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
22526
22908
|
function assigneeFilterKey(assignee) {
|
|
22527
22909
|
return (assignee ?? "").trim();
|
|
22528
22910
|
}
|
|
@@ -22729,8 +23111,10 @@ async function updateLinearIssue(input, opts) {
|
|
|
22729
23111
|
if (input.description !== void 0) {
|
|
22730
23112
|
mutationInput.description = input.description;
|
|
22731
23113
|
}
|
|
22732
|
-
const
|
|
22733
|
-
const
|
|
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;
|
|
22734
23118
|
if (input.state?.trim()) {
|
|
22735
23119
|
const { teams } = await listLinearTeams(opts);
|
|
22736
23120
|
const teamRef = existing?.team?.key || existing?.team?.id;
|
|
@@ -22763,14 +23147,44 @@ async function updateLinearIssue(input, opts) {
|
|
|
22763
23147
|
if (input.priority !== void 0) {
|
|
22764
23148
|
mutationInput.priority = normalizePriority(input.priority);
|
|
22765
23149
|
}
|
|
22766
|
-
if (
|
|
22767
|
-
|
|
23150
|
+
if (input.labels !== void 0) {
|
|
23151
|
+
mutationInput.labelIds = await resolveLinearLabelIds(input.labels, opts);
|
|
23152
|
+
}
|
|
23153
|
+
if (input.project !== void 0) {
|
|
23154
|
+
mutationInput.projectId = await resolveLinearProjectId(input.project, opts);
|
|
23155
|
+
}
|
|
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
|
+
);
|
|
22768
23170
|
}
|
|
22769
|
-
|
|
22770
|
-
if (
|
|
22771
|
-
|
|
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);
|
|
22772
23186
|
}
|
|
22773
|
-
return
|
|
23187
|
+
return updated;
|
|
22774
23188
|
}
|
|
22775
23189
|
async function commentLinearIssue(input, opts) {
|
|
22776
23190
|
const issueId = input.id.trim();
|
|
@@ -24038,14 +24452,23 @@ function registerAbleTimeTools(server) {
|
|
|
24038
24452
|
}
|
|
24039
24453
|
}
|
|
24040
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
|
+
});
|
|
24041
24459
|
server.tool(
|
|
24042
24460
|
"abletime_update_task",
|
|
24043
|
-
|
|
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.',
|
|
24044
24462
|
{
|
|
24045
24463
|
id: import_zod4.z.string(),
|
|
24046
24464
|
title: import_zod4.z.string().optional(),
|
|
24047
24465
|
description: import_zod4.z.string().optional(),
|
|
24048
|
-
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.")
|
|
24049
24472
|
},
|
|
24050
24473
|
async (args) => {
|
|
24051
24474
|
try {
|
|
@@ -24180,14 +24603,23 @@ function registerGithubIssueTools(server) {
|
|
|
24180
24603
|
}
|
|
24181
24604
|
}
|
|
24182
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
|
+
});
|
|
24183
24610
|
server.tool(
|
|
24184
24611
|
"github_update_issue",
|
|
24185
|
-
|
|
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.',
|
|
24186
24613
|
{
|
|
24187
24614
|
id: import_zod5.z.string(),
|
|
24188
24615
|
title: import_zod5.z.string().optional(),
|
|
24189
24616
|
body: import_zod5.z.string().optional(),
|
|
24190
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."),
|
|
24191
24623
|
repoPath: repoPathSchema
|
|
24192
24624
|
},
|
|
24193
24625
|
async (args) => {
|
|
@@ -24317,9 +24749,13 @@ function registerLinearTools(server) {
|
|
|
24317
24749
|
}
|
|
24318
24750
|
}
|
|
24319
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
|
+
});
|
|
24320
24756
|
server.tool(
|
|
24321
24757
|
"linear_update_issue",
|
|
24322
|
-
'Update a Linear issue (uuid or ENG-123). Pass title, description, state, assignee, priority, and/or
|
|
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.',
|
|
24323
24759
|
{
|
|
24324
24760
|
id: import_zod6.z.string(),
|
|
24325
24761
|
title: import_zod6.z.string().optional(),
|
|
@@ -24327,7 +24763,12 @@ function registerLinearTools(server) {
|
|
|
24327
24763
|
state: import_zod6.z.string().optional(),
|
|
24328
24764
|
assignee: import_zod6.z.string().nullable().optional(),
|
|
24329
24765
|
priority: prioritySchema,
|
|
24330
|
-
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).")
|
|
24331
24772
|
},
|
|
24332
24773
|
async (args) => {
|
|
24333
24774
|
try {
|