@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.
- package/dist/{abletime-J3EGHIAV.js → abletime-53T7G667.js} +3 -1
- package/dist/{abletime-CAFQ4GEG.js → abletime-JFMBCSIQ.js} +3 -1
- package/dist/agents/cursor-runner.js +1 -1
- package/dist/{agents-WH3TW5OP.js → agents-5OBZETUV.js} +1 -1
- package/dist/{agents-XPWKGOVT.js → agents-DTPVTDB5.js} +2 -2
- package/dist/{chunk-TEJU6UJY.js → chunk-4M2AK3O5.js} +126 -20
- package/dist/{chunk-U6FCBOS7.js → chunk-5RRAKGPK.js} +9 -9
- package/dist/{chunk-ZSFQLR7Y.js → chunk-FY64F2ZQ.js} +14 -6
- package/dist/{chunk-QU663NPO.js → chunk-GBWKMWUQ.js} +14 -6
- package/dist/{chunk-LE5OZCKB.js → chunk-GPSIZGEZ.js} +7 -7
- package/dist/{chunk-ISOCLQEX.js → chunk-OLDZXO53.js} +1 -1
- package/dist/{chunk-KEVY4GEK.js → chunk-UBQIELFS.js} +123 -20
- package/dist/index.cjs +536 -55
- package/dist/index.d.cts +90 -5
- package/dist/index.d.ts +90 -5
- package/dist/index.js +393 -29
- package/dist/mcp/run-stdio.cjs +516 -55
- package/dist/mcp/run-stdio.js +378 -28
- package/dist/{orchestrator-EVDOHS2S.js → orchestrator-2X6LYBQI.js} +2 -2
- package/dist/{orchestrator-CWBYGHAT.js → orchestrator-F3CXDBZB.js} +3 -3
- package/package.json +1 -1
package/dist/mcp/run-stdio.cjs
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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 <
|
|
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
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
15628
|
-
|
|
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,
|
|
15738
|
+
{ ...taskIdArgs(id), state },
|
|
15634
15739
|
opts
|
|
15635
15740
|
).catch(async () => {
|
|
15636
15741
|
await callAbleTimeTool(
|
|
15637
15742
|
"update_task",
|
|
15638
|
-
{
|
|
15743
|
+
{ ...updateArgs, state },
|
|
15639
15744
|
opts
|
|
15640
15745
|
);
|
|
15641
15746
|
});
|
|
15642
15747
|
}
|
|
15643
|
-
if (
|
|
15644
|
-
await callAbleTimeTool(
|
|
15645
|
-
|
|
15646
|
-
|
|
15647
|
-
|
|
15648
|
-
|
|
15649
|
-
|
|
15650
|
-
|
|
15651
|
-
|
|
15652
|
-
|
|
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 =
|
|
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
|
-
|
|
21904
|
-
|
|
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
|
-
|
|
21921
|
-
|
|
21922
|
-
|
|
21923
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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({
|
|
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
|
|
22713
|
-
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;
|
|
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 (
|
|
22747
|
-
|
|
23150
|
+
if (input.labels !== void 0) {
|
|
23151
|
+
mutationInput.labelIds = await resolveLinearLabelIds(input.labels, opts);
|
|
22748
23152
|
}
|
|
22749
|
-
|
|
22750
|
-
|
|
22751
|
-
throw new Error("Linear issueUpdate failed");
|
|
23153
|
+
if (input.project !== void 0) {
|
|
23154
|
+
mutationInput.projectId = await resolveLinearProjectId(input.project, opts);
|
|
22752
23155
|
}
|
|
22753
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|