@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/index.js
CHANGED
|
@@ -238,7 +238,7 @@ import {
|
|
|
238
238
|
worktreeCleanupSettings,
|
|
239
239
|
wrapReviewSkillMarkdown,
|
|
240
240
|
writeWorktreeFile
|
|
241
|
-
} from "./chunk-
|
|
241
|
+
} from "./chunk-5RRAKGPK.js";
|
|
242
242
|
import {
|
|
243
243
|
LEGACY_PLAN_FILE_REL,
|
|
244
244
|
PLAN_FILE_NAME,
|
|
@@ -344,7 +344,7 @@ import {
|
|
|
344
344
|
withEventParentId,
|
|
345
345
|
withEventsParentId,
|
|
346
346
|
writeInjectedMcpConfig
|
|
347
|
-
} from "./chunk-
|
|
347
|
+
} from "./chunk-OLDZXO53.js";
|
|
348
348
|
import {
|
|
349
349
|
CLOUD_COORDINATOR_BUSY_REPLY,
|
|
350
350
|
CLOUD_COORDINATOR_STOPPED_REPLY,
|
|
@@ -514,9 +514,13 @@ import {
|
|
|
514
514
|
} from "./chunk-AN3THU7F.js";
|
|
515
515
|
import {
|
|
516
516
|
ABLETIME_MCP_PATH,
|
|
517
|
+
ABLETIME_MCP_PM_PATH,
|
|
518
|
+
ABLETIME_PM_TOOL_NAMES,
|
|
517
519
|
DEFAULT_ABLETIME_HOST,
|
|
520
|
+
ableTimeDependencyTarget,
|
|
518
521
|
abletimeMcpRequest,
|
|
519
522
|
abletimeMcpUrl,
|
|
523
|
+
abletimeToolUsesPm,
|
|
520
524
|
callAbleTimeTool,
|
|
521
525
|
commentAbleTimeTask,
|
|
522
526
|
createAbleTimeTask,
|
|
@@ -535,7 +539,7 @@ import {
|
|
|
535
539
|
toAbleTimeIssueInfo,
|
|
536
540
|
updateAbleTimeTask,
|
|
537
541
|
verifyAbleTimeConnection
|
|
538
|
-
} from "./chunk-
|
|
542
|
+
} from "./chunk-4M2AK3O5.js";
|
|
539
543
|
import {
|
|
540
544
|
formatFetchError,
|
|
541
545
|
httpFetch,
|
|
@@ -553,7 +557,7 @@ import {
|
|
|
553
557
|
trustSystemCertificates,
|
|
554
558
|
turnCostUsdFromCursorUsage,
|
|
555
559
|
withMaxOldSpaceSize
|
|
556
|
-
} from "./chunk-
|
|
560
|
+
} from "./chunk-FY64F2ZQ.js";
|
|
557
561
|
import {
|
|
558
562
|
FOLLOW_UP_BEHAVIORS,
|
|
559
563
|
GITHUB_GIT_AUTH_MODES,
|
|
@@ -1159,12 +1163,14 @@ var ISSUE_FIELDS = `
|
|
|
1159
1163
|
children(first: 25) { nodes { ${ISSUE_REF_FIELDS} } }
|
|
1160
1164
|
relations(first: 25) {
|
|
1161
1165
|
nodes {
|
|
1166
|
+
id
|
|
1162
1167
|
type
|
|
1163
1168
|
relatedIssue { ${ISSUE_REF_FIELDS} }
|
|
1164
1169
|
}
|
|
1165
1170
|
}
|
|
1166
1171
|
inverseRelations(first: 25) {
|
|
1167
1172
|
nodes {
|
|
1173
|
+
id
|
|
1168
1174
|
type
|
|
1169
1175
|
issue { ${ISSUE_REF_FIELDS} }
|
|
1170
1176
|
}
|
|
@@ -1284,6 +1290,35 @@ mutation SideboardCommentCreate($input: CommentCreateInput!) {
|
|
|
1284
1290
|
}
|
|
1285
1291
|
}
|
|
1286
1292
|
`;
|
|
1293
|
+
var LABELS_QUERY = `
|
|
1294
|
+
query SideboardLabels($first: Int!) {
|
|
1295
|
+
issueLabels(first: $first) {
|
|
1296
|
+
nodes { id name }
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
`;
|
|
1300
|
+
var PROJECTS_QUERY = `
|
|
1301
|
+
query SideboardProjects($first: Int!, $filter: ProjectFilter) {
|
|
1302
|
+
projects(first: $first, filter: $filter) {
|
|
1303
|
+
nodes { id name slugId }
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
`;
|
|
1307
|
+
var ISSUE_RELATION_CREATE = `
|
|
1308
|
+
mutation SideboardIssueRelationCreate($input: IssueRelationCreateInput!) {
|
|
1309
|
+
issueRelationCreate(input: $input) {
|
|
1310
|
+
success
|
|
1311
|
+
issueRelation { id type }
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
`;
|
|
1315
|
+
var ISSUE_RELATION_DELETE = `
|
|
1316
|
+
mutation SideboardIssueRelationDelete($id: String!) {
|
|
1317
|
+
issueRelationDelete(id: $id) {
|
|
1318
|
+
success
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
`;
|
|
1287
1322
|
function rewriteLinearError(message) {
|
|
1288
1323
|
if (/scope|permission|not (authorized|allowed)|unauthorized|insufficient/i.test(message)) {
|
|
1289
1324
|
return `${message} \u2014 Disconnect and Connect Linear in Account settings to grant write access.`;
|
|
@@ -1403,12 +1438,18 @@ function mapRelations(node) {
|
|
|
1403
1438
|
for (const rel of node.relations?.nodes ?? []) {
|
|
1404
1439
|
const issue = mapIssueRef(rel.relatedIssue);
|
|
1405
1440
|
if (!issue) continue;
|
|
1406
|
-
|
|
1441
|
+
const id = rel.id?.trim();
|
|
1442
|
+
out.push({ type: String(rel.type ?? "related"), issue, ...id ? { id } : {} });
|
|
1407
1443
|
}
|
|
1408
1444
|
for (const rel of node.inverseRelations?.nodes ?? []) {
|
|
1409
1445
|
const issue = mapIssueRef(rel.issue);
|
|
1410
1446
|
if (!issue) continue;
|
|
1411
|
-
|
|
1447
|
+
const id = rel.id?.trim();
|
|
1448
|
+
out.push({
|
|
1449
|
+
type: flipLinearRelationType(String(rel.type ?? "related")),
|
|
1450
|
+
issue,
|
|
1451
|
+
...id ? { id } : {}
|
|
1452
|
+
});
|
|
1412
1453
|
}
|
|
1413
1454
|
return out;
|
|
1414
1455
|
}
|
|
@@ -1554,7 +1595,14 @@ function resolveLinearCycle(team, cycle) {
|
|
|
1554
1595
|
}
|
|
1555
1596
|
const lower = s.toLowerCase();
|
|
1556
1597
|
const asNumber = Number.parseInt(s, 10);
|
|
1557
|
-
const
|
|
1598
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1599
|
+
const cycles = [team.activeCycle, ...team.cycles ?? []].filter(
|
|
1600
|
+
(c) => {
|
|
1601
|
+
if (!c?.id || seen.has(c.id)) return false;
|
|
1602
|
+
seen.add(c.id);
|
|
1603
|
+
return true;
|
|
1604
|
+
}
|
|
1605
|
+
);
|
|
1558
1606
|
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);
|
|
1559
1607
|
if (!found) {
|
|
1560
1608
|
const available = cycles.map((c) => c.number != null ? `${c.name} (${c.number})` : c.name).join(", ") || "(none)";
|
|
@@ -1562,6 +1610,148 @@ function resolveLinearCycle(team, cycle) {
|
|
|
1562
1610
|
}
|
|
1563
1611
|
return found.id;
|
|
1564
1612
|
}
|
|
1613
|
+
var LINEAR_NONE_RE = /^(none|clear|unschedule|null)$/i;
|
|
1614
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1615
|
+
function isLinearNoneToken(value) {
|
|
1616
|
+
if (value == null) return true;
|
|
1617
|
+
const s = value.trim();
|
|
1618
|
+
return !s || LINEAR_NONE_RE.test(s);
|
|
1619
|
+
}
|
|
1620
|
+
function resolveLinearLabel(labels, name) {
|
|
1621
|
+
const s = name.trim();
|
|
1622
|
+
if (!s) throw new Error("Linear label is empty");
|
|
1623
|
+
const lower = s.toLowerCase();
|
|
1624
|
+
const found = labels.find((x) => x.id === s) || labels.find((x) => x.name.toLowerCase() === lower);
|
|
1625
|
+
if (!found) {
|
|
1626
|
+
const available = labels.map((x) => x.name).filter(Boolean).slice(0, 20).join(", ") || "(none)";
|
|
1627
|
+
throw new Error(`Linear label not found: ${name}. Available: ${available}`);
|
|
1628
|
+
}
|
|
1629
|
+
return found;
|
|
1630
|
+
}
|
|
1631
|
+
function resolveLinearProject(projects, project) {
|
|
1632
|
+
if (isLinearNoneToken(project)) return null;
|
|
1633
|
+
const s = (project ?? "").trim();
|
|
1634
|
+
const lower = s.toLowerCase();
|
|
1635
|
+
const found = projects.find((x) => x.id === s) || projects.find((x) => x.name.toLowerCase() === lower) || projects.find((x) => x.slugId?.toLowerCase() === lower);
|
|
1636
|
+
if (!found) {
|
|
1637
|
+
const available = projects.map((x) => x.name).filter(Boolean).slice(0, 20).join(", ") || "(none)";
|
|
1638
|
+
throw new Error(`Linear project not found: ${project}. Available: ${available}`);
|
|
1639
|
+
}
|
|
1640
|
+
return found.id;
|
|
1641
|
+
}
|
|
1642
|
+
function normalizeLinearRelationType(type) {
|
|
1643
|
+
const s = type.trim().toLowerCase().replace(/[\s_-]+/g, "");
|
|
1644
|
+
if (s === "blocks" || s === "block") return "blocks";
|
|
1645
|
+
if (s === "blockedby" || s === "blocked") return "blockedBy";
|
|
1646
|
+
if (s === "related" || s === "relates" || s === "relatesto") return "related";
|
|
1647
|
+
if (s === "duplicate" || s === "dup") return "duplicate";
|
|
1648
|
+
if (s === "duplicateof" || s === "original") return "duplicateOf";
|
|
1649
|
+
if (s === "similar") return "similar";
|
|
1650
|
+
throw new Error(
|
|
1651
|
+
`Linear relation type must be blocks, blockedBy, related, duplicate, or duplicateOf (got ${type})`
|
|
1652
|
+
);
|
|
1653
|
+
}
|
|
1654
|
+
function linearRelationCreateInput(thisIssueId, type, relatedIssueId) {
|
|
1655
|
+
const view = normalizeLinearRelationType(type);
|
|
1656
|
+
if (view === "blockedBy") {
|
|
1657
|
+
return { issueId: relatedIssueId, relatedIssueId: thisIssueId, type: "blocks" };
|
|
1658
|
+
}
|
|
1659
|
+
if (view === "duplicateOf") {
|
|
1660
|
+
return { issueId: relatedIssueId, relatedIssueId: thisIssueId, type: "duplicate" };
|
|
1661
|
+
}
|
|
1662
|
+
return { issueId: thisIssueId, relatedIssueId, type: view };
|
|
1663
|
+
}
|
|
1664
|
+
function relationMatches(rel, type, issueRef) {
|
|
1665
|
+
if (rel.type.toLowerCase() !== type.toLowerCase()) return false;
|
|
1666
|
+
const ref = issueRef.trim().toLowerCase();
|
|
1667
|
+
return rel.issue.id.toLowerCase() === ref || rel.issue.identifier.toLowerCase() === ref;
|
|
1668
|
+
}
|
|
1669
|
+
function formatRelationList(relations) {
|
|
1670
|
+
if (!relations.length) return "(none)";
|
|
1671
|
+
return relations.map((r) => `${r.type} ${r.issue.identifier || r.issue.id}`).join(", ");
|
|
1672
|
+
}
|
|
1673
|
+
async function listLinearLabels(opts) {
|
|
1674
|
+
const json = await linearGraphql(LABELS_QUERY, { first: 250 }, opts);
|
|
1675
|
+
return (json.issueLabels?.nodes ?? []).flatMap((node) => {
|
|
1676
|
+
const id = String(node.id ?? "").trim();
|
|
1677
|
+
const name = String(node.name ?? "").trim();
|
|
1678
|
+
if (!id) return [];
|
|
1679
|
+
return [{ id, name }];
|
|
1680
|
+
});
|
|
1681
|
+
}
|
|
1682
|
+
async function listLinearProjects(filter, opts) {
|
|
1683
|
+
const json = await linearGraphql(PROJECTS_QUERY, { first: 50, filter: filter ?? {} }, opts);
|
|
1684
|
+
return (json.projects?.nodes ?? []).flatMap((node) => {
|
|
1685
|
+
const id = String(node.id ?? "").trim();
|
|
1686
|
+
if (!id) return [];
|
|
1687
|
+
return [
|
|
1688
|
+
{
|
|
1689
|
+
id,
|
|
1690
|
+
name: String(node.name ?? ""),
|
|
1691
|
+
...node.slugId ? { slugId: String(node.slugId) } : {}
|
|
1692
|
+
}
|
|
1693
|
+
];
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
async function resolveLinearProjectId(project, opts) {
|
|
1697
|
+
if (isLinearNoneToken(project)) return null;
|
|
1698
|
+
const s = (project ?? "").trim();
|
|
1699
|
+
const filter = UUID_RE.test(s) ? { id: { eq: s } } : { name: { eqIgnoreCase: s } };
|
|
1700
|
+
const matched = await listLinearProjects(filter, opts);
|
|
1701
|
+
try {
|
|
1702
|
+
return resolveLinearProject(matched, s);
|
|
1703
|
+
} catch {
|
|
1704
|
+
if (UUID_RE.test(s)) return s;
|
|
1705
|
+
const listed = await listLinearProjects(void 0, opts);
|
|
1706
|
+
return resolveLinearProject(listed, s);
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
async function resolveLinearLabelIds(names, opts) {
|
|
1710
|
+
if (names.length === 0) return [];
|
|
1711
|
+
const labels = await listLinearLabels(opts);
|
|
1712
|
+
return names.map((name) => {
|
|
1713
|
+
const s = name.trim();
|
|
1714
|
+
try {
|
|
1715
|
+
return resolveLinearLabel(labels, s).id;
|
|
1716
|
+
} catch (error) {
|
|
1717
|
+
if (UUID_RE.test(s)) return s;
|
|
1718
|
+
throw error;
|
|
1719
|
+
}
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
async function applyLinearIssueRelations(issue, add, remove, opts) {
|
|
1723
|
+
const current = [...issue.relations];
|
|
1724
|
+
for (const rel of remove ?? []) {
|
|
1725
|
+
const type = normalizeLinearRelationType(rel.type);
|
|
1726
|
+
const issueRef = rel.issue.trim();
|
|
1727
|
+
if (!issueRef) throw new Error("Linear removeRelations.issue is required (uuid or ENG-123)");
|
|
1728
|
+
const match = current.find((r) => relationMatches(r, type, issueRef));
|
|
1729
|
+
if (!match?.id) {
|
|
1730
|
+
throw new Error(
|
|
1731
|
+
`Linear relation not found: ${type} ${issueRef}. Existing: ${formatRelationList(current)}`
|
|
1732
|
+
);
|
|
1733
|
+
}
|
|
1734
|
+
const json = await linearGraphql(ISSUE_RELATION_DELETE, { id: match.id }, opts);
|
|
1735
|
+
if (!json.issueRelationDelete?.success) {
|
|
1736
|
+
throw new Error(`Linear issueRelationDelete failed (${type} ${issueRef})`);
|
|
1737
|
+
}
|
|
1738
|
+
const idx = current.indexOf(match);
|
|
1739
|
+
if (idx >= 0) current.splice(idx, 1);
|
|
1740
|
+
}
|
|
1741
|
+
for (const rel of add ?? []) {
|
|
1742
|
+
const type = normalizeLinearRelationType(rel.type);
|
|
1743
|
+
const issueRef = rel.issue.trim();
|
|
1744
|
+
if (!issueRef) throw new Error("Linear relations.issue is required (uuid or ENG-123)");
|
|
1745
|
+
if (current.some((r) => relationMatches(r, type, issueRef))) continue;
|
|
1746
|
+
const related = await getLinearIssue(issueRef, opts);
|
|
1747
|
+
const input = linearRelationCreateInput(issue.id, type, related.id);
|
|
1748
|
+
const json = await linearGraphql(ISSUE_RELATION_CREATE, { input }, opts);
|
|
1749
|
+
if (!json.issueRelationCreate?.success) {
|
|
1750
|
+
throw new Error(`Linear issueRelationCreate failed (${type} ${related.identifier})`);
|
|
1751
|
+
}
|
|
1752
|
+
current.push({ type, issue: related, id: void 0 });
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1565
1755
|
function normalizePriority(priority) {
|
|
1566
1756
|
if (priority == null) return void 0;
|
|
1567
1757
|
if (!Number.isInteger(priority) || priority < 0 || priority > 4) {
|
|
@@ -1569,7 +1759,6 @@ function normalizePriority(priority) {
|
|
|
1569
1759
|
}
|
|
1570
1760
|
return priority;
|
|
1571
1761
|
}
|
|
1572
|
-
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1573
1762
|
function assigneeFilterKey(assignee) {
|
|
1574
1763
|
return (assignee ?? "").trim();
|
|
1575
1764
|
}
|
|
@@ -1671,7 +1860,12 @@ async function listLinearCommentsSince(opts) {
|
|
|
1671
1860
|
first,
|
|
1672
1861
|
filter: {
|
|
1673
1862
|
createdAt: { gte: opts.since },
|
|
1674
|
-
issue: buildLinearIssueFilter({
|
|
1863
|
+
issue: buildLinearIssueFilter({
|
|
1864
|
+
assignee,
|
|
1865
|
+
// Search hits may match identifier/comments — do not also require
|
|
1866
|
+
// title/description contains when an allowlist is already applied.
|
|
1867
|
+
query: restrictToIssues ? void 0 : query || void 0
|
|
1868
|
+
})
|
|
1675
1869
|
}
|
|
1676
1870
|
},
|
|
1677
1871
|
opts
|
|
@@ -1777,8 +1971,10 @@ async function updateLinearIssue(input, opts) {
|
|
|
1777
1971
|
if (input.description !== void 0) {
|
|
1778
1972
|
mutationInput.description = input.description;
|
|
1779
1973
|
}
|
|
1780
|
-
const
|
|
1781
|
-
const
|
|
1974
|
+
const addRelations = input.relations?.length ? input.relations : void 0;
|
|
1975
|
+
const removeRelations = input.removeRelations?.length ? input.removeRelations : void 0;
|
|
1976
|
+
const needsIssue = Boolean(input.state?.trim()) || input.cycle !== void 0 || Boolean(addRelations) || Boolean(removeRelations);
|
|
1977
|
+
const existing = needsIssue ? await getLinearIssue(issueId, opts) : void 0;
|
|
1782
1978
|
if (input.state?.trim()) {
|
|
1783
1979
|
const { teams } = await listLinearTeams(opts);
|
|
1784
1980
|
const teamRef = existing?.team?.key || existing?.team?.id;
|
|
@@ -1811,14 +2007,44 @@ async function updateLinearIssue(input, opts) {
|
|
|
1811
2007
|
if (input.priority !== void 0) {
|
|
1812
2008
|
mutationInput.priority = normalizePriority(input.priority);
|
|
1813
2009
|
}
|
|
1814
|
-
if (
|
|
1815
|
-
|
|
2010
|
+
if (input.labels !== void 0) {
|
|
2011
|
+
mutationInput.labelIds = await resolveLinearLabelIds(input.labels, opts);
|
|
2012
|
+
}
|
|
2013
|
+
if (input.project !== void 0) {
|
|
2014
|
+
mutationInput.projectId = await resolveLinearProjectId(input.project, opts);
|
|
2015
|
+
}
|
|
2016
|
+
if (input.parent !== void 0) {
|
|
2017
|
+
if (isLinearNoneToken(input.parent)) {
|
|
2018
|
+
mutationInput.parentId = null;
|
|
2019
|
+
} else {
|
|
2020
|
+
const parent = await getLinearIssue((input.parent ?? "").trim(), opts);
|
|
2021
|
+
mutationInput.parentId = parent.id;
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
const hasIssueFields = Object.keys(mutationInput).length > 0;
|
|
2025
|
+
const hasRelationWrites = Boolean(addRelations || removeRelations);
|
|
2026
|
+
if (!hasIssueFields && !hasRelationWrites) {
|
|
2027
|
+
throw new Error(
|
|
2028
|
+
"linear_update_issue needs at least one of title, description, state, assignee, priority, cycle, labels, project, parent, relations, removeRelations"
|
|
2029
|
+
);
|
|
1816
2030
|
}
|
|
1817
|
-
|
|
1818
|
-
if (
|
|
1819
|
-
|
|
2031
|
+
let updated;
|
|
2032
|
+
if (hasIssueFields) {
|
|
2033
|
+
const json = await linearGraphql(ISSUE_UPDATE, { id: issueId, input: mutationInput }, opts);
|
|
2034
|
+
if (!json.issueUpdate?.success || !json.issueUpdate.issue) {
|
|
2035
|
+
throw new Error("Linear issueUpdate failed");
|
|
2036
|
+
}
|
|
2037
|
+
updated = mapIssue(json.issueUpdate.issue);
|
|
1820
2038
|
}
|
|
1821
|
-
|
|
2039
|
+
if (hasRelationWrites) {
|
|
2040
|
+
const base = updated ?? existing;
|
|
2041
|
+
if (!base) {
|
|
2042
|
+
throw new Error(`Linear issue not found: ${issueId}`);
|
|
2043
|
+
}
|
|
2044
|
+
await applyLinearIssueRelations(base, addRelations, removeRelations, opts);
|
|
2045
|
+
return getLinearIssue(base.id || issueId, opts);
|
|
2046
|
+
}
|
|
2047
|
+
return updated;
|
|
1822
2048
|
}
|
|
1823
2049
|
async function commentLinearIssue(input, opts) {
|
|
1824
2050
|
const issueId = input.id.trim();
|
|
@@ -1970,7 +2196,7 @@ async function listGitHubIssueCommentsSince(opts) {
|
|
|
1970
2196
|
}).filter((n) => n != null)
|
|
1971
2197
|
);
|
|
1972
2198
|
if (restrictToIssues && allowed.size === 0) return [];
|
|
1973
|
-
const pageSize =
|
|
2199
|
+
const pageSize = GITHUB_ISSUE_COMMENTS_PAGE_SIZE;
|
|
1974
2200
|
const out = [];
|
|
1975
2201
|
for (let page = 1; page <= GITHUB_ISSUE_COMMENTS_MAX_PAGES; page++) {
|
|
1976
2202
|
const path2 = `repos/${slug}/issues/comments?since=${encodeURIComponent(opts.since)}&sort=created&direction=desc&per_page=${pageSize}&page=${page}`;
|
|
@@ -2038,13 +2264,78 @@ async function commentGitHubIssue(input, opts) {
|
|
|
2038
2264
|
const url = stdout.trim().split(/\s+/).find((part) => /^https?:\/\//i.test(part));
|
|
2039
2265
|
return { body, url };
|
|
2040
2266
|
}
|
|
2267
|
+
function isGitHubNoneToken(value) {
|
|
2268
|
+
if (value == null) return true;
|
|
2269
|
+
const s = value.trim();
|
|
2270
|
+
return !s || /^(none|clear|unschedule|null)$/i.test(s);
|
|
2271
|
+
}
|
|
2272
|
+
function githubRelationEditFlag(type, remove = false) {
|
|
2273
|
+
const s = type.trim().toLowerCase().replace(/[\s_-]+/g, "");
|
|
2274
|
+
if (s === "blockedby" || s === "blocked" || s === "dependson") {
|
|
2275
|
+
return remove ? "--remove-blocked-by" : "--add-blocked-by";
|
|
2276
|
+
}
|
|
2277
|
+
if (s === "blocks" || s === "blocking") {
|
|
2278
|
+
return remove ? "--remove-blocking" : "--add-blocking";
|
|
2279
|
+
}
|
|
2280
|
+
throw new Error(
|
|
2281
|
+
`GitHub relation type must be blocks or blockedBy (got ${type}). Parent is a separate parent= field.`
|
|
2282
|
+
);
|
|
2283
|
+
}
|
|
2284
|
+
function labelsEqual(a, b) {
|
|
2285
|
+
return a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
2286
|
+
}
|
|
2287
|
+
function parseProjectTitles(raw) {
|
|
2288
|
+
const out = [];
|
|
2289
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2290
|
+
const push = (name) => {
|
|
2291
|
+
const title = name.trim();
|
|
2292
|
+
const key = title.toLowerCase();
|
|
2293
|
+
if (!title || seen.has(key)) return;
|
|
2294
|
+
seen.add(key);
|
|
2295
|
+
out.push(title);
|
|
2296
|
+
};
|
|
2297
|
+
const fromItems = raw.projectItems ?? raw.projects ?? raw.projectItemsV2;
|
|
2298
|
+
if (Array.isArray(fromItems)) {
|
|
2299
|
+
for (const item of fromItems) {
|
|
2300
|
+
if (typeof item === "string") {
|
|
2301
|
+
push(item);
|
|
2302
|
+
continue;
|
|
2303
|
+
}
|
|
2304
|
+
if (!item || typeof item !== "object") continue;
|
|
2305
|
+
const rec = item;
|
|
2306
|
+
const nested = rec.project && typeof rec.project === "object" ? rec.project : rec;
|
|
2307
|
+
const title = String(nested.title ?? nested.name ?? rec.title ?? rec.name ?? "").trim();
|
|
2308
|
+
if (title) push(title);
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
return out;
|
|
2312
|
+
}
|
|
2313
|
+
async function listGitHubIssueProjects(number, cwd, repoArgs) {
|
|
2314
|
+
const result = await gh(
|
|
2315
|
+
["issue", "view", String(number), ...repoArgs, "--json", "projectItems,projects"],
|
|
2316
|
+
cwd,
|
|
2317
|
+
{ reject: false }
|
|
2318
|
+
);
|
|
2319
|
+
if (result.exitCode !== 0 || !result.stdout.trim()) return [];
|
|
2320
|
+
try {
|
|
2321
|
+
const parsed = JSON.parse(result.stdout);
|
|
2322
|
+
return parseProjectTitles(parsed);
|
|
2323
|
+
} catch {
|
|
2324
|
+
return [];
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2041
2327
|
async function updateGitHubIssue(input, opts) {
|
|
2042
2328
|
const number = parseGitHubIssueNumber(input.id);
|
|
2043
2329
|
const title = input.title?.trim();
|
|
2044
2330
|
const body = input.body;
|
|
2045
2331
|
const state = input.state?.trim().toLowerCase();
|
|
2046
|
-
|
|
2047
|
-
|
|
2332
|
+
const addRelations = input.relations?.length ? input.relations : void 0;
|
|
2333
|
+
const removeRelations = input.removeRelations?.length ? input.removeRelations : void 0;
|
|
2334
|
+
const hasEditFields = Boolean(title) || body !== void 0 || input.labels !== void 0 || input.project !== void 0 || input.parent !== void 0 || Boolean(addRelations) || Boolean(removeRelations);
|
|
2335
|
+
if (!hasEditFields && !state) {
|
|
2336
|
+
throw new Error(
|
|
2337
|
+
"github_update_issue needs at least one of title, body, state, labels, project, parent, relations, removeRelations"
|
|
2338
|
+
);
|
|
2048
2339
|
}
|
|
2049
2340
|
const { cwd, repoArgs } = await resolveGitHubIssueRepo(opts?.repoPath);
|
|
2050
2341
|
if (state === "closed" || state === "close") {
|
|
@@ -2060,10 +2351,46 @@ async function updateGitHubIssue(input, opts) {
|
|
|
2060
2351
|
} else if (state) {
|
|
2061
2352
|
throw new Error(`GitHub issue state must be open or closed (got ${input.state})`);
|
|
2062
2353
|
}
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2354
|
+
const args = ["issue", "edit", String(number), ...repoArgs];
|
|
2355
|
+
if (title) args.push("--title", title);
|
|
2356
|
+
if (body !== void 0) args.push("--body", body);
|
|
2357
|
+
if (input.labels !== void 0) {
|
|
2358
|
+
const existing = await getGitHubIssue(String(number), opts);
|
|
2359
|
+
const desired = input.labels.map((name) => name.trim()).filter(Boolean);
|
|
2360
|
+
const add = desired.filter((name) => !existing.labels.some((cur) => labelsEqual(cur, name)));
|
|
2361
|
+
const remove = existing.labels.filter((cur) => !desired.some((name) => labelsEqual(cur, name)));
|
|
2362
|
+
if (add.length) args.push("--add-label", add.join(","));
|
|
2363
|
+
if (remove.length) args.push("--remove-label", remove.join(","));
|
|
2364
|
+
}
|
|
2365
|
+
if (input.project !== void 0) {
|
|
2366
|
+
const current = await listGitHubIssueProjects(number, cwd, repoArgs);
|
|
2367
|
+
if (isGitHubNoneToken(input.project)) {
|
|
2368
|
+
for (const name of current) args.push("--remove-project", name);
|
|
2369
|
+
} else {
|
|
2370
|
+
const wanted = (input.project ?? "").trim();
|
|
2371
|
+
if (!current.some((name) => labelsEqual(name, wanted))) {
|
|
2372
|
+
args.push("--add-project", wanted);
|
|
2373
|
+
}
|
|
2374
|
+
for (const name of current) {
|
|
2375
|
+
if (!labelsEqual(name, wanted)) args.push("--remove-project", name);
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
if (input.parent !== void 0) {
|
|
2380
|
+
if (isGitHubNoneToken(input.parent)) {
|
|
2381
|
+
args.push("--remove-parent");
|
|
2382
|
+
} else {
|
|
2383
|
+
args.push("--parent", String(parseGitHubIssueNumber(input.parent ?? "")));
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
for (const rel of removeRelations ?? []) {
|
|
2387
|
+
args.push(githubRelationEditFlag(rel.type, true), String(parseGitHubIssueNumber(rel.issue)));
|
|
2388
|
+
}
|
|
2389
|
+
for (const rel of addRelations ?? []) {
|
|
2390
|
+
args.push(githubRelationEditFlag(rel.type, false), String(parseGitHubIssueNumber(rel.issue)));
|
|
2391
|
+
}
|
|
2392
|
+
const editChanged = args.length > 3 + repoArgs.length;
|
|
2393
|
+
if (editChanged) {
|
|
2067
2394
|
requireGhOk(await gh(args, cwd, { reject: false }), `GitHub edit #${number}`);
|
|
2068
2395
|
}
|
|
2069
2396
|
return getGitHubIssue(String(number), opts);
|
|
@@ -3659,14 +3986,23 @@ function registerAbleTimeTools(server) {
|
|
|
3659
3986
|
}
|
|
3660
3987
|
}
|
|
3661
3988
|
);
|
|
3989
|
+
const relationInputSchema = z4.object({
|
|
3990
|
+
type: z4.string().describe("blockedBy (this waits on issue) or blocks (issue waits on this)."),
|
|
3991
|
+
issue: z4.string().describe("Related task id or reference (CRM-232).")
|
|
3992
|
+
});
|
|
3662
3993
|
server.tool(
|
|
3663
3994
|
"abletime_update_task",
|
|
3664
|
-
|
|
3995
|
+
'Update an AbleTime task (id or CRM-232). Pass title, description, state, labels, project, parent, and/or relations. labels replaces tags. project is name or id (tasks stay in a project). parent is CRM-232 or "none". relations sets the wait-on dependency (blockedBy/blocks); removeRelations clears it. When reviewing a PR or ticket, wait until they type a next step before changing the task \u2014 do not ask_user after the review. The author is notified.',
|
|
3665
3996
|
{
|
|
3666
3997
|
id: z4.string(),
|
|
3667
3998
|
title: z4.string().optional(),
|
|
3668
3999
|
description: z4.string().optional(),
|
|
3669
|
-
state: z4.string().optional()
|
|
4000
|
+
state: z4.string().optional(),
|
|
4001
|
+
labels: z4.array(z4.string()).optional().describe("Replace tags/labels. Empty array clears when the host accepts it."),
|
|
4002
|
+
project: z4.string().nullable().optional().describe("Project name or id. AbleTime tasks stay in a project."),
|
|
4003
|
+
parent: z4.string().nullable().optional().describe('Parent task id or reference (CRM-232), or "none" to unset.'),
|
|
4004
|
+
relations: z4.array(relationInputSchema).optional().describe("Set a wait-on dependency: blockedBy or blocks."),
|
|
4005
|
+
removeRelations: z4.array(relationInputSchema).optional().describe("Clear a wait-on dependency.")
|
|
3670
4006
|
},
|
|
3671
4007
|
async (args) => {
|
|
3672
4008
|
try {
|
|
@@ -3800,14 +4136,23 @@ function registerGithubIssueTools(server) {
|
|
|
3800
4136
|
}
|
|
3801
4137
|
}
|
|
3802
4138
|
);
|
|
4139
|
+
const relationInputSchema = z5.object({
|
|
4140
|
+
type: z5.string().describe("blocks or blockedBy (this issue's view)."),
|
|
4141
|
+
issue: z5.string().describe("Related issue #123 or URL.")
|
|
4142
|
+
});
|
|
3803
4143
|
server.tool(
|
|
3804
4144
|
"github_update_issue",
|
|
3805
|
-
|
|
4145
|
+
'Update a GitHub issue (#123). Pass title, body, state (open|closed), labels, project, parent, and/or relations. labels replaces all names (pass [] to clear). project is a Project title, or "none" to remove. parent is #123 or "none". relations adds blockedBy/blocks; removeRelations drops them (`gh` 2.94+). When reviewing a PR or ticket, wait until they type a next step before changing the issue \u2014 do not ask_user after the review. The author is notified.',
|
|
3806
4146
|
{
|
|
3807
4147
|
id: z5.string(),
|
|
3808
4148
|
title: z5.string().optional(),
|
|
3809
4149
|
body: z5.string().optional(),
|
|
3810
4150
|
state: z5.string().optional().describe("open or closed"),
|
|
4151
|
+
labels: z5.array(z5.string()).optional().describe("Replace all labels. Empty array clears. Call github_get_issue first to keep existing."),
|
|
4152
|
+
project: z5.string().nullable().optional().describe('GitHub Project title, or "none" to remove current projects.'),
|
|
4153
|
+
parent: z5.string().nullable().optional().describe('Parent issue #123 or URL, or "none" to unset.'),
|
|
4154
|
+
relations: z5.array(relationInputSchema).optional().describe("Add blockedBy or blocks relations (issue #123 or URL)."),
|
|
4155
|
+
removeRelations: z5.array(relationInputSchema).optional().describe("Remove blockedBy or blocks relations."),
|
|
3811
4156
|
repoPath: repoPathSchema
|
|
3812
4157
|
},
|
|
3813
4158
|
async (args) => {
|
|
@@ -3937,9 +4282,13 @@ function registerLinearTools(server) {
|
|
|
3937
4282
|
}
|
|
3938
4283
|
}
|
|
3939
4284
|
);
|
|
4285
|
+
const relationInputSchema = z6.object({
|
|
4286
|
+
type: z6.string().describe("blocks, blockedBy, related, duplicate, or duplicateOf (this issue's view)."),
|
|
4287
|
+
issue: z6.string().describe("Related issue uuid or identifier (ENG-123).")
|
|
4288
|
+
});
|
|
3940
4289
|
server.tool(
|
|
3941
4290
|
"linear_update_issue",
|
|
3942
|
-
'Update a Linear issue (uuid or ENG-123). Pass title, description, state, assignee, priority, and/or
|
|
4291
|
+
'Update a Linear issue (uuid or ENG-123). Pass title, description, state, assignee, priority, cycle, labels, project, parent, and/or relations. Cycle/project/parent: name or id, or "none" to clear. labels replaces all names (pass [] to clear; get the issue first to keep existing). relations adds; removeRelations removes by type + issue. When reviewing a PR or ticket, wait until they type a next step before changing the ticket \u2014 do not ask_user after the review. The author is notified.',
|
|
3943
4292
|
{
|
|
3944
4293
|
id: z6.string(),
|
|
3945
4294
|
title: z6.string().optional(),
|
|
@@ -3947,7 +4296,12 @@ function registerLinearTools(server) {
|
|
|
3947
4296
|
state: z6.string().optional(),
|
|
3948
4297
|
assignee: z6.string().nullable().optional(),
|
|
3949
4298
|
priority: prioritySchema,
|
|
3950
|
-
cycle: z6.string().nullable().optional().describe('Cycle name, number, "current"/"active", or "none" to unschedule.')
|
|
4299
|
+
cycle: z6.string().nullable().optional().describe('Cycle name, number, "current"/"active", or "none" to unschedule.'),
|
|
4300
|
+
labels: z6.array(z6.string()).optional().describe("Replace all labels (names or ids). Empty array clears. Call linear_get_issue first to keep existing."),
|
|
4301
|
+
project: z6.string().nullable().optional().describe('Project name, id, slug, or "none" to unassign.'),
|
|
4302
|
+
parent: z6.string().nullable().optional().describe('Parent issue uuid or identifier (ENG-123), or "none" to unset.'),
|
|
4303
|
+
relations: z6.array(relationInputSchema).optional().describe("Add relations from this issue's view (blocks, blockedBy, related, duplicate, duplicateOf)."),
|
|
4304
|
+
removeRelations: z6.array(relationInputSchema).optional().describe("Remove relations by type + related issue (uuid or ENG-123).")
|
|
3951
4305
|
},
|
|
3952
4306
|
async (args) => {
|
|
3953
4307
|
try {
|
|
@@ -7985,6 +8339,8 @@ async function startSlackRelayServer(opts) {
|
|
|
7985
8339
|
}
|
|
7986
8340
|
export {
|
|
7987
8341
|
ABLETIME_MCP_PATH,
|
|
8342
|
+
ABLETIME_MCP_PM_PATH,
|
|
8343
|
+
ABLETIME_PM_TOOL_NAMES,
|
|
7988
8344
|
AGENT_GIT_ACTIONS,
|
|
7989
8345
|
AGENT_RUNNER_MAX_OLD_SPACE_MB,
|
|
7990
8346
|
ATTACHMENTS_DIR,
|
|
@@ -8074,8 +8430,10 @@ export {
|
|
|
8074
8430
|
WORKTREE_GITHUB_MCP_TOOLS,
|
|
8075
8431
|
WORKTREE_LINEAR_MCP_TOOLS,
|
|
8076
8432
|
WORKTREE_MCP_TOOLS,
|
|
8433
|
+
ableTimeDependencyTarget,
|
|
8077
8434
|
abletimeMcpRequest,
|
|
8078
8435
|
abletimeMcpUrl,
|
|
8436
|
+
abletimeToolUsesPm,
|
|
8079
8437
|
ackSlackInboundSeen,
|
|
8080
8438
|
addBoardPin,
|
|
8081
8439
|
addPrStackLayer,
|
|
@@ -8348,6 +8706,7 @@ export {
|
|
|
8348
8706
|
git,
|
|
8349
8707
|
githubAgentGitEnv,
|
|
8350
8708
|
githubHttpsInsteadOfEntries,
|
|
8709
|
+
githubRelationEditFlag,
|
|
8351
8710
|
globalAgentCwd,
|
|
8352
8711
|
groupHomeBoardWorktrees,
|
|
8353
8712
|
handleSlackInbound,
|
|
@@ -8397,6 +8756,7 @@ export {
|
|
|
8397
8756
|
isInternalAgentStatusText,
|
|
8398
8757
|
isIssueSourceConnected,
|
|
8399
8758
|
isLinearConnected,
|
|
8759
|
+
isLinearNoneToken,
|
|
8400
8760
|
isLinearOAuthCancelled,
|
|
8401
8761
|
isOptionalServiceId,
|
|
8402
8762
|
isOrchestratorCapableAgent,
|
|
@@ -8434,6 +8794,7 @@ export {
|
|
|
8434
8794
|
linearGraphql,
|
|
8435
8795
|
linearOAuthAuthorizeUrl,
|
|
8436
8796
|
linearOAuthCredentials,
|
|
8797
|
+
linearRelationCreateInput,
|
|
8437
8798
|
linearTicketIdFromThread,
|
|
8438
8799
|
listAbleTimeAssignedIssues,
|
|
8439
8800
|
listAbleTimeProjects,
|
|
@@ -8502,6 +8863,7 @@ export {
|
|
|
8502
8863
|
nonInteractiveGitProcessEnv,
|
|
8503
8864
|
normalizeAbleTimeHost,
|
|
8504
8865
|
normalizeCodeSelection,
|
|
8866
|
+
normalizeLinearRelationType,
|
|
8505
8867
|
normalizeParseResult,
|
|
8506
8868
|
normalizeServiceOrigin,
|
|
8507
8869
|
normalizeThinkingEffort,
|
|
@@ -8595,6 +8957,8 @@ export {
|
|
|
8595
8957
|
resolveGithubAgentToken,
|
|
8596
8958
|
resolveGithubRepoSlug,
|
|
8597
8959
|
resolveLinearCycle,
|
|
8960
|
+
resolveLinearLabel,
|
|
8961
|
+
resolveLinearProject,
|
|
8598
8962
|
resolveLinearState,
|
|
8599
8963
|
resolveLinearTeam,
|
|
8600
8964
|
resolveLoginCommand,
|