@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.js
CHANGED
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
syncBoardPins,
|
|
38
38
|
updateSchedule,
|
|
39
39
|
waitForDetachedJob
|
|
40
|
-
} from "../chunk-
|
|
40
|
+
} from "../chunk-GPSIZGEZ.js";
|
|
41
41
|
import {
|
|
42
42
|
applySystemCaEnv,
|
|
43
43
|
isCursorApiKeyConfigured,
|
|
@@ -46,7 +46,7 @@ import {
|
|
|
46
46
|
materializeSystemCaBundle,
|
|
47
47
|
sideboardMcpProfile,
|
|
48
48
|
trustSystemCertificates
|
|
49
|
-
} from "../chunk-
|
|
49
|
+
} from "../chunk-GBWKMWUQ.js";
|
|
50
50
|
import "../chunk-HVIEYTUK.js";
|
|
51
51
|
import "../chunk-Y4MIXGPA.js";
|
|
52
52
|
import {
|
|
@@ -60,7 +60,7 @@ import {
|
|
|
60
60
|
searchAbleTimeTasks,
|
|
61
61
|
toAbleTimeIssueInfo,
|
|
62
62
|
updateAbleTimeTask
|
|
63
|
-
} from "../chunk-
|
|
63
|
+
} from "../chunk-UBQIELFS.js";
|
|
64
64
|
import {
|
|
65
65
|
httpFetch
|
|
66
66
|
} from "../chunk-VKQGAWHY.js";
|
|
@@ -298,7 +298,7 @@ async function listGitHubIssueCommentsSince(opts) {
|
|
|
298
298
|
}).filter((n) => n != null)
|
|
299
299
|
);
|
|
300
300
|
if (restrictToIssues && allowed.size === 0) return [];
|
|
301
|
-
const pageSize =
|
|
301
|
+
const pageSize = GITHUB_ISSUE_COMMENTS_PAGE_SIZE;
|
|
302
302
|
const out = [];
|
|
303
303
|
for (let page = 1; page <= GITHUB_ISSUE_COMMENTS_MAX_PAGES; page++) {
|
|
304
304
|
const path = `repos/${slug}/issues/comments?since=${encodeURIComponent(opts.since)}&sort=created&direction=desc&per_page=${pageSize}&page=${page}`;
|
|
@@ -366,13 +366,78 @@ async function commentGitHubIssue(input, opts) {
|
|
|
366
366
|
const url = stdout.trim().split(/\s+/).find((part) => /^https?:\/\//i.test(part));
|
|
367
367
|
return { body, url };
|
|
368
368
|
}
|
|
369
|
+
function isGitHubNoneToken(value) {
|
|
370
|
+
if (value == null) return true;
|
|
371
|
+
const s = value.trim();
|
|
372
|
+
return !s || /^(none|clear|unschedule|null)$/i.test(s);
|
|
373
|
+
}
|
|
374
|
+
function githubRelationEditFlag(type, remove = false) {
|
|
375
|
+
const s = type.trim().toLowerCase().replace(/[\s_-]+/g, "");
|
|
376
|
+
if (s === "blockedby" || s === "blocked" || s === "dependson") {
|
|
377
|
+
return remove ? "--remove-blocked-by" : "--add-blocked-by";
|
|
378
|
+
}
|
|
379
|
+
if (s === "blocks" || s === "blocking") {
|
|
380
|
+
return remove ? "--remove-blocking" : "--add-blocking";
|
|
381
|
+
}
|
|
382
|
+
throw new Error(
|
|
383
|
+
`GitHub relation type must be blocks or blockedBy (got ${type}). Parent is a separate parent= field.`
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
function labelsEqual(a, b) {
|
|
387
|
+
return a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
388
|
+
}
|
|
389
|
+
function parseProjectTitles(raw) {
|
|
390
|
+
const out = [];
|
|
391
|
+
const seen = /* @__PURE__ */ new Set();
|
|
392
|
+
const push = (name) => {
|
|
393
|
+
const title = name.trim();
|
|
394
|
+
const key = title.toLowerCase();
|
|
395
|
+
if (!title || seen.has(key)) return;
|
|
396
|
+
seen.add(key);
|
|
397
|
+
out.push(title);
|
|
398
|
+
};
|
|
399
|
+
const fromItems = raw.projectItems ?? raw.projects ?? raw.projectItemsV2;
|
|
400
|
+
if (Array.isArray(fromItems)) {
|
|
401
|
+
for (const item of fromItems) {
|
|
402
|
+
if (typeof item === "string") {
|
|
403
|
+
push(item);
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
if (!item || typeof item !== "object") continue;
|
|
407
|
+
const rec = item;
|
|
408
|
+
const nested = rec.project && typeof rec.project === "object" ? rec.project : rec;
|
|
409
|
+
const title = String(nested.title ?? nested.name ?? rec.title ?? rec.name ?? "").trim();
|
|
410
|
+
if (title) push(title);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return out;
|
|
414
|
+
}
|
|
415
|
+
async function listGitHubIssueProjects(number, cwd, repoArgs) {
|
|
416
|
+
const result = await gh(
|
|
417
|
+
["issue", "view", String(number), ...repoArgs, "--json", "projectItems,projects"],
|
|
418
|
+
cwd,
|
|
419
|
+
{ reject: false }
|
|
420
|
+
);
|
|
421
|
+
if (result.exitCode !== 0 || !result.stdout.trim()) return [];
|
|
422
|
+
try {
|
|
423
|
+
const parsed = JSON.parse(result.stdout);
|
|
424
|
+
return parseProjectTitles(parsed);
|
|
425
|
+
} catch {
|
|
426
|
+
return [];
|
|
427
|
+
}
|
|
428
|
+
}
|
|
369
429
|
async function updateGitHubIssue(input, opts) {
|
|
370
430
|
const number = parseGitHubIssueNumber(input.id);
|
|
371
431
|
const title = input.title?.trim();
|
|
372
432
|
const body = input.body;
|
|
373
433
|
const state = input.state?.trim().toLowerCase();
|
|
374
|
-
|
|
375
|
-
|
|
434
|
+
const addRelations = input.relations?.length ? input.relations : void 0;
|
|
435
|
+
const removeRelations = input.removeRelations?.length ? input.removeRelations : void 0;
|
|
436
|
+
const hasEditFields = Boolean(title) || body !== void 0 || input.labels !== void 0 || input.project !== void 0 || input.parent !== void 0 || Boolean(addRelations) || Boolean(removeRelations);
|
|
437
|
+
if (!hasEditFields && !state) {
|
|
438
|
+
throw new Error(
|
|
439
|
+
"github_update_issue needs at least one of title, body, state, labels, project, parent, relations, removeRelations"
|
|
440
|
+
);
|
|
376
441
|
}
|
|
377
442
|
const { cwd, repoArgs } = await resolveGitHubIssueRepo(opts?.repoPath);
|
|
378
443
|
if (state === "closed" || state === "close") {
|
|
@@ -388,10 +453,46 @@ async function updateGitHubIssue(input, opts) {
|
|
|
388
453
|
} else if (state) {
|
|
389
454
|
throw new Error(`GitHub issue state must be open or closed (got ${input.state})`);
|
|
390
455
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
456
|
+
const args = ["issue", "edit", String(number), ...repoArgs];
|
|
457
|
+
if (title) args.push("--title", title);
|
|
458
|
+
if (body !== void 0) args.push("--body", body);
|
|
459
|
+
if (input.labels !== void 0) {
|
|
460
|
+
const existing = await getGitHubIssue(String(number), opts);
|
|
461
|
+
const desired = input.labels.map((name) => name.trim()).filter(Boolean);
|
|
462
|
+
const add = desired.filter((name) => !existing.labels.some((cur) => labelsEqual(cur, name)));
|
|
463
|
+
const remove = existing.labels.filter((cur) => !desired.some((name) => labelsEqual(cur, name)));
|
|
464
|
+
if (add.length) args.push("--add-label", add.join(","));
|
|
465
|
+
if (remove.length) args.push("--remove-label", remove.join(","));
|
|
466
|
+
}
|
|
467
|
+
if (input.project !== void 0) {
|
|
468
|
+
const current = await listGitHubIssueProjects(number, cwd, repoArgs);
|
|
469
|
+
if (isGitHubNoneToken(input.project)) {
|
|
470
|
+
for (const name of current) args.push("--remove-project", name);
|
|
471
|
+
} else {
|
|
472
|
+
const wanted = (input.project ?? "").trim();
|
|
473
|
+
if (!current.some((name) => labelsEqual(name, wanted))) {
|
|
474
|
+
args.push("--add-project", wanted);
|
|
475
|
+
}
|
|
476
|
+
for (const name of current) {
|
|
477
|
+
if (!labelsEqual(name, wanted)) args.push("--remove-project", name);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (input.parent !== void 0) {
|
|
482
|
+
if (isGitHubNoneToken(input.parent)) {
|
|
483
|
+
args.push("--remove-parent");
|
|
484
|
+
} else {
|
|
485
|
+
args.push("--parent", String(parseGitHubIssueNumber(input.parent ?? "")));
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
for (const rel of removeRelations ?? []) {
|
|
489
|
+
args.push(githubRelationEditFlag(rel.type, true), String(parseGitHubIssueNumber(rel.issue)));
|
|
490
|
+
}
|
|
491
|
+
for (const rel of addRelations ?? []) {
|
|
492
|
+
args.push(githubRelationEditFlag(rel.type, false), String(parseGitHubIssueNumber(rel.issue)));
|
|
493
|
+
}
|
|
494
|
+
const editChanged = args.length > 3 + repoArgs.length;
|
|
495
|
+
if (editChanged) {
|
|
395
496
|
requireGhOk(await gh(args, cwd, { reject: false }), `GitHub edit #${number}`);
|
|
396
497
|
}
|
|
397
498
|
return getGitHubIssue(String(number), opts);
|
|
@@ -563,12 +664,14 @@ var ISSUE_FIELDS = `
|
|
|
563
664
|
children(first: 25) { nodes { ${ISSUE_REF_FIELDS} } }
|
|
564
665
|
relations(first: 25) {
|
|
565
666
|
nodes {
|
|
667
|
+
id
|
|
566
668
|
type
|
|
567
669
|
relatedIssue { ${ISSUE_REF_FIELDS} }
|
|
568
670
|
}
|
|
569
671
|
}
|
|
570
672
|
inverseRelations(first: 25) {
|
|
571
673
|
nodes {
|
|
674
|
+
id
|
|
572
675
|
type
|
|
573
676
|
issue { ${ISSUE_REF_FIELDS} }
|
|
574
677
|
}
|
|
@@ -688,6 +791,35 @@ mutation SideboardCommentCreate($input: CommentCreateInput!) {
|
|
|
688
791
|
}
|
|
689
792
|
}
|
|
690
793
|
`;
|
|
794
|
+
var LABELS_QUERY = `
|
|
795
|
+
query SideboardLabels($first: Int!) {
|
|
796
|
+
issueLabels(first: $first) {
|
|
797
|
+
nodes { id name }
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
`;
|
|
801
|
+
var PROJECTS_QUERY = `
|
|
802
|
+
query SideboardProjects($first: Int!, $filter: ProjectFilter) {
|
|
803
|
+
projects(first: $first, filter: $filter) {
|
|
804
|
+
nodes { id name slugId }
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
`;
|
|
808
|
+
var ISSUE_RELATION_CREATE = `
|
|
809
|
+
mutation SideboardIssueRelationCreate($input: IssueRelationCreateInput!) {
|
|
810
|
+
issueRelationCreate(input: $input) {
|
|
811
|
+
success
|
|
812
|
+
issueRelation { id type }
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
`;
|
|
816
|
+
var ISSUE_RELATION_DELETE = `
|
|
817
|
+
mutation SideboardIssueRelationDelete($id: String!) {
|
|
818
|
+
issueRelationDelete(id: $id) {
|
|
819
|
+
success
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
`;
|
|
691
823
|
function rewriteLinearError(message) {
|
|
692
824
|
if (/scope|permission|not (authorized|allowed)|unauthorized|insufficient/i.test(message)) {
|
|
693
825
|
return `${message} \u2014 Disconnect and Connect Linear in Account settings to grant write access.`;
|
|
@@ -807,12 +939,18 @@ function mapRelations(node) {
|
|
|
807
939
|
for (const rel of node.relations?.nodes ?? []) {
|
|
808
940
|
const issue = mapIssueRef(rel.relatedIssue);
|
|
809
941
|
if (!issue) continue;
|
|
810
|
-
|
|
942
|
+
const id = rel.id?.trim();
|
|
943
|
+
out.push({ type: String(rel.type ?? "related"), issue, ...id ? { id } : {} });
|
|
811
944
|
}
|
|
812
945
|
for (const rel of node.inverseRelations?.nodes ?? []) {
|
|
813
946
|
const issue = mapIssueRef(rel.issue);
|
|
814
947
|
if (!issue) continue;
|
|
815
|
-
|
|
948
|
+
const id = rel.id?.trim();
|
|
949
|
+
out.push({
|
|
950
|
+
type: flipLinearRelationType(String(rel.type ?? "related")),
|
|
951
|
+
issue,
|
|
952
|
+
...id ? { id } : {}
|
|
953
|
+
});
|
|
816
954
|
}
|
|
817
955
|
return out;
|
|
818
956
|
}
|
|
@@ -958,7 +1096,14 @@ function resolveLinearCycle(team, cycle) {
|
|
|
958
1096
|
}
|
|
959
1097
|
const lower = s.toLowerCase();
|
|
960
1098
|
const asNumber = Number.parseInt(s, 10);
|
|
961
|
-
const
|
|
1099
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1100
|
+
const cycles = [team.activeCycle, ...team.cycles ?? []].filter(
|
|
1101
|
+
(c) => {
|
|
1102
|
+
if (!c?.id || seen.has(c.id)) return false;
|
|
1103
|
+
seen.add(c.id);
|
|
1104
|
+
return true;
|
|
1105
|
+
}
|
|
1106
|
+
);
|
|
962
1107
|
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);
|
|
963
1108
|
if (!found) {
|
|
964
1109
|
const available = cycles.map((c) => c.number != null ? `${c.name} (${c.number})` : c.name).join(", ") || "(none)";
|
|
@@ -966,6 +1111,148 @@ function resolveLinearCycle(team, cycle) {
|
|
|
966
1111
|
}
|
|
967
1112
|
return found.id;
|
|
968
1113
|
}
|
|
1114
|
+
var LINEAR_NONE_RE = /^(none|clear|unschedule|null)$/i;
|
|
1115
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1116
|
+
function isLinearNoneToken(value) {
|
|
1117
|
+
if (value == null) return true;
|
|
1118
|
+
const s = value.trim();
|
|
1119
|
+
return !s || LINEAR_NONE_RE.test(s);
|
|
1120
|
+
}
|
|
1121
|
+
function resolveLinearLabel(labels, name) {
|
|
1122
|
+
const s = name.trim();
|
|
1123
|
+
if (!s) throw new Error("Linear label is empty");
|
|
1124
|
+
const lower = s.toLowerCase();
|
|
1125
|
+
const found = labels.find((x) => x.id === s) || labels.find((x) => x.name.toLowerCase() === lower);
|
|
1126
|
+
if (!found) {
|
|
1127
|
+
const available = labels.map((x) => x.name).filter(Boolean).slice(0, 20).join(", ") || "(none)";
|
|
1128
|
+
throw new Error(`Linear label not found: ${name}. Available: ${available}`);
|
|
1129
|
+
}
|
|
1130
|
+
return found;
|
|
1131
|
+
}
|
|
1132
|
+
function resolveLinearProject(projects, project) {
|
|
1133
|
+
if (isLinearNoneToken(project)) return null;
|
|
1134
|
+
const s = (project ?? "").trim();
|
|
1135
|
+
const lower = s.toLowerCase();
|
|
1136
|
+
const found = projects.find((x) => x.id === s) || projects.find((x) => x.name.toLowerCase() === lower) || projects.find((x) => x.slugId?.toLowerCase() === lower);
|
|
1137
|
+
if (!found) {
|
|
1138
|
+
const available = projects.map((x) => x.name).filter(Boolean).slice(0, 20).join(", ") || "(none)";
|
|
1139
|
+
throw new Error(`Linear project not found: ${project}. Available: ${available}`);
|
|
1140
|
+
}
|
|
1141
|
+
return found.id;
|
|
1142
|
+
}
|
|
1143
|
+
function normalizeLinearRelationType(type) {
|
|
1144
|
+
const s = type.trim().toLowerCase().replace(/[\s_-]+/g, "");
|
|
1145
|
+
if (s === "blocks" || s === "block") return "blocks";
|
|
1146
|
+
if (s === "blockedby" || s === "blocked") return "blockedBy";
|
|
1147
|
+
if (s === "related" || s === "relates" || s === "relatesto") return "related";
|
|
1148
|
+
if (s === "duplicate" || s === "dup") return "duplicate";
|
|
1149
|
+
if (s === "duplicateof" || s === "original") return "duplicateOf";
|
|
1150
|
+
if (s === "similar") return "similar";
|
|
1151
|
+
throw new Error(
|
|
1152
|
+
`Linear relation type must be blocks, blockedBy, related, duplicate, or duplicateOf (got ${type})`
|
|
1153
|
+
);
|
|
1154
|
+
}
|
|
1155
|
+
function linearRelationCreateInput(thisIssueId, type, relatedIssueId) {
|
|
1156
|
+
const view = normalizeLinearRelationType(type);
|
|
1157
|
+
if (view === "blockedBy") {
|
|
1158
|
+
return { issueId: relatedIssueId, relatedIssueId: thisIssueId, type: "blocks" };
|
|
1159
|
+
}
|
|
1160
|
+
if (view === "duplicateOf") {
|
|
1161
|
+
return { issueId: relatedIssueId, relatedIssueId: thisIssueId, type: "duplicate" };
|
|
1162
|
+
}
|
|
1163
|
+
return { issueId: thisIssueId, relatedIssueId, type: view };
|
|
1164
|
+
}
|
|
1165
|
+
function relationMatches(rel, type, issueRef) {
|
|
1166
|
+
if (rel.type.toLowerCase() !== type.toLowerCase()) return false;
|
|
1167
|
+
const ref = issueRef.trim().toLowerCase();
|
|
1168
|
+
return rel.issue.id.toLowerCase() === ref || rel.issue.identifier.toLowerCase() === ref;
|
|
1169
|
+
}
|
|
1170
|
+
function formatRelationList(relations) {
|
|
1171
|
+
if (!relations.length) return "(none)";
|
|
1172
|
+
return relations.map((r) => `${r.type} ${r.issue.identifier || r.issue.id}`).join(", ");
|
|
1173
|
+
}
|
|
1174
|
+
async function listLinearLabels(opts) {
|
|
1175
|
+
const json = await linearGraphql(LABELS_QUERY, { first: 250 }, opts);
|
|
1176
|
+
return (json.issueLabels?.nodes ?? []).flatMap((node) => {
|
|
1177
|
+
const id = String(node.id ?? "").trim();
|
|
1178
|
+
const name = String(node.name ?? "").trim();
|
|
1179
|
+
if (!id) return [];
|
|
1180
|
+
return [{ id, name }];
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
async function listLinearProjects(filter, opts) {
|
|
1184
|
+
const json = await linearGraphql(PROJECTS_QUERY, { first: 50, filter: filter ?? {} }, opts);
|
|
1185
|
+
return (json.projects?.nodes ?? []).flatMap((node) => {
|
|
1186
|
+
const id = String(node.id ?? "").trim();
|
|
1187
|
+
if (!id) return [];
|
|
1188
|
+
return [
|
|
1189
|
+
{
|
|
1190
|
+
id,
|
|
1191
|
+
name: String(node.name ?? ""),
|
|
1192
|
+
...node.slugId ? { slugId: String(node.slugId) } : {}
|
|
1193
|
+
}
|
|
1194
|
+
];
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
async function resolveLinearProjectId(project, opts) {
|
|
1198
|
+
if (isLinearNoneToken(project)) return null;
|
|
1199
|
+
const s = (project ?? "").trim();
|
|
1200
|
+
const filter = UUID_RE.test(s) ? { id: { eq: s } } : { name: { eqIgnoreCase: s } };
|
|
1201
|
+
const matched = await listLinearProjects(filter, opts);
|
|
1202
|
+
try {
|
|
1203
|
+
return resolveLinearProject(matched, s);
|
|
1204
|
+
} catch {
|
|
1205
|
+
if (UUID_RE.test(s)) return s;
|
|
1206
|
+
const listed = await listLinearProjects(void 0, opts);
|
|
1207
|
+
return resolveLinearProject(listed, s);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
async function resolveLinearLabelIds(names, opts) {
|
|
1211
|
+
if (names.length === 0) return [];
|
|
1212
|
+
const labels = await listLinearLabels(opts);
|
|
1213
|
+
return names.map((name) => {
|
|
1214
|
+
const s = name.trim();
|
|
1215
|
+
try {
|
|
1216
|
+
return resolveLinearLabel(labels, s).id;
|
|
1217
|
+
} catch (error) {
|
|
1218
|
+
if (UUID_RE.test(s)) return s;
|
|
1219
|
+
throw error;
|
|
1220
|
+
}
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
async function applyLinearIssueRelations(issue, add, remove, opts) {
|
|
1224
|
+
const current = [...issue.relations];
|
|
1225
|
+
for (const rel of remove ?? []) {
|
|
1226
|
+
const type = normalizeLinearRelationType(rel.type);
|
|
1227
|
+
const issueRef = rel.issue.trim();
|
|
1228
|
+
if (!issueRef) throw new Error("Linear removeRelations.issue is required (uuid or ENG-123)");
|
|
1229
|
+
const match = current.find((r) => relationMatches(r, type, issueRef));
|
|
1230
|
+
if (!match?.id) {
|
|
1231
|
+
throw new Error(
|
|
1232
|
+
`Linear relation not found: ${type} ${issueRef}. Existing: ${formatRelationList(current)}`
|
|
1233
|
+
);
|
|
1234
|
+
}
|
|
1235
|
+
const json = await linearGraphql(ISSUE_RELATION_DELETE, { id: match.id }, opts);
|
|
1236
|
+
if (!json.issueRelationDelete?.success) {
|
|
1237
|
+
throw new Error(`Linear issueRelationDelete failed (${type} ${issueRef})`);
|
|
1238
|
+
}
|
|
1239
|
+
const idx = current.indexOf(match);
|
|
1240
|
+
if (idx >= 0) current.splice(idx, 1);
|
|
1241
|
+
}
|
|
1242
|
+
for (const rel of add ?? []) {
|
|
1243
|
+
const type = normalizeLinearRelationType(rel.type);
|
|
1244
|
+
const issueRef = rel.issue.trim();
|
|
1245
|
+
if (!issueRef) throw new Error("Linear relations.issue is required (uuid or ENG-123)");
|
|
1246
|
+
if (current.some((r) => relationMatches(r, type, issueRef))) continue;
|
|
1247
|
+
const related = await getLinearIssue(issueRef, opts);
|
|
1248
|
+
const input = linearRelationCreateInput(issue.id, type, related.id);
|
|
1249
|
+
const json = await linearGraphql(ISSUE_RELATION_CREATE, { input }, opts);
|
|
1250
|
+
if (!json.issueRelationCreate?.success) {
|
|
1251
|
+
throw new Error(`Linear issueRelationCreate failed (${type} ${related.identifier})`);
|
|
1252
|
+
}
|
|
1253
|
+
current.push({ type, issue: related, id: void 0 });
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
969
1256
|
function normalizePriority(priority) {
|
|
970
1257
|
if (priority == null) return void 0;
|
|
971
1258
|
if (!Number.isInteger(priority) || priority < 0 || priority > 4) {
|
|
@@ -973,7 +1260,6 @@ function normalizePriority(priority) {
|
|
|
973
1260
|
}
|
|
974
1261
|
return priority;
|
|
975
1262
|
}
|
|
976
|
-
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
977
1263
|
function assigneeFilterKey(assignee) {
|
|
978
1264
|
return (assignee ?? "").trim();
|
|
979
1265
|
}
|
|
@@ -1075,7 +1361,12 @@ async function listLinearCommentsSince(opts) {
|
|
|
1075
1361
|
first,
|
|
1076
1362
|
filter: {
|
|
1077
1363
|
createdAt: { gte: opts.since },
|
|
1078
|
-
issue: buildLinearIssueFilter({
|
|
1364
|
+
issue: buildLinearIssueFilter({
|
|
1365
|
+
assignee,
|
|
1366
|
+
// Search hits may match identifier/comments — do not also require
|
|
1367
|
+
// title/description contains when an allowlist is already applied.
|
|
1368
|
+
query: restrictToIssues ? void 0 : query || void 0
|
|
1369
|
+
})
|
|
1079
1370
|
}
|
|
1080
1371
|
},
|
|
1081
1372
|
opts
|
|
@@ -1175,8 +1466,10 @@ async function updateLinearIssue(input, opts) {
|
|
|
1175
1466
|
if (input.description !== void 0) {
|
|
1176
1467
|
mutationInput.description = input.description;
|
|
1177
1468
|
}
|
|
1178
|
-
const
|
|
1179
|
-
const
|
|
1469
|
+
const addRelations = input.relations?.length ? input.relations : void 0;
|
|
1470
|
+
const removeRelations = input.removeRelations?.length ? input.removeRelations : void 0;
|
|
1471
|
+
const needsIssue = Boolean(input.state?.trim()) || input.cycle !== void 0 || Boolean(addRelations) || Boolean(removeRelations);
|
|
1472
|
+
const existing = needsIssue ? await getLinearIssue(issueId, opts) : void 0;
|
|
1180
1473
|
if (input.state?.trim()) {
|
|
1181
1474
|
const { teams } = await listLinearTeams(opts);
|
|
1182
1475
|
const teamRef = existing?.team?.key || existing?.team?.id;
|
|
@@ -1209,14 +1502,44 @@ async function updateLinearIssue(input, opts) {
|
|
|
1209
1502
|
if (input.priority !== void 0) {
|
|
1210
1503
|
mutationInput.priority = normalizePriority(input.priority);
|
|
1211
1504
|
}
|
|
1212
|
-
if (
|
|
1213
|
-
|
|
1505
|
+
if (input.labels !== void 0) {
|
|
1506
|
+
mutationInput.labelIds = await resolveLinearLabelIds(input.labels, opts);
|
|
1214
1507
|
}
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
throw new Error("Linear issueUpdate failed");
|
|
1508
|
+
if (input.project !== void 0) {
|
|
1509
|
+
mutationInput.projectId = await resolveLinearProjectId(input.project, opts);
|
|
1218
1510
|
}
|
|
1219
|
-
|
|
1511
|
+
if (input.parent !== void 0) {
|
|
1512
|
+
if (isLinearNoneToken(input.parent)) {
|
|
1513
|
+
mutationInput.parentId = null;
|
|
1514
|
+
} else {
|
|
1515
|
+
const parent = await getLinearIssue((input.parent ?? "").trim(), opts);
|
|
1516
|
+
mutationInput.parentId = parent.id;
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
const hasIssueFields = Object.keys(mutationInput).length > 0;
|
|
1520
|
+
const hasRelationWrites = Boolean(addRelations || removeRelations);
|
|
1521
|
+
if (!hasIssueFields && !hasRelationWrites) {
|
|
1522
|
+
throw new Error(
|
|
1523
|
+
"linear_update_issue needs at least one of title, description, state, assignee, priority, cycle, labels, project, parent, relations, removeRelations"
|
|
1524
|
+
);
|
|
1525
|
+
}
|
|
1526
|
+
let updated;
|
|
1527
|
+
if (hasIssueFields) {
|
|
1528
|
+
const json = await linearGraphql(ISSUE_UPDATE, { id: issueId, input: mutationInput }, opts);
|
|
1529
|
+
if (!json.issueUpdate?.success || !json.issueUpdate.issue) {
|
|
1530
|
+
throw new Error("Linear issueUpdate failed");
|
|
1531
|
+
}
|
|
1532
|
+
updated = mapIssue(json.issueUpdate.issue);
|
|
1533
|
+
}
|
|
1534
|
+
if (hasRelationWrites) {
|
|
1535
|
+
const base = updated ?? existing;
|
|
1536
|
+
if (!base) {
|
|
1537
|
+
throw new Error(`Linear issue not found: ${issueId}`);
|
|
1538
|
+
}
|
|
1539
|
+
await applyLinearIssueRelations(base, addRelations, removeRelations, opts);
|
|
1540
|
+
return getLinearIssue(base.id || issueId, opts);
|
|
1541
|
+
}
|
|
1542
|
+
return updated;
|
|
1220
1543
|
}
|
|
1221
1544
|
async function commentLinearIssue(input, opts) {
|
|
1222
1545
|
const issueId = input.id.trim();
|
|
@@ -2460,14 +2783,23 @@ function registerAbleTimeTools(server) {
|
|
|
2460
2783
|
}
|
|
2461
2784
|
}
|
|
2462
2785
|
);
|
|
2786
|
+
const relationInputSchema = z4.object({
|
|
2787
|
+
type: z4.string().describe("blockedBy (this waits on issue) or blocks (issue waits on this)."),
|
|
2788
|
+
issue: z4.string().describe("Related task id or reference (CRM-232).")
|
|
2789
|
+
});
|
|
2463
2790
|
server.tool(
|
|
2464
2791
|
"abletime_update_task",
|
|
2465
|
-
|
|
2792
|
+
'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.',
|
|
2466
2793
|
{
|
|
2467
2794
|
id: z4.string(),
|
|
2468
2795
|
title: z4.string().optional(),
|
|
2469
2796
|
description: z4.string().optional(),
|
|
2470
|
-
state: z4.string().optional()
|
|
2797
|
+
state: z4.string().optional(),
|
|
2798
|
+
labels: z4.array(z4.string()).optional().describe("Replace tags/labels. Empty array clears when the host accepts it."),
|
|
2799
|
+
project: z4.string().nullable().optional().describe("Project name or id. AbleTime tasks stay in a project."),
|
|
2800
|
+
parent: z4.string().nullable().optional().describe('Parent task id or reference (CRM-232), or "none" to unset.'),
|
|
2801
|
+
relations: z4.array(relationInputSchema).optional().describe("Set a wait-on dependency: blockedBy or blocks."),
|
|
2802
|
+
removeRelations: z4.array(relationInputSchema).optional().describe("Clear a wait-on dependency.")
|
|
2471
2803
|
},
|
|
2472
2804
|
async (args) => {
|
|
2473
2805
|
try {
|
|
@@ -2601,14 +2933,23 @@ function registerGithubIssueTools(server) {
|
|
|
2601
2933
|
}
|
|
2602
2934
|
}
|
|
2603
2935
|
);
|
|
2936
|
+
const relationInputSchema = z5.object({
|
|
2937
|
+
type: z5.string().describe("blocks or blockedBy (this issue's view)."),
|
|
2938
|
+
issue: z5.string().describe("Related issue #123 or URL.")
|
|
2939
|
+
});
|
|
2604
2940
|
server.tool(
|
|
2605
2941
|
"github_update_issue",
|
|
2606
|
-
|
|
2942
|
+
'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.',
|
|
2607
2943
|
{
|
|
2608
2944
|
id: z5.string(),
|
|
2609
2945
|
title: z5.string().optional(),
|
|
2610
2946
|
body: z5.string().optional(),
|
|
2611
2947
|
state: z5.string().optional().describe("open or closed"),
|
|
2948
|
+
labels: z5.array(z5.string()).optional().describe("Replace all labels. Empty array clears. Call github_get_issue first to keep existing."),
|
|
2949
|
+
project: z5.string().nullable().optional().describe('GitHub Project title, or "none" to remove current projects.'),
|
|
2950
|
+
parent: z5.string().nullable().optional().describe('Parent issue #123 or URL, or "none" to unset.'),
|
|
2951
|
+
relations: z5.array(relationInputSchema).optional().describe("Add blockedBy or blocks relations (issue #123 or URL)."),
|
|
2952
|
+
removeRelations: z5.array(relationInputSchema).optional().describe("Remove blockedBy or blocks relations."),
|
|
2612
2953
|
repoPath: repoPathSchema
|
|
2613
2954
|
},
|
|
2614
2955
|
async (args) => {
|
|
@@ -2738,9 +3079,13 @@ function registerLinearTools(server) {
|
|
|
2738
3079
|
}
|
|
2739
3080
|
}
|
|
2740
3081
|
);
|
|
3082
|
+
const relationInputSchema = z6.object({
|
|
3083
|
+
type: z6.string().describe("blocks, blockedBy, related, duplicate, or duplicateOf (this issue's view)."),
|
|
3084
|
+
issue: z6.string().describe("Related issue uuid or identifier (ENG-123).")
|
|
3085
|
+
});
|
|
2741
3086
|
server.tool(
|
|
2742
3087
|
"linear_update_issue",
|
|
2743
|
-
'Update a Linear issue (uuid or ENG-123). Pass title, description, state, assignee, priority, and/or
|
|
3088
|
+
'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.',
|
|
2744
3089
|
{
|
|
2745
3090
|
id: z6.string(),
|
|
2746
3091
|
title: z6.string().optional(),
|
|
@@ -2748,7 +3093,12 @@ function registerLinearTools(server) {
|
|
|
2748
3093
|
state: z6.string().optional(),
|
|
2749
3094
|
assignee: z6.string().nullable().optional(),
|
|
2750
3095
|
priority: prioritySchema,
|
|
2751
|
-
cycle: z6.string().nullable().optional().describe('Cycle name, number, "current"/"active", or "none" to unschedule.')
|
|
3096
|
+
cycle: z6.string().nullable().optional().describe('Cycle name, number, "current"/"active", or "none" to unschedule.'),
|
|
3097
|
+
labels: z6.array(z6.string()).optional().describe("Replace all labels (names or ids). Empty array clears. Call linear_get_issue first to keep existing."),
|
|
3098
|
+
project: z6.string().nullable().optional().describe('Project name, id, slug, or "none" to unassign.'),
|
|
3099
|
+
parent: z6.string().nullable().optional().describe('Parent issue uuid or identifier (ENG-123), or "none" to unset.'),
|
|
3100
|
+
relations: z6.array(relationInputSchema).optional().describe("Add relations from this issue's view (blocks, blockedBy, related, duplicate, duplicateOf)."),
|
|
3101
|
+
removeRelations: z6.array(relationInputSchema).optional().describe("Remove relations by type + related issue (uuid or ENG-123).")
|
|
2752
3102
|
},
|
|
2753
3103
|
async (args) => {
|
|
2754
3104
|
try {
|
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
resolveSendFollowUp,
|
|
9
9
|
startOrchestration,
|
|
10
10
|
waitForPidExit
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import "./chunk-
|
|
11
|
+
} from "./chunk-GPSIZGEZ.js";
|
|
12
|
+
import "./chunk-GBWKMWUQ.js";
|
|
13
13
|
import "./chunk-HVIEYTUK.js";
|
|
14
14
|
import "./chunk-Y4MIXGPA.js";
|
|
15
15
|
import "./chunk-VKQGAWHY.js";
|
|
@@ -6,9 +6,9 @@ import {
|
|
|
6
6
|
resolveSendFollowUp,
|
|
7
7
|
startOrchestration,
|
|
8
8
|
waitForPidExit
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-5RRAKGPK.js";
|
|
10
10
|
import "./chunk-DJU54RYW.js";
|
|
11
|
-
import "./chunk-
|
|
11
|
+
import "./chunk-OLDZXO53.js";
|
|
12
12
|
import "./chunk-VIODP5PT.js";
|
|
13
13
|
import "./chunk-WXKXSZN4.js";
|
|
14
14
|
import "./chunk-6K64FJDD.js";
|
|
@@ -16,7 +16,7 @@ import "./chunk-IEEAVWAP.js";
|
|
|
16
16
|
import "./chunk-AN3THU7F.js";
|
|
17
17
|
import "./chunk-N7WZ6NIN.js";
|
|
18
18
|
import "./chunk-AXQ4ZWHK.js";
|
|
19
|
-
import "./chunk-
|
|
19
|
+
import "./chunk-FY64F2ZQ.js";
|
|
20
20
|
import "./chunk-J7LQVLFQ.js";
|
|
21
21
|
import "./chunk-4TR3HZFT.js";
|
|
22
22
|
import "./chunk-AO35MGOE.js";
|