@sideboard-ai/core 0.1.190 → 0.1.191
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{abletime-J3EGHIAV.js → abletime-53T7G667.js} +3 -1
- package/dist/{abletime-CAFQ4GEG.js → abletime-JFMBCSIQ.js} +3 -1
- package/dist/{chunk-TEJU6UJY.js → chunk-4M2AK3O5.js} +126 -20
- package/dist/{chunk-Z6TW3A5P.js → chunk-5RRAKGPK.js} +6 -6
- package/dist/{chunk-IAOXW6RI.js → chunk-GPSIZGEZ.js} +6 -6
- package/dist/{chunk-KEVY4GEK.js → chunk-UBQIELFS.js} +123 -20
- package/dist/index.cjs +507 -46
- package/dist/index.d.cts +88 -3
- package/dist/index.d.ts +88 -3
- package/dist/index.js +376 -24
- package/dist/mcp/run-stdio.cjs +487 -46
- package/dist/mcp/run-stdio.js +362 -24
- package/dist/{orchestrator-BHJELEFM.js → orchestrator-2X6LYBQI.js} +1 -1
- package/dist/{orchestrator-HWNGCDGW.js → orchestrator-F3CXDBZB.js} +1 -1
- package/package.json +1 -1
package/dist/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,
|
|
@@ -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,
|
|
@@ -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
|
}
|
|
@@ -1569,6 +1610,148 @@ function resolveLinearCycle(team, cycle) {
|
|
|
1569
1610
|
}
|
|
1570
1611
|
return found.id;
|
|
1571
1612
|
}
|
|
1613
|
+
var LINEAR_NONE_RE = /^(none|clear|unschedule|null)$/i;
|
|
1614
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1615
|
+
function isLinearNoneToken(value) {
|
|
1616
|
+
if (value == null) return true;
|
|
1617
|
+
const s = value.trim();
|
|
1618
|
+
return !s || LINEAR_NONE_RE.test(s);
|
|
1619
|
+
}
|
|
1620
|
+
function resolveLinearLabel(labels, name) {
|
|
1621
|
+
const s = name.trim();
|
|
1622
|
+
if (!s) throw new Error("Linear label is empty");
|
|
1623
|
+
const lower = s.toLowerCase();
|
|
1624
|
+
const found = labels.find((x) => x.id === s) || labels.find((x) => x.name.toLowerCase() === lower);
|
|
1625
|
+
if (!found) {
|
|
1626
|
+
const available = labels.map((x) => x.name).filter(Boolean).slice(0, 20).join(", ") || "(none)";
|
|
1627
|
+
throw new Error(`Linear label not found: ${name}. Available: ${available}`);
|
|
1628
|
+
}
|
|
1629
|
+
return found;
|
|
1630
|
+
}
|
|
1631
|
+
function resolveLinearProject(projects, project) {
|
|
1632
|
+
if (isLinearNoneToken(project)) return null;
|
|
1633
|
+
const s = (project ?? "").trim();
|
|
1634
|
+
const lower = s.toLowerCase();
|
|
1635
|
+
const found = projects.find((x) => x.id === s) || projects.find((x) => x.name.toLowerCase() === lower) || projects.find((x) => x.slugId?.toLowerCase() === lower);
|
|
1636
|
+
if (!found) {
|
|
1637
|
+
const available = projects.map((x) => x.name).filter(Boolean).slice(0, 20).join(", ") || "(none)";
|
|
1638
|
+
throw new Error(`Linear project not found: ${project}. Available: ${available}`);
|
|
1639
|
+
}
|
|
1640
|
+
return found.id;
|
|
1641
|
+
}
|
|
1642
|
+
function normalizeLinearRelationType(type) {
|
|
1643
|
+
const s = type.trim().toLowerCase().replace(/[\s_-]+/g, "");
|
|
1644
|
+
if (s === "blocks" || s === "block") return "blocks";
|
|
1645
|
+
if (s === "blockedby" || s === "blocked") return "blockedBy";
|
|
1646
|
+
if (s === "related" || s === "relates" || s === "relatesto") return "related";
|
|
1647
|
+
if (s === "duplicate" || s === "dup") return "duplicate";
|
|
1648
|
+
if (s === "duplicateof" || s === "original") return "duplicateOf";
|
|
1649
|
+
if (s === "similar") return "similar";
|
|
1650
|
+
throw new Error(
|
|
1651
|
+
`Linear relation type must be blocks, blockedBy, related, duplicate, or duplicateOf (got ${type})`
|
|
1652
|
+
);
|
|
1653
|
+
}
|
|
1654
|
+
function linearRelationCreateInput(thisIssueId, type, relatedIssueId) {
|
|
1655
|
+
const view = normalizeLinearRelationType(type);
|
|
1656
|
+
if (view === "blockedBy") {
|
|
1657
|
+
return { issueId: relatedIssueId, relatedIssueId: thisIssueId, type: "blocks" };
|
|
1658
|
+
}
|
|
1659
|
+
if (view === "duplicateOf") {
|
|
1660
|
+
return { issueId: relatedIssueId, relatedIssueId: thisIssueId, type: "duplicate" };
|
|
1661
|
+
}
|
|
1662
|
+
return { issueId: thisIssueId, relatedIssueId, type: view };
|
|
1663
|
+
}
|
|
1664
|
+
function relationMatches(rel, type, issueRef) {
|
|
1665
|
+
if (rel.type.toLowerCase() !== type.toLowerCase()) return false;
|
|
1666
|
+
const ref = issueRef.trim().toLowerCase();
|
|
1667
|
+
return rel.issue.id.toLowerCase() === ref || rel.issue.identifier.toLowerCase() === ref;
|
|
1668
|
+
}
|
|
1669
|
+
function formatRelationList(relations) {
|
|
1670
|
+
if (!relations.length) return "(none)";
|
|
1671
|
+
return relations.map((r) => `${r.type} ${r.issue.identifier || r.issue.id}`).join(", ");
|
|
1672
|
+
}
|
|
1673
|
+
async function listLinearLabels(opts) {
|
|
1674
|
+
const json = await linearGraphql(LABELS_QUERY, { first: 250 }, opts);
|
|
1675
|
+
return (json.issueLabels?.nodes ?? []).flatMap((node) => {
|
|
1676
|
+
const id = String(node.id ?? "").trim();
|
|
1677
|
+
const name = String(node.name ?? "").trim();
|
|
1678
|
+
if (!id) return [];
|
|
1679
|
+
return [{ id, name }];
|
|
1680
|
+
});
|
|
1681
|
+
}
|
|
1682
|
+
async function listLinearProjects(filter, opts) {
|
|
1683
|
+
const json = await linearGraphql(PROJECTS_QUERY, { first: 50, filter: filter ?? {} }, opts);
|
|
1684
|
+
return (json.projects?.nodes ?? []).flatMap((node) => {
|
|
1685
|
+
const id = String(node.id ?? "").trim();
|
|
1686
|
+
if (!id) return [];
|
|
1687
|
+
return [
|
|
1688
|
+
{
|
|
1689
|
+
id,
|
|
1690
|
+
name: String(node.name ?? ""),
|
|
1691
|
+
...node.slugId ? { slugId: String(node.slugId) } : {}
|
|
1692
|
+
}
|
|
1693
|
+
];
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
async function resolveLinearProjectId(project, opts) {
|
|
1697
|
+
if (isLinearNoneToken(project)) return null;
|
|
1698
|
+
const s = (project ?? "").trim();
|
|
1699
|
+
const filter = UUID_RE.test(s) ? { id: { eq: s } } : { name: { eqIgnoreCase: s } };
|
|
1700
|
+
const matched = await listLinearProjects(filter, opts);
|
|
1701
|
+
try {
|
|
1702
|
+
return resolveLinearProject(matched, s);
|
|
1703
|
+
} catch {
|
|
1704
|
+
if (UUID_RE.test(s)) return s;
|
|
1705
|
+
const listed = await listLinearProjects(void 0, opts);
|
|
1706
|
+
return resolveLinearProject(listed, s);
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
async function resolveLinearLabelIds(names, opts) {
|
|
1710
|
+
if (names.length === 0) return [];
|
|
1711
|
+
const labels = await listLinearLabels(opts);
|
|
1712
|
+
return names.map((name) => {
|
|
1713
|
+
const s = name.trim();
|
|
1714
|
+
try {
|
|
1715
|
+
return resolveLinearLabel(labels, s).id;
|
|
1716
|
+
} catch (error) {
|
|
1717
|
+
if (UUID_RE.test(s)) return s;
|
|
1718
|
+
throw error;
|
|
1719
|
+
}
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
async function applyLinearIssueRelations(issue, add, remove, opts) {
|
|
1723
|
+
const current = [...issue.relations];
|
|
1724
|
+
for (const rel of remove ?? []) {
|
|
1725
|
+
const type = normalizeLinearRelationType(rel.type);
|
|
1726
|
+
const issueRef = rel.issue.trim();
|
|
1727
|
+
if (!issueRef) throw new Error("Linear removeRelations.issue is required (uuid or ENG-123)");
|
|
1728
|
+
const match = current.find((r) => relationMatches(r, type, issueRef));
|
|
1729
|
+
if (!match?.id) {
|
|
1730
|
+
throw new Error(
|
|
1731
|
+
`Linear relation not found: ${type} ${issueRef}. Existing: ${formatRelationList(current)}`
|
|
1732
|
+
);
|
|
1733
|
+
}
|
|
1734
|
+
const json = await linearGraphql(ISSUE_RELATION_DELETE, { id: match.id }, opts);
|
|
1735
|
+
if (!json.issueRelationDelete?.success) {
|
|
1736
|
+
throw new Error(`Linear issueRelationDelete failed (${type} ${issueRef})`);
|
|
1737
|
+
}
|
|
1738
|
+
const idx = current.indexOf(match);
|
|
1739
|
+
if (idx >= 0) current.splice(idx, 1);
|
|
1740
|
+
}
|
|
1741
|
+
for (const rel of add ?? []) {
|
|
1742
|
+
const type = normalizeLinearRelationType(rel.type);
|
|
1743
|
+
const issueRef = rel.issue.trim();
|
|
1744
|
+
if (!issueRef) throw new Error("Linear relations.issue is required (uuid or ENG-123)");
|
|
1745
|
+
if (current.some((r) => relationMatches(r, type, issueRef))) continue;
|
|
1746
|
+
const related = await getLinearIssue(issueRef, opts);
|
|
1747
|
+
const input = linearRelationCreateInput(issue.id, type, related.id);
|
|
1748
|
+
const json = await linearGraphql(ISSUE_RELATION_CREATE, { input }, opts);
|
|
1749
|
+
if (!json.issueRelationCreate?.success) {
|
|
1750
|
+
throw new Error(`Linear issueRelationCreate failed (${type} ${related.identifier})`);
|
|
1751
|
+
}
|
|
1752
|
+
current.push({ type, issue: related, id: void 0 });
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1572
1755
|
function normalizePriority(priority) {
|
|
1573
1756
|
if (priority == null) return void 0;
|
|
1574
1757
|
if (!Number.isInteger(priority) || priority < 0 || priority > 4) {
|
|
@@ -1576,7 +1759,6 @@ function normalizePriority(priority) {
|
|
|
1576
1759
|
}
|
|
1577
1760
|
return priority;
|
|
1578
1761
|
}
|
|
1579
|
-
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1580
1762
|
function assigneeFilterKey(assignee) {
|
|
1581
1763
|
return (assignee ?? "").trim();
|
|
1582
1764
|
}
|
|
@@ -1789,8 +1971,10 @@ async function updateLinearIssue(input, opts) {
|
|
|
1789
1971
|
if (input.description !== void 0) {
|
|
1790
1972
|
mutationInput.description = input.description;
|
|
1791
1973
|
}
|
|
1792
|
-
const
|
|
1793
|
-
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;
|
|
1794
1978
|
if (input.state?.trim()) {
|
|
1795
1979
|
const { teams } = await listLinearTeams(opts);
|
|
1796
1980
|
const teamRef = existing?.team?.key || existing?.team?.id;
|
|
@@ -1823,14 +2007,44 @@ async function updateLinearIssue(input, opts) {
|
|
|
1823
2007
|
if (input.priority !== void 0) {
|
|
1824
2008
|
mutationInput.priority = normalizePriority(input.priority);
|
|
1825
2009
|
}
|
|
1826
|
-
if (
|
|
1827
|
-
|
|
2010
|
+
if (input.labels !== void 0) {
|
|
2011
|
+
mutationInput.labelIds = await resolveLinearLabelIds(input.labels, opts);
|
|
2012
|
+
}
|
|
2013
|
+
if (input.project !== void 0) {
|
|
2014
|
+
mutationInput.projectId = await resolveLinearProjectId(input.project, opts);
|
|
1828
2015
|
}
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
2016
|
+
if (input.parent !== void 0) {
|
|
2017
|
+
if (isLinearNoneToken(input.parent)) {
|
|
2018
|
+
mutationInput.parentId = null;
|
|
2019
|
+
} else {
|
|
2020
|
+
const parent = await getLinearIssue((input.parent ?? "").trim(), opts);
|
|
2021
|
+
mutationInput.parentId = parent.id;
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
const hasIssueFields = Object.keys(mutationInput).length > 0;
|
|
2025
|
+
const hasRelationWrites = Boolean(addRelations || removeRelations);
|
|
2026
|
+
if (!hasIssueFields && !hasRelationWrites) {
|
|
2027
|
+
throw new Error(
|
|
2028
|
+
"linear_update_issue needs at least one of title, description, state, assignee, priority, cycle, labels, project, parent, relations, removeRelations"
|
|
2029
|
+
);
|
|
2030
|
+
}
|
|
2031
|
+
let updated;
|
|
2032
|
+
if (hasIssueFields) {
|
|
2033
|
+
const json = await linearGraphql(ISSUE_UPDATE, { id: issueId, input: mutationInput }, opts);
|
|
2034
|
+
if (!json.issueUpdate?.success || !json.issueUpdate.issue) {
|
|
2035
|
+
throw new Error("Linear issueUpdate failed");
|
|
2036
|
+
}
|
|
2037
|
+
updated = mapIssue(json.issueUpdate.issue);
|
|
1832
2038
|
}
|
|
1833
|
-
|
|
2039
|
+
if (hasRelationWrites) {
|
|
2040
|
+
const base = updated ?? existing;
|
|
2041
|
+
if (!base) {
|
|
2042
|
+
throw new Error(`Linear issue not found: ${issueId}`);
|
|
2043
|
+
}
|
|
2044
|
+
await applyLinearIssueRelations(base, addRelations, removeRelations, opts);
|
|
2045
|
+
return getLinearIssue(base.id || issueId, opts);
|
|
2046
|
+
}
|
|
2047
|
+
return updated;
|
|
1834
2048
|
}
|
|
1835
2049
|
async function commentLinearIssue(input, opts) {
|
|
1836
2050
|
const issueId = input.id.trim();
|
|
@@ -2050,13 +2264,78 @@ async function commentGitHubIssue(input, opts) {
|
|
|
2050
2264
|
const url = stdout.trim().split(/\s+/).find((part) => /^https?:\/\//i.test(part));
|
|
2051
2265
|
return { body, url };
|
|
2052
2266
|
}
|
|
2267
|
+
function isGitHubNoneToken(value) {
|
|
2268
|
+
if (value == null) return true;
|
|
2269
|
+
const s = value.trim();
|
|
2270
|
+
return !s || /^(none|clear|unschedule|null)$/i.test(s);
|
|
2271
|
+
}
|
|
2272
|
+
function githubRelationEditFlag(type, remove = false) {
|
|
2273
|
+
const s = type.trim().toLowerCase().replace(/[\s_-]+/g, "");
|
|
2274
|
+
if (s === "blockedby" || s === "blocked" || s === "dependson") {
|
|
2275
|
+
return remove ? "--remove-blocked-by" : "--add-blocked-by";
|
|
2276
|
+
}
|
|
2277
|
+
if (s === "blocks" || s === "blocking") {
|
|
2278
|
+
return remove ? "--remove-blocking" : "--add-blocking";
|
|
2279
|
+
}
|
|
2280
|
+
throw new Error(
|
|
2281
|
+
`GitHub relation type must be blocks or blockedBy (got ${type}). Parent is a separate parent= field.`
|
|
2282
|
+
);
|
|
2283
|
+
}
|
|
2284
|
+
function labelsEqual(a, b) {
|
|
2285
|
+
return a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
2286
|
+
}
|
|
2287
|
+
function parseProjectTitles(raw) {
|
|
2288
|
+
const out = [];
|
|
2289
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2290
|
+
const push = (name) => {
|
|
2291
|
+
const title = name.trim();
|
|
2292
|
+
const key = title.toLowerCase();
|
|
2293
|
+
if (!title || seen.has(key)) return;
|
|
2294
|
+
seen.add(key);
|
|
2295
|
+
out.push(title);
|
|
2296
|
+
};
|
|
2297
|
+
const fromItems = raw.projectItems ?? raw.projects ?? raw.projectItemsV2;
|
|
2298
|
+
if (Array.isArray(fromItems)) {
|
|
2299
|
+
for (const item of fromItems) {
|
|
2300
|
+
if (typeof item === "string") {
|
|
2301
|
+
push(item);
|
|
2302
|
+
continue;
|
|
2303
|
+
}
|
|
2304
|
+
if (!item || typeof item !== "object") continue;
|
|
2305
|
+
const rec = item;
|
|
2306
|
+
const nested = rec.project && typeof rec.project === "object" ? rec.project : rec;
|
|
2307
|
+
const title = String(nested.title ?? nested.name ?? rec.title ?? rec.name ?? "").trim();
|
|
2308
|
+
if (title) push(title);
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
return out;
|
|
2312
|
+
}
|
|
2313
|
+
async function listGitHubIssueProjects(number, cwd, repoArgs) {
|
|
2314
|
+
const result = await gh(
|
|
2315
|
+
["issue", "view", String(number), ...repoArgs, "--json", "projectItems,projects"],
|
|
2316
|
+
cwd,
|
|
2317
|
+
{ reject: false }
|
|
2318
|
+
);
|
|
2319
|
+
if (result.exitCode !== 0 || !result.stdout.trim()) return [];
|
|
2320
|
+
try {
|
|
2321
|
+
const parsed = JSON.parse(result.stdout);
|
|
2322
|
+
return parseProjectTitles(parsed);
|
|
2323
|
+
} catch {
|
|
2324
|
+
return [];
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2053
2327
|
async function updateGitHubIssue(input, opts) {
|
|
2054
2328
|
const number = parseGitHubIssueNumber(input.id);
|
|
2055
2329
|
const title = input.title?.trim();
|
|
2056
2330
|
const body = input.body;
|
|
2057
2331
|
const state = input.state?.trim().toLowerCase();
|
|
2058
|
-
|
|
2059
|
-
|
|
2332
|
+
const addRelations = input.relations?.length ? input.relations : void 0;
|
|
2333
|
+
const removeRelations = input.removeRelations?.length ? input.removeRelations : void 0;
|
|
2334
|
+
const hasEditFields = Boolean(title) || body !== void 0 || input.labels !== void 0 || input.project !== void 0 || input.parent !== void 0 || Boolean(addRelations) || Boolean(removeRelations);
|
|
2335
|
+
if (!hasEditFields && !state) {
|
|
2336
|
+
throw new Error(
|
|
2337
|
+
"github_update_issue needs at least one of title, body, state, labels, project, parent, relations, removeRelations"
|
|
2338
|
+
);
|
|
2060
2339
|
}
|
|
2061
2340
|
const { cwd, repoArgs } = await resolveGitHubIssueRepo(opts?.repoPath);
|
|
2062
2341
|
if (state === "closed" || state === "close") {
|
|
@@ -2072,10 +2351,46 @@ async function updateGitHubIssue(input, opts) {
|
|
|
2072
2351
|
} else if (state) {
|
|
2073
2352
|
throw new Error(`GitHub issue state must be open or closed (got ${input.state})`);
|
|
2074
2353
|
}
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2354
|
+
const args = ["issue", "edit", String(number), ...repoArgs];
|
|
2355
|
+
if (title) args.push("--title", title);
|
|
2356
|
+
if (body !== void 0) args.push("--body", body);
|
|
2357
|
+
if (input.labels !== void 0) {
|
|
2358
|
+
const existing = await getGitHubIssue(String(number), opts);
|
|
2359
|
+
const desired = input.labels.map((name) => name.trim()).filter(Boolean);
|
|
2360
|
+
const add = desired.filter((name) => !existing.labels.some((cur) => labelsEqual(cur, name)));
|
|
2361
|
+
const remove = existing.labels.filter((cur) => !desired.some((name) => labelsEqual(cur, name)));
|
|
2362
|
+
if (add.length) args.push("--add-label", add.join(","));
|
|
2363
|
+
if (remove.length) args.push("--remove-label", remove.join(","));
|
|
2364
|
+
}
|
|
2365
|
+
if (input.project !== void 0) {
|
|
2366
|
+
const current = await listGitHubIssueProjects(number, cwd, repoArgs);
|
|
2367
|
+
if (isGitHubNoneToken(input.project)) {
|
|
2368
|
+
for (const name of current) args.push("--remove-project", name);
|
|
2369
|
+
} else {
|
|
2370
|
+
const wanted = (input.project ?? "").trim();
|
|
2371
|
+
if (!current.some((name) => labelsEqual(name, wanted))) {
|
|
2372
|
+
args.push("--add-project", wanted);
|
|
2373
|
+
}
|
|
2374
|
+
for (const name of current) {
|
|
2375
|
+
if (!labelsEqual(name, wanted)) args.push("--remove-project", name);
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
if (input.parent !== void 0) {
|
|
2380
|
+
if (isGitHubNoneToken(input.parent)) {
|
|
2381
|
+
args.push("--remove-parent");
|
|
2382
|
+
} else {
|
|
2383
|
+
args.push("--parent", String(parseGitHubIssueNumber(input.parent ?? "")));
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
for (const rel of removeRelations ?? []) {
|
|
2387
|
+
args.push(githubRelationEditFlag(rel.type, true), String(parseGitHubIssueNumber(rel.issue)));
|
|
2388
|
+
}
|
|
2389
|
+
for (const rel of addRelations ?? []) {
|
|
2390
|
+
args.push(githubRelationEditFlag(rel.type, false), String(parseGitHubIssueNumber(rel.issue)));
|
|
2391
|
+
}
|
|
2392
|
+
const editChanged = args.length > 3 + repoArgs.length;
|
|
2393
|
+
if (editChanged) {
|
|
2079
2394
|
requireGhOk(await gh(args, cwd, { reject: false }), `GitHub edit #${number}`);
|
|
2080
2395
|
}
|
|
2081
2396
|
return getGitHubIssue(String(number), opts);
|
|
@@ -3671,14 +3986,23 @@ function registerAbleTimeTools(server) {
|
|
|
3671
3986
|
}
|
|
3672
3987
|
}
|
|
3673
3988
|
);
|
|
3989
|
+
const relationInputSchema = z4.object({
|
|
3990
|
+
type: z4.string().describe("blockedBy (this waits on issue) or blocks (issue waits on this)."),
|
|
3991
|
+
issue: z4.string().describe("Related task id or reference (CRM-232).")
|
|
3992
|
+
});
|
|
3674
3993
|
server.tool(
|
|
3675
3994
|
"abletime_update_task",
|
|
3676
|
-
|
|
3995
|
+
'Update an AbleTime task (id or CRM-232). Pass title, description, state, labels, project, parent, and/or relations. labels replaces tags. project is name or id (tasks stay in a project). parent is CRM-232 or "none". relations sets the wait-on dependency (blockedBy/blocks); removeRelations clears it. When reviewing a PR or ticket, wait until they type a next step before changing the task \u2014 do not ask_user after the review. The author is notified.',
|
|
3677
3996
|
{
|
|
3678
3997
|
id: z4.string(),
|
|
3679
3998
|
title: z4.string().optional(),
|
|
3680
3999
|
description: z4.string().optional(),
|
|
3681
|
-
state: z4.string().optional()
|
|
4000
|
+
state: z4.string().optional(),
|
|
4001
|
+
labels: z4.array(z4.string()).optional().describe("Replace tags/labels. Empty array clears when the host accepts it."),
|
|
4002
|
+
project: z4.string().nullable().optional().describe("Project name or id. AbleTime tasks stay in a project."),
|
|
4003
|
+
parent: z4.string().nullable().optional().describe('Parent task id or reference (CRM-232), or "none" to unset.'),
|
|
4004
|
+
relations: z4.array(relationInputSchema).optional().describe("Set a wait-on dependency: blockedBy or blocks."),
|
|
4005
|
+
removeRelations: z4.array(relationInputSchema).optional().describe("Clear a wait-on dependency.")
|
|
3682
4006
|
},
|
|
3683
4007
|
async (args) => {
|
|
3684
4008
|
try {
|
|
@@ -3812,14 +4136,23 @@ function registerGithubIssueTools(server) {
|
|
|
3812
4136
|
}
|
|
3813
4137
|
}
|
|
3814
4138
|
);
|
|
4139
|
+
const relationInputSchema = z5.object({
|
|
4140
|
+
type: z5.string().describe("blocks or blockedBy (this issue's view)."),
|
|
4141
|
+
issue: z5.string().describe("Related issue #123 or URL.")
|
|
4142
|
+
});
|
|
3815
4143
|
server.tool(
|
|
3816
4144
|
"github_update_issue",
|
|
3817
|
-
|
|
4145
|
+
'Update a GitHub issue (#123). Pass title, body, state (open|closed), labels, project, parent, and/or relations. labels replaces all names (pass [] to clear). project is a Project title, or "none" to remove. parent is #123 or "none". relations adds blockedBy/blocks; removeRelations drops them (`gh` 2.94+). When reviewing a PR or ticket, wait until they type a next step before changing the issue \u2014 do not ask_user after the review. The author is notified.',
|
|
3818
4146
|
{
|
|
3819
4147
|
id: z5.string(),
|
|
3820
4148
|
title: z5.string().optional(),
|
|
3821
4149
|
body: z5.string().optional(),
|
|
3822
4150
|
state: z5.string().optional().describe("open or closed"),
|
|
4151
|
+
labels: z5.array(z5.string()).optional().describe("Replace all labels. Empty array clears. Call github_get_issue first to keep existing."),
|
|
4152
|
+
project: z5.string().nullable().optional().describe('GitHub Project title, or "none" to remove current projects.'),
|
|
4153
|
+
parent: z5.string().nullable().optional().describe('Parent issue #123 or URL, or "none" to unset.'),
|
|
4154
|
+
relations: z5.array(relationInputSchema).optional().describe("Add blockedBy or blocks relations (issue #123 or URL)."),
|
|
4155
|
+
removeRelations: z5.array(relationInputSchema).optional().describe("Remove blockedBy or blocks relations."),
|
|
3823
4156
|
repoPath: repoPathSchema
|
|
3824
4157
|
},
|
|
3825
4158
|
async (args) => {
|
|
@@ -3949,9 +4282,13 @@ function registerLinearTools(server) {
|
|
|
3949
4282
|
}
|
|
3950
4283
|
}
|
|
3951
4284
|
);
|
|
4285
|
+
const relationInputSchema = z6.object({
|
|
4286
|
+
type: z6.string().describe("blocks, blockedBy, related, duplicate, or duplicateOf (this issue's view)."),
|
|
4287
|
+
issue: z6.string().describe("Related issue uuid or identifier (ENG-123).")
|
|
4288
|
+
});
|
|
3952
4289
|
server.tool(
|
|
3953
4290
|
"linear_update_issue",
|
|
3954
|
-
'Update a Linear issue (uuid or ENG-123). Pass title, description, state, assignee, priority, and/or
|
|
4291
|
+
'Update a Linear issue (uuid or ENG-123). Pass title, description, state, assignee, priority, cycle, labels, project, parent, and/or relations. Cycle/project/parent: name or id, or "none" to clear. labels replaces all names (pass [] to clear; get the issue first to keep existing). relations adds; removeRelations removes by type + issue. When reviewing a PR or ticket, wait until they type a next step before changing the ticket \u2014 do not ask_user after the review. The author is notified.',
|
|
3955
4292
|
{
|
|
3956
4293
|
id: z6.string(),
|
|
3957
4294
|
title: z6.string().optional(),
|
|
@@ -3959,7 +4296,12 @@ function registerLinearTools(server) {
|
|
|
3959
4296
|
state: z6.string().optional(),
|
|
3960
4297
|
assignee: z6.string().nullable().optional(),
|
|
3961
4298
|
priority: prioritySchema,
|
|
3962
|
-
cycle: z6.string().nullable().optional().describe('Cycle name, number, "current"/"active", or "none" to unschedule.')
|
|
4299
|
+
cycle: z6.string().nullable().optional().describe('Cycle name, number, "current"/"active", or "none" to unschedule.'),
|
|
4300
|
+
labels: z6.array(z6.string()).optional().describe("Replace all labels (names or ids). Empty array clears. Call linear_get_issue first to keep existing."),
|
|
4301
|
+
project: z6.string().nullable().optional().describe('Project name, id, slug, or "none" to unassign.'),
|
|
4302
|
+
parent: z6.string().nullable().optional().describe('Parent issue uuid or identifier (ENG-123), or "none" to unset.'),
|
|
4303
|
+
relations: z6.array(relationInputSchema).optional().describe("Add relations from this issue's view (blocks, blockedBy, related, duplicate, duplicateOf)."),
|
|
4304
|
+
removeRelations: z6.array(relationInputSchema).optional().describe("Remove relations by type + related issue (uuid or ENG-123).")
|
|
3963
4305
|
},
|
|
3964
4306
|
async (args) => {
|
|
3965
4307
|
try {
|
|
@@ -7997,6 +8339,8 @@ async function startSlackRelayServer(opts) {
|
|
|
7997
8339
|
}
|
|
7998
8340
|
export {
|
|
7999
8341
|
ABLETIME_MCP_PATH,
|
|
8342
|
+
ABLETIME_MCP_PM_PATH,
|
|
8343
|
+
ABLETIME_PM_TOOL_NAMES,
|
|
8000
8344
|
AGENT_GIT_ACTIONS,
|
|
8001
8345
|
AGENT_RUNNER_MAX_OLD_SPACE_MB,
|
|
8002
8346
|
ATTACHMENTS_DIR,
|
|
@@ -8086,8 +8430,10 @@ export {
|
|
|
8086
8430
|
WORKTREE_GITHUB_MCP_TOOLS,
|
|
8087
8431
|
WORKTREE_LINEAR_MCP_TOOLS,
|
|
8088
8432
|
WORKTREE_MCP_TOOLS,
|
|
8433
|
+
ableTimeDependencyTarget,
|
|
8089
8434
|
abletimeMcpRequest,
|
|
8090
8435
|
abletimeMcpUrl,
|
|
8436
|
+
abletimeToolUsesPm,
|
|
8091
8437
|
ackSlackInboundSeen,
|
|
8092
8438
|
addBoardPin,
|
|
8093
8439
|
addPrStackLayer,
|
|
@@ -8360,6 +8706,7 @@ export {
|
|
|
8360
8706
|
git,
|
|
8361
8707
|
githubAgentGitEnv,
|
|
8362
8708
|
githubHttpsInsteadOfEntries,
|
|
8709
|
+
githubRelationEditFlag,
|
|
8363
8710
|
globalAgentCwd,
|
|
8364
8711
|
groupHomeBoardWorktrees,
|
|
8365
8712
|
handleSlackInbound,
|
|
@@ -8409,6 +8756,7 @@ export {
|
|
|
8409
8756
|
isInternalAgentStatusText,
|
|
8410
8757
|
isIssueSourceConnected,
|
|
8411
8758
|
isLinearConnected,
|
|
8759
|
+
isLinearNoneToken,
|
|
8412
8760
|
isLinearOAuthCancelled,
|
|
8413
8761
|
isOptionalServiceId,
|
|
8414
8762
|
isOrchestratorCapableAgent,
|
|
@@ -8446,6 +8794,7 @@ export {
|
|
|
8446
8794
|
linearGraphql,
|
|
8447
8795
|
linearOAuthAuthorizeUrl,
|
|
8448
8796
|
linearOAuthCredentials,
|
|
8797
|
+
linearRelationCreateInput,
|
|
8449
8798
|
linearTicketIdFromThread,
|
|
8450
8799
|
listAbleTimeAssignedIssues,
|
|
8451
8800
|
listAbleTimeProjects,
|
|
@@ -8514,6 +8863,7 @@ export {
|
|
|
8514
8863
|
nonInteractiveGitProcessEnv,
|
|
8515
8864
|
normalizeAbleTimeHost,
|
|
8516
8865
|
normalizeCodeSelection,
|
|
8866
|
+
normalizeLinearRelationType,
|
|
8517
8867
|
normalizeParseResult,
|
|
8518
8868
|
normalizeServiceOrigin,
|
|
8519
8869
|
normalizeThinkingEffort,
|
|
@@ -8607,6 +8957,8 @@ export {
|
|
|
8607
8957
|
resolveGithubAgentToken,
|
|
8608
8958
|
resolveGithubRepoSlug,
|
|
8609
8959
|
resolveLinearCycle,
|
|
8960
|
+
resolveLinearLabel,
|
|
8961
|
+
resolveLinearProject,
|
|
8610
8962
|
resolveLinearState,
|
|
8611
8963
|
resolveLinearTeam,
|
|
8612
8964
|
resolveLoginCommand,
|