@craftsamo/github-tools 0.6.0 → 0.6.1
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/.antigravity-plugin/plugin.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +3 -1
- package/dist/cli.js +122 -51
- package/dist/mcp.js +123 -52
- package/dist/opencode.js +122 -51
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/plugin.yaml +1 -1
- package/skills/github-projects/SKILL.md +10 -3
package/README.md
CHANGED
|
@@ -217,7 +217,9 @@ none is given), and found among the boards linked to the repository, then its
|
|
|
217
217
|
organization's, then the viewer's. The token needs the `project` scope
|
|
218
218
|
(`gh auth refresh -s project`). No API sets a roadmap view's date fields, so
|
|
219
219
|
after `github_project_create` set the Roadmap view's "Date fields" to Start and
|
|
220
|
-
Target in the web UI.
|
|
220
|
+
Target in the web UI. A column that mirrors an organization's issue field is
|
|
221
|
+
read like any other, but its value is the issue's own: the board tools refuse
|
|
222
|
+
to set or clear it, and `github_issue_edit` does both.
|
|
221
223
|
|
|
222
224
|
## Reads and writes
|
|
223
225
|
|
package/dist/cli.js
CHANGED
|
@@ -20586,11 +20586,18 @@ function listOf(names) {
|
|
|
20586
20586
|
return names.length > 0 ? names.map((name) => `"${name}"`).join(", ") : "none";
|
|
20587
20587
|
}
|
|
20588
20588
|
function settableNames(fields) {
|
|
20589
|
-
return listOf(fields.filter((field) => isSettable(field.type)).map((field) => field.name));
|
|
20589
|
+
return listOf(fields.filter((field) => isSettable(field.type) && !field.issue_field).map((field) => field.name));
|
|
20590
20590
|
}
|
|
20591
|
-
function settableField(fields, name) {
|
|
20591
|
+
function settableField(fields, name, action = "set") {
|
|
20592
20592
|
const field = fields.find((candidate) => sameTitle(candidate.name, name));
|
|
20593
20593
|
if (!field) throw invalid3(`The board has no field "${name}". Fields that can be set: ${settableNames(fields)}.`);
|
|
20594
|
+
if (field.issue_field) {
|
|
20595
|
+
const verb = action === "clear" ? "cleared" : "set";
|
|
20596
|
+
const fix = action === "clear" ? "Clear it with clear_issue_fields in github_issue_edit." : "Set it with issue_fields in github_issue_edit or github_issue_create.";
|
|
20597
|
+
throw invalid3(
|
|
20598
|
+
`"${field.name}" mirrors the organization's issue field; its value is the issue's own and cannot be ${verb} on a board. ${fix} Fields that can be set: ${settableNames(fields)}.`
|
|
20599
|
+
);
|
|
20600
|
+
}
|
|
20594
20601
|
if (!isSettable(field.type)) {
|
|
20595
20602
|
const hint = ISSUE_FIELD_TYPES.includes(field.type) ? " github_issue_edit sets assignees, labels and milestone on the issue." : "";
|
|
20596
20603
|
throw invalid3(
|
|
@@ -20670,7 +20677,7 @@ function planValue(field, value, now) {
|
|
|
20670
20677
|
function planFieldValues(fields, values, now = /* @__PURE__ */ new Date()) {
|
|
20671
20678
|
const writes = [];
|
|
20672
20679
|
for (const [name, value] of Object.entries(values)) {
|
|
20673
|
-
const field = settableField(fields, name);
|
|
20680
|
+
const field = settableField(fields, name, "set");
|
|
20674
20681
|
if (writes.some((write) => write.field.id === field.id)) {
|
|
20675
20682
|
throw invalid3(`fields names "${field.name}" twice; field names are matched without regard to case.`);
|
|
20676
20683
|
}
|
|
@@ -20681,7 +20688,7 @@ function planFieldValues(fields, values, now = /* @__PURE__ */ new Date()) {
|
|
|
20681
20688
|
function planClears(fields, names) {
|
|
20682
20689
|
const cleared = [];
|
|
20683
20690
|
for (const name of names) {
|
|
20684
|
-
const field = settableField(fields, name);
|
|
20691
|
+
const field = settableField(fields, name, "clear");
|
|
20685
20692
|
if (!cleared.some((each) => each.id === field.id)) cleared.push(field);
|
|
20686
20693
|
}
|
|
20687
20694
|
return cleared;
|
|
@@ -20696,38 +20703,43 @@ function isHeld(write, current) {
|
|
|
20696
20703
|
|
|
20697
20704
|
// src/core/github/issue-fields.ts
|
|
20698
20705
|
var issueFieldValuesSchema = external_exports.record(external_exports.string(), external_exports.union([external_exports.string(), external_exports.number(), external_exports.array(external_exports.string())]));
|
|
20699
|
-
var
|
|
20706
|
+
var ISSUE_FIELD_VALUE_SELECTION = "__typename ... on IssueFieldTextValue{value} ... on IssueFieldNumberValue{value} ... on IssueFieldDateValue{value} ... on IssueFieldSingleSelectValue{name} ... on IssueFieldMultiSelectValue{options{name}}";
|
|
20707
|
+
var ISSUE_FIELD_VALUES_FRAGMENT = `issueFieldValues(first:25){nodes{... on IssueFieldValueCommon{field{... on IssueFieldCommon{name}}} ${ISSUE_FIELD_VALUE_SELECTION}}}`;
|
|
20708
|
+
function issueFieldValueOf(node2) {
|
|
20709
|
+
if (!node2) return void 0;
|
|
20710
|
+
switch (node2.__typename) {
|
|
20711
|
+
case "IssueFieldTextValue":
|
|
20712
|
+
return typeof node2.value === "string" ? node2.value : void 0;
|
|
20713
|
+
case "IssueFieldNumberValue":
|
|
20714
|
+
return typeof node2.value === "number" && !Number.isNaN(node2.value) ? node2.value : void 0;
|
|
20715
|
+
case "IssueFieldDateValue":
|
|
20716
|
+
return typeof node2.value === "string" ? node2.value : void 0;
|
|
20717
|
+
case "IssueFieldSingleSelectValue":
|
|
20718
|
+
return typeof node2.name === "string" ? node2.name : void 0;
|
|
20719
|
+
case "IssueFieldMultiSelectValue": {
|
|
20720
|
+
const options = (node2.options ?? []).map((opt) => opt?.name).filter((name) => typeof name === "string");
|
|
20721
|
+
return options.length > 0 ? options : void 0;
|
|
20722
|
+
}
|
|
20723
|
+
default:
|
|
20724
|
+
return void 0;
|
|
20725
|
+
}
|
|
20726
|
+
}
|
|
20700
20727
|
function issueFieldValues(connection) {
|
|
20701
20728
|
const values = {};
|
|
20702
20729
|
for (const node2 of connection?.nodes ?? []) {
|
|
20703
20730
|
if (!node2) continue;
|
|
20704
20731
|
const fieldName = node2.field?.name?.trim();
|
|
20705
20732
|
if (!fieldName) continue;
|
|
20706
|
-
|
|
20707
|
-
|
|
20708
|
-
if (typeof node2.value === "string") values[fieldName] = node2.value;
|
|
20709
|
-
break;
|
|
20710
|
-
case "IssueFieldNumberValue":
|
|
20711
|
-
if (typeof node2.value === "number" && !Number.isNaN(node2.value)) values[fieldName] = node2.value;
|
|
20712
|
-
break;
|
|
20713
|
-
case "IssueFieldDateValue":
|
|
20714
|
-
if (typeof node2.value === "string") values[fieldName] = node2.value;
|
|
20715
|
-
break;
|
|
20716
|
-
case "IssueFieldSingleSelectValue":
|
|
20717
|
-
if (typeof node2.name === "string") values[fieldName] = node2.name;
|
|
20718
|
-
break;
|
|
20719
|
-
case "IssueFieldMultiSelectValue": {
|
|
20720
|
-
const options = (node2.options ?? []).map((opt) => opt?.name).filter((name) => typeof name === "string");
|
|
20721
|
-
if (options.length > 0) values[fieldName] = options;
|
|
20722
|
-
break;
|
|
20723
|
-
}
|
|
20724
|
-
}
|
|
20733
|
+
const val = issueFieldValueOf(node2);
|
|
20734
|
+
if (val !== void 0) values[fieldName] = val;
|
|
20725
20735
|
}
|
|
20726
20736
|
return values;
|
|
20727
20737
|
}
|
|
20728
20738
|
var SCHEMA_ERROR_PATTERNS = [
|
|
20729
20739
|
/issueFieldValues.*(?:doesn['’]t exist on type|is not defined on type).*['"`]?Issue['"`]?/i,
|
|
20730
|
-
/issueFields.*(?:doesn['’]t exist on type|is not defined on type).*['"`]?Repository['"`]?/i
|
|
20740
|
+
/issueFields.*(?:doesn['’]t exist on type|is not defined on type).*['"`]?Repository['"`]?/i,
|
|
20741
|
+
/(?:isIssueField|issueField).*(?:doesn['’]t exist on type|is not defined on type).*['"`]?ProjectV2/i,
|
|
20742
|
+
/(?:No such type\s+['"`]?(?:ProjectV2ItemIssueFieldValue|ProjectV2IssueFieldValues)['"`]?|(?:ProjectV2ItemIssueFieldValue|ProjectV2IssueFieldValues).*(?:doesn['’]t exist|is not defined))/i
|
|
20731
20743
|
];
|
|
20732
20744
|
function matchesSchemaError(text) {
|
|
20733
20745
|
return SCHEMA_ERROR_PATTERNS.some((pattern) => pattern.test(text));
|
|
@@ -21665,12 +21677,18 @@ var issueComment = defineTool({
|
|
|
21665
21677
|
// src/core/github/board-fields.ts
|
|
21666
21678
|
var VIEW_LAYOUT_SUFFIX = /_LAYOUT$/;
|
|
21667
21679
|
var FIELD_SELECTION = "__typename ... on ProjectV2FieldCommon{id name dataType} ... on ProjectV2SingleSelectField{options{id name color description}} ... on ProjectV2IterationField{configuration{iterations{id title startDate duration}}}";
|
|
21668
|
-
var
|
|
21680
|
+
var ISSUE_FIELD_SELECTION = "isIssueField issueField{__typename ... on IssueFieldSingleSelect{options{id name color description}}}";
|
|
21681
|
+
var READ_FIELD_SELECTION = `${FIELD_SELECTION} ... on ProjectV2Field{${ISSUE_FIELD_SELECTION}} ... on ProjectV2SingleSelectField{${ISSUE_FIELD_SELECTION}}`;
|
|
21682
|
+
var FIELDS_QUERY2 = `query($id:ID!,$after:String){node(id:$id){... on ProjectV2{fields(first:${PAGE_SIZE},after:$after){${PAGE_INFO} nodes{${READ_FIELD_SELECTION}}}}}}`;
|
|
21683
|
+
var FALLBACK_FIELDS_QUERY = `query($id:ID!,$after:String){node(id:$id){... on ProjectV2{fields(first:${PAGE_SIZE},after:$after){${PAGE_INFO} nodes{${FIELD_SELECTION}}}}}}`;
|
|
21669
21684
|
var VIEWS_QUERY = `query($id:ID!,$after:String){node(id:$id){... on ProjectV2{views(first:${PAGE_SIZE},after:$after){${PAGE_INFO} nodes{id number name layout filter}}}}}`;
|
|
21670
21685
|
var boardFieldSchema = external_exports.object({
|
|
21671
21686
|
id: external_exports.string(),
|
|
21672
21687
|
name: external_exports.string(),
|
|
21673
21688
|
type: external_exports.string().describe("GitHub's data type in lower case: text, number, date, single_select, iteration, title, assignees, \u2026"),
|
|
21689
|
+
issue_field: external_exports.boolean().describe(
|
|
21690
|
+
"Whether the column mirrors one of the organization's issue fields: its value is the issue's own, set with the issue tools, not on the board."
|
|
21691
|
+
),
|
|
21674
21692
|
options: external_exports.array(external_exports.object({ id: external_exports.string(), name: external_exports.string(), color: external_exports.string(), description: external_exports.string() })).optional().describe("single_select: the options, in order."),
|
|
21675
21693
|
iterations: external_exports.array(external_exports.object({ id: external_exports.string(), title: external_exports.string(), start_date: external_exports.string(), duration: external_exports.number().int() })).optional().describe("iteration: the iterations; duration is in days.")
|
|
21676
21694
|
});
|
|
@@ -21681,9 +21699,16 @@ var boardViewSchema = external_exports.object({
|
|
|
21681
21699
|
filter: external_exports.string().nullable()
|
|
21682
21700
|
});
|
|
21683
21701
|
function toField(raw) {
|
|
21684
|
-
const
|
|
21685
|
-
|
|
21686
|
-
|
|
21702
|
+
const isIssueField = Boolean(raw.isIssueField);
|
|
21703
|
+
const field = {
|
|
21704
|
+
id: raw.id,
|
|
21705
|
+
name: raw.name ?? "",
|
|
21706
|
+
type: (raw.dataType ?? "").toLowerCase(),
|
|
21707
|
+
issue_field: isIssueField
|
|
21708
|
+
};
|
|
21709
|
+
const rawOptions = isIssueField ? raw.issueField?.options ?? (field.type === "single_select" ? [] : void 0) : raw.options;
|
|
21710
|
+
if (rawOptions) {
|
|
21711
|
+
field.options = rawOptions.map((option) => ({
|
|
21687
21712
|
id: option.id,
|
|
21688
21713
|
name: option.name,
|
|
21689
21714
|
color: option.color ?? "",
|
|
@@ -21701,11 +21726,21 @@ function toField(raw) {
|
|
|
21701
21726
|
return field;
|
|
21702
21727
|
}
|
|
21703
21728
|
async function readBoardFields(projectId, ctx) {
|
|
21704
|
-
const
|
|
21705
|
-
|
|
21706
|
-
|
|
21707
|
-
|
|
21708
|
-
|
|
21729
|
+
const fetchFields = async (query) => {
|
|
21730
|
+
const { items } = await paginate(query, { id: projectId }, (data) => data?.node?.fields, {
|
|
21731
|
+
cwd: ctx.cwd,
|
|
21732
|
+
signal: ctx.signal
|
|
21733
|
+
});
|
|
21734
|
+
return items.filter((raw) => Boolean(raw?.id)).map(toField);
|
|
21735
|
+
};
|
|
21736
|
+
try {
|
|
21737
|
+
return await fetchFields(FIELDS_QUERY2);
|
|
21738
|
+
} catch (error62) {
|
|
21739
|
+
if (isIssueFieldsMissingError(error62)) {
|
|
21740
|
+
return await fetchFields(FALLBACK_FIELDS_QUERY);
|
|
21741
|
+
}
|
|
21742
|
+
throw error62;
|
|
21743
|
+
}
|
|
21709
21744
|
}
|
|
21710
21745
|
async function readBoardViewNodes(projectId, ctx) {
|
|
21711
21746
|
const { items } = await paginate(VIEWS_QUERY, { id: projectId }, (data) => data?.node?.views, {
|
|
@@ -21991,6 +22026,11 @@ async function ensureField(board, fields, request2, policy, ctx) {
|
|
|
21991
22026
|
if (request2.type !== "single_select" || request2.options.length === 0) return unchanged;
|
|
21992
22027
|
const merge2 = (policy === "replace" ? replaceOptions : appendOptions)(existing.options ?? [], request2.options);
|
|
21993
22028
|
if (!merge2.changed) return unchanged;
|
|
22029
|
+
if (existing.issue_field) {
|
|
22030
|
+
throw invalid7(
|
|
22031
|
+
`"${existing.name}" mirrors the organization's issue field; its options are the organization's and cannot be changed on a board.`
|
|
22032
|
+
);
|
|
22033
|
+
}
|
|
21994
22034
|
const data = await graphql(
|
|
21995
22035
|
updateOptionsMutation(optionsLiteral(merge2.options)),
|
|
21996
22036
|
{ field: existing.id },
|
|
@@ -22088,10 +22128,10 @@ var itemArgument = external_exports.string().describe(
|
|
|
22088
22128
|
"The board item: its node ID (PVTI_\u2026), or an issue or pull request as 12, #12, owner/repo#12 or its URL, found on the board that project, owner and repo choose. A draft can only be named by its ID."
|
|
22089
22129
|
);
|
|
22090
22130
|
var ITEM_NOTE = "An item is named by its node ID (PVTI_\u2026), which also tells its board (a project given beside it must be that board), or by an issue or pull request (12, #12, owner/repo#12 or its URL) that is on the board the lookup finds. A draft can only be named by its ID.";
|
|
22091
|
-
var FIELDS_NOTE = "Field values are set by field name and option or iteration name, matched without regard to case: text as a string, number as a number or numeric string, date as YYYY-MM-DD, single select as an option name, iteration as an iteration title or @current (the iteration containing today, UTC). Only text, number, date, single select and iteration fields can be set; GitHub's own (Title, Assignees, Labels, Milestone, Repository, Linked pull requests, \u2026) are set on the issue, and github_issue_edit sets assignees, labels and milestone. Every field and value is checked before anything is written.";
|
|
22131
|
+
var FIELDS_NOTE = "Field values are set by field name and option or iteration name, matched without regard to case: text as a string, number as a number or numeric string, date as YYYY-MM-DD, single select as an option name, iteration as an iteration title or @current (the iteration containing today, UTC). Only text, number, date, single select and iteration fields can be set; a column that mirrors an issue field is set with the issue tools' issue_fields, not here. GitHub's own (Title, Assignees, Labels, Milestone, Repository, Linked pull requests, \u2026) are set on the issue, and github_issue_edit sets assignees, labels and milestone. Every field and value is checked before anything is written.";
|
|
22092
22132
|
var fieldValuesSchema = external_exports.record(external_exports.string(), external_exports.union([external_exports.string(), external_exports.number()]));
|
|
22093
22133
|
var fieldValuesArgument = fieldValuesSchema.optional().describe(
|
|
22094
|
-
|
|
22134
|
+
`Field values to set, by field name: {"Status": "Todo", "Target": "2026-12-01"}. Values are text, a number, a YYYY-MM-DD date, an option name or an iteration title (@current is the one containing today). A column that mirrors an issue field is set with the issue tools' issue_fields, not here.`
|
|
22095
22135
|
);
|
|
22096
22136
|
var fieldsResultSchema = fieldValuesSchema.describe(
|
|
22097
22137
|
"The values that were set, by field name as the board spells it: an option or iteration by its name, a number as a number."
|
|
@@ -23558,7 +23598,10 @@ var FIELD_LABELS = 20;
|
|
|
23558
23598
|
var FIELD_USERS = 10;
|
|
23559
23599
|
var FIELD_PULL_REQUESTS = 10;
|
|
23560
23600
|
var FIELD_NAME = "field{... on ProjectV2FieldCommon{name}}";
|
|
23561
|
-
|
|
23601
|
+
function itemFields(includeIssueFields = true) {
|
|
23602
|
+
const issueFieldSelection = includeIssueFields ? `
|
|
23603
|
+
... on ProjectV2ItemIssueFieldValue{${FIELD_NAME} issueFieldValue{${ISSUE_FIELD_VALUE_SELECTION}}}` : "";
|
|
23604
|
+
return `id isArchived
|
|
23562
23605
|
fieldValues(first:${FIELD_VALUES}){nodes{
|
|
23563
23606
|
... on ProjectV2ItemFieldTextValue{text ${FIELD_NAME}}
|
|
23564
23607
|
... on ProjectV2ItemFieldNumberValue{number ${FIELD_NAME}}
|
|
@@ -23569,15 +23612,22 @@ fieldValues(first:${FIELD_VALUES}){nodes{
|
|
|
23569
23612
|
... on ProjectV2ItemFieldRepositoryValue{repository{nameWithOwner} ${FIELD_NAME}}
|
|
23570
23613
|
... on ProjectV2ItemFieldLabelValue{labels(first:${FIELD_LABELS}){nodes{name}} ${FIELD_NAME}}
|
|
23571
23614
|
... on ProjectV2ItemFieldUserValue{users(first:${FIELD_USERS}){nodes{login}} ${FIELD_NAME}}
|
|
23572
|
-
... on ProjectV2ItemFieldPullRequestValue{pullRequests(first:${FIELD_PULL_REQUESTS}){nodes{number}} ${FIELD_NAME}}
|
|
23615
|
+
... on ProjectV2ItemFieldPullRequestValue{pullRequests(first:${FIELD_PULL_REQUESTS}){nodes{number}} ${FIELD_NAME}}${issueFieldSelection}
|
|
23573
23616
|
}}
|
|
23574
23617
|
content{__typename
|
|
23575
23618
|
... on DraftIssue{id title body}
|
|
23576
23619
|
... on Issue{number title url issueState: state repository{nameWithOwner} issueType{name} labels(first:${CONTENT_LABELS}){nodes{name}} assignees(first:${CONTENT_ASSIGNEES}){nodes{login}} parent{number repository{nameWithOwner}} subIssuesSummary{total completed}}
|
|
23577
23620
|
... on PullRequest{number title url pullRequestState: state repository{nameWithOwner} labels(first:${CONTENT_LABELS}){nodes{name}} assignees(first:${CONTENT_ASSIGNEES}){nodes{login}}}
|
|
23578
23621
|
}`;
|
|
23579
|
-
|
|
23580
|
-
var
|
|
23622
|
+
}
|
|
23623
|
+
var ITEM_FIELDS = itemFields(true);
|
|
23624
|
+
var ITEM_FIELDS_WITHOUT_ISSUE_FIELDS = itemFields(false);
|
|
23625
|
+
var itemsQuery = (fields) => `query($id:ID!,$after:String){node(id:$id){... on ProjectV2{items(first:${PAGE_SIZE2},after:$after){pageInfo{hasNextPage endCursor} nodes{${fields}}}}}}`;
|
|
23626
|
+
var itemQuery = (fields) => `query($id:ID!){node(id:$id){... on ProjectV2Item{${fields}}}}`;
|
|
23627
|
+
var ITEMS_QUERY = itemsQuery(ITEM_FIELDS);
|
|
23628
|
+
var ITEMS_QUERY_WITHOUT_ISSUE_FIELDS = itemsQuery(ITEM_FIELDS_WITHOUT_ISSUE_FIELDS);
|
|
23629
|
+
var ITEM_QUERY = itemQuery(ITEM_FIELDS);
|
|
23630
|
+
var ITEM_QUERY_WITHOUT_ISSUE_FIELDS = itemQuery(ITEM_FIELDS_WITHOUT_ISSUE_FIELDS);
|
|
23581
23631
|
var iterationValueSchema = external_exports.object({
|
|
23582
23632
|
title: external_exports.string(),
|
|
23583
23633
|
start_date: external_exports.string(),
|
|
@@ -23608,7 +23658,7 @@ var boardItemSchema = external_exports.object({
|
|
|
23608
23658
|
item_id: external_exports.string(),
|
|
23609
23659
|
archived: external_exports.boolean(),
|
|
23610
23660
|
fields: external_exports.record(external_exports.string(), itemFieldValueSchema).describe(
|
|
23611
|
-
"Values by field name: text, number, date and single select as they are (the option's name), iteration as { title, start_date, duration }, milestone as its title, repository as owner/name, labels as names, users as logins, linked pull requests as numbers."
|
|
23661
|
+
"Values by field name: text, number, date and single select as they are (the option's name), a column mirroring an organization issue field as the issue's value (an option's name, or a list of names for a multi-select), iteration as { title, start_date, duration }, milestone as its title, repository as owner/name, labels as names, users as logins, linked pull requests as numbers."
|
|
23612
23662
|
),
|
|
23613
23663
|
content: itemContentSchema.nullable().describe("What the item stands for; null when GitHub did not return it.")
|
|
23614
23664
|
});
|
|
@@ -23616,6 +23666,7 @@ function nodesOf2(connection) {
|
|
|
23616
23666
|
return (connection?.nodes ?? []).filter((node2) => node2 != null);
|
|
23617
23667
|
}
|
|
23618
23668
|
function fieldValue(raw) {
|
|
23669
|
+
if ("issueFieldValue" in raw) return issueFieldValueOf(raw.issueFieldValue);
|
|
23619
23670
|
if ("text" in raw) return raw.text ?? null;
|
|
23620
23671
|
if ("number" in raw) return raw.number ?? null;
|
|
23621
23672
|
if ("date" in raw) return raw.date ?? null;
|
|
@@ -23679,16 +23730,34 @@ function toItem(raw) {
|
|
|
23679
23730
|
};
|
|
23680
23731
|
}
|
|
23681
23732
|
async function readBoardItems(projectId, ctx) {
|
|
23682
|
-
const
|
|
23683
|
-
|
|
23684
|
-
|
|
23685
|
-
|
|
23686
|
-
|
|
23687
|
-
|
|
23688
|
-
|
|
23733
|
+
const run = (query) => paginate(query, { id: projectId }, (data) => data?.node?.items, {
|
|
23734
|
+
cwd: ctx.cwd,
|
|
23735
|
+
signal: ctx.signal,
|
|
23736
|
+
limit: MAX_BOARD_ITEMS
|
|
23737
|
+
});
|
|
23738
|
+
let result;
|
|
23739
|
+
try {
|
|
23740
|
+
result = await run(ITEMS_QUERY);
|
|
23741
|
+
} catch (error62) {
|
|
23742
|
+
if (isIssueFieldsMissingError(error62)) {
|
|
23743
|
+
result = await run(ITEMS_QUERY_WITHOUT_ISSUE_FIELDS);
|
|
23744
|
+
} else {
|
|
23745
|
+
throw error62;
|
|
23746
|
+
}
|
|
23747
|
+
}
|
|
23748
|
+
return { items: result.items.filter((raw) => raw != null).map(toItem), truncated: result.truncated };
|
|
23689
23749
|
}
|
|
23690
23750
|
async function readBoardItem(itemId, ctx) {
|
|
23691
|
-
|
|
23751
|
+
let data;
|
|
23752
|
+
try {
|
|
23753
|
+
data = await graphql(ITEM_QUERY, { id: itemId }, ctx);
|
|
23754
|
+
} catch (error62) {
|
|
23755
|
+
if (isIssueFieldsMissingError(error62)) {
|
|
23756
|
+
data = await graphql(ITEM_QUERY_WITHOUT_ISSUE_FIELDS, { id: itemId }, ctx);
|
|
23757
|
+
} else {
|
|
23758
|
+
throw error62;
|
|
23759
|
+
}
|
|
23760
|
+
}
|
|
23692
23761
|
const raw = data?.node;
|
|
23693
23762
|
if (!raw?.id) return null;
|
|
23694
23763
|
return { item: toItem(raw), draftId: raw.content?.__typename === "DraftIssue" ? raw.content.id ?? null : null };
|
|
@@ -23913,7 +23982,9 @@ var input16 = external_exports.object({
|
|
|
23913
23982
|
item: itemArgument,
|
|
23914
23983
|
...ITEM_BOARD_SELECTION,
|
|
23915
23984
|
fields: fieldValuesArgument,
|
|
23916
|
-
clear_fields: external_exports.array(external_exports.string()).optional().describe(
|
|
23985
|
+
clear_fields: external_exports.array(external_exports.string()).optional().describe(
|
|
23986
|
+
"Names of the fields to empty. A column that mirrors an issue field is cleared with clear_issue_fields in github_issue_edit, not here."
|
|
23987
|
+
),
|
|
23917
23988
|
title: external_exports.string().optional().describe("Drafts only: the new title."),
|
|
23918
23989
|
body: external_exports.string().optional().describe("Drafts only: the new body, which replaces the whole old one; empty clears it."),
|
|
23919
23990
|
append_body: external_exports.string().optional().describe("Drafts only: text added after the current body, set off by a blank line.")
|
|
@@ -24221,7 +24292,7 @@ var projectRead = defineTool({
|
|
|
24221
24292
|
id: "github_project_read",
|
|
24222
24293
|
title: "Read GitHub project boards",
|
|
24223
24294
|
description: [
|
|
24224
|
-
'Read GitHub project boards (Projects v2). method "view": the board the lookup finds, its fields (with single-select options and iterations) and its views, with the route that found it and what every route considered.',
|
|
24295
|
+
'Read GitHub project boards (Projects v2). method "view": the board the lookup finds, its fields (with single-select options and iterations; a field mirroring an issue field is marked issue_field) and its views, with the route that found it and what every route considered.',
|
|
24225
24296
|
`method "list": an owner's boards (owner, a login or @me) or the boards linked to a repository (repo, default the working tree's), open ones unless include_closed; truncated says more matched than limit (default ${DEFAULT_LIST_LIMIT}).`,
|
|
24226
24297
|
`method "item_list": the board's items, filtered here by status (names, comma-separated, "-Name" leaves one out), content_type (issue, pr, draft), content_repo, search in the title and include_archived, with field values flattened by field name; truncated says the board holds more than ${MAX_BOARD_ITEMS} items or more matched than limit (default ${DEFAULT_ITEM_LIMIT}).`,
|
|
24227
24298
|
"A board is named by project: its number, owner/number or its URL, used as it is (closed too); a bare number takes owner, else the owner of repo or of the working tree's repository.",
|
package/dist/mcp.js
CHANGED
|
@@ -29679,11 +29679,18 @@ function listOf(names) {
|
|
|
29679
29679
|
return names.length > 0 ? names.map((name) => `"${name}"`).join(", ") : "none";
|
|
29680
29680
|
}
|
|
29681
29681
|
function settableNames(fields) {
|
|
29682
|
-
return listOf(fields.filter((field) => isSettable(field.type)).map((field) => field.name));
|
|
29682
|
+
return listOf(fields.filter((field) => isSettable(field.type) && !field.issue_field).map((field) => field.name));
|
|
29683
29683
|
}
|
|
29684
|
-
function settableField(fields, name) {
|
|
29684
|
+
function settableField(fields, name, action = "set") {
|
|
29685
29685
|
const field = fields.find((candidate) => sameTitle(candidate.name, name));
|
|
29686
29686
|
if (!field) throw invalid3(`The board has no field "${name}". Fields that can be set: ${settableNames(fields)}.`);
|
|
29687
|
+
if (field.issue_field) {
|
|
29688
|
+
const verb = action === "clear" ? "cleared" : "set";
|
|
29689
|
+
const fix = action === "clear" ? "Clear it with clear_issue_fields in github_issue_edit." : "Set it with issue_fields in github_issue_edit or github_issue_create.";
|
|
29690
|
+
throw invalid3(
|
|
29691
|
+
`"${field.name}" mirrors the organization's issue field; its value is the issue's own and cannot be ${verb} on a board. ${fix} Fields that can be set: ${settableNames(fields)}.`
|
|
29692
|
+
);
|
|
29693
|
+
}
|
|
29687
29694
|
if (!isSettable(field.type)) {
|
|
29688
29695
|
const hint = ISSUE_FIELD_TYPES.includes(field.type) ? " github_issue_edit sets assignees, labels and milestone on the issue." : "";
|
|
29689
29696
|
throw invalid3(
|
|
@@ -29763,7 +29770,7 @@ function planValue(field, value, now) {
|
|
|
29763
29770
|
function planFieldValues(fields, values, now = /* @__PURE__ */ new Date()) {
|
|
29764
29771
|
const writes = [];
|
|
29765
29772
|
for (const [name, value] of Object.entries(values)) {
|
|
29766
|
-
const field = settableField(fields, name);
|
|
29773
|
+
const field = settableField(fields, name, "set");
|
|
29767
29774
|
if (writes.some((write) => write.field.id === field.id)) {
|
|
29768
29775
|
throw invalid3(`fields names "${field.name}" twice; field names are matched without regard to case.`);
|
|
29769
29776
|
}
|
|
@@ -29774,7 +29781,7 @@ function planFieldValues(fields, values, now = /* @__PURE__ */ new Date()) {
|
|
|
29774
29781
|
function planClears(fields, names) {
|
|
29775
29782
|
const cleared = [];
|
|
29776
29783
|
for (const name of names) {
|
|
29777
|
-
const field = settableField(fields, name);
|
|
29784
|
+
const field = settableField(fields, name, "clear");
|
|
29778
29785
|
if (!cleared.some((each) => each.id === field.id)) cleared.push(field);
|
|
29779
29786
|
}
|
|
29780
29787
|
return cleared;
|
|
@@ -29789,38 +29796,43 @@ function isHeld(write, current) {
|
|
|
29789
29796
|
|
|
29790
29797
|
// src/core/github/issue-fields.ts
|
|
29791
29798
|
var issueFieldValuesSchema = external_exports.record(external_exports.string(), external_exports.union([external_exports.string(), external_exports.number(), external_exports.array(external_exports.string())]));
|
|
29792
|
-
var
|
|
29799
|
+
var ISSUE_FIELD_VALUE_SELECTION = "__typename ... on IssueFieldTextValue{value} ... on IssueFieldNumberValue{value} ... on IssueFieldDateValue{value} ... on IssueFieldSingleSelectValue{name} ... on IssueFieldMultiSelectValue{options{name}}";
|
|
29800
|
+
var ISSUE_FIELD_VALUES_FRAGMENT = `issueFieldValues(first:25){nodes{... on IssueFieldValueCommon{field{... on IssueFieldCommon{name}}} ${ISSUE_FIELD_VALUE_SELECTION}}}`;
|
|
29801
|
+
function issueFieldValueOf(node2) {
|
|
29802
|
+
if (!node2) return void 0;
|
|
29803
|
+
switch (node2.__typename) {
|
|
29804
|
+
case "IssueFieldTextValue":
|
|
29805
|
+
return typeof node2.value === "string" ? node2.value : void 0;
|
|
29806
|
+
case "IssueFieldNumberValue":
|
|
29807
|
+
return typeof node2.value === "number" && !Number.isNaN(node2.value) ? node2.value : void 0;
|
|
29808
|
+
case "IssueFieldDateValue":
|
|
29809
|
+
return typeof node2.value === "string" ? node2.value : void 0;
|
|
29810
|
+
case "IssueFieldSingleSelectValue":
|
|
29811
|
+
return typeof node2.name === "string" ? node2.name : void 0;
|
|
29812
|
+
case "IssueFieldMultiSelectValue": {
|
|
29813
|
+
const options = (node2.options ?? []).map((opt) => opt?.name).filter((name) => typeof name === "string");
|
|
29814
|
+
return options.length > 0 ? options : void 0;
|
|
29815
|
+
}
|
|
29816
|
+
default:
|
|
29817
|
+
return void 0;
|
|
29818
|
+
}
|
|
29819
|
+
}
|
|
29793
29820
|
function issueFieldValues(connection) {
|
|
29794
29821
|
const values = {};
|
|
29795
29822
|
for (const node2 of connection?.nodes ?? []) {
|
|
29796
29823
|
if (!node2) continue;
|
|
29797
29824
|
const fieldName = node2.field?.name?.trim();
|
|
29798
29825
|
if (!fieldName) continue;
|
|
29799
|
-
|
|
29800
|
-
|
|
29801
|
-
if (typeof node2.value === "string") values[fieldName] = node2.value;
|
|
29802
|
-
break;
|
|
29803
|
-
case "IssueFieldNumberValue":
|
|
29804
|
-
if (typeof node2.value === "number" && !Number.isNaN(node2.value)) values[fieldName] = node2.value;
|
|
29805
|
-
break;
|
|
29806
|
-
case "IssueFieldDateValue":
|
|
29807
|
-
if (typeof node2.value === "string") values[fieldName] = node2.value;
|
|
29808
|
-
break;
|
|
29809
|
-
case "IssueFieldSingleSelectValue":
|
|
29810
|
-
if (typeof node2.name === "string") values[fieldName] = node2.name;
|
|
29811
|
-
break;
|
|
29812
|
-
case "IssueFieldMultiSelectValue": {
|
|
29813
|
-
const options = (node2.options ?? []).map((opt) => opt?.name).filter((name) => typeof name === "string");
|
|
29814
|
-
if (options.length > 0) values[fieldName] = options;
|
|
29815
|
-
break;
|
|
29816
|
-
}
|
|
29817
|
-
}
|
|
29826
|
+
const val = issueFieldValueOf(node2);
|
|
29827
|
+
if (val !== void 0) values[fieldName] = val;
|
|
29818
29828
|
}
|
|
29819
29829
|
return values;
|
|
29820
29830
|
}
|
|
29821
29831
|
var SCHEMA_ERROR_PATTERNS = [
|
|
29822
29832
|
/issueFieldValues.*(?:doesn['’]t exist on type|is not defined on type).*['"`]?Issue['"`]?/i,
|
|
29823
|
-
/issueFields.*(?:doesn['’]t exist on type|is not defined on type).*['"`]?Repository['"`]?/i
|
|
29833
|
+
/issueFields.*(?:doesn['’]t exist on type|is not defined on type).*['"`]?Repository['"`]?/i,
|
|
29834
|
+
/(?:isIssueField|issueField).*(?:doesn['’]t exist on type|is not defined on type).*['"`]?ProjectV2/i,
|
|
29835
|
+
/(?:No such type\s+['"`]?(?:ProjectV2ItemIssueFieldValue|ProjectV2IssueFieldValues)['"`]?|(?:ProjectV2ItemIssueFieldValue|ProjectV2IssueFieldValues).*(?:doesn['’]t exist|is not defined))/i
|
|
29824
29836
|
];
|
|
29825
29837
|
function matchesSchemaError(text) {
|
|
29826
29838
|
return SCHEMA_ERROR_PATTERNS.some((pattern) => pattern.test(text));
|
|
@@ -30633,12 +30645,18 @@ var issueComment = defineTool({
|
|
|
30633
30645
|
// src/core/github/board-fields.ts
|
|
30634
30646
|
var VIEW_LAYOUT_SUFFIX = /_LAYOUT$/;
|
|
30635
30647
|
var FIELD_SELECTION = "__typename ... on ProjectV2FieldCommon{id name dataType} ... on ProjectV2SingleSelectField{options{id name color description}} ... on ProjectV2IterationField{configuration{iterations{id title startDate duration}}}";
|
|
30636
|
-
var
|
|
30648
|
+
var ISSUE_FIELD_SELECTION = "isIssueField issueField{__typename ... on IssueFieldSingleSelect{options{id name color description}}}";
|
|
30649
|
+
var READ_FIELD_SELECTION = `${FIELD_SELECTION} ... on ProjectV2Field{${ISSUE_FIELD_SELECTION}} ... on ProjectV2SingleSelectField{${ISSUE_FIELD_SELECTION}}`;
|
|
30650
|
+
var FIELDS_QUERY2 = `query($id:ID!,$after:String){node(id:$id){... on ProjectV2{fields(first:${PAGE_SIZE},after:$after){${PAGE_INFO} nodes{${READ_FIELD_SELECTION}}}}}}`;
|
|
30651
|
+
var FALLBACK_FIELDS_QUERY = `query($id:ID!,$after:String){node(id:$id){... on ProjectV2{fields(first:${PAGE_SIZE},after:$after){${PAGE_INFO} nodes{${FIELD_SELECTION}}}}}}`;
|
|
30637
30652
|
var VIEWS_QUERY = `query($id:ID!,$after:String){node(id:$id){... on ProjectV2{views(first:${PAGE_SIZE},after:$after){${PAGE_INFO} nodes{id number name layout filter}}}}}`;
|
|
30638
30653
|
var boardFieldSchema = external_exports.object({
|
|
30639
30654
|
id: external_exports.string(),
|
|
30640
30655
|
name: external_exports.string(),
|
|
30641
30656
|
type: external_exports.string().describe("GitHub's data type in lower case: text, number, date, single_select, iteration, title, assignees, \u2026"),
|
|
30657
|
+
issue_field: external_exports.boolean().describe(
|
|
30658
|
+
"Whether the column mirrors one of the organization's issue fields: its value is the issue's own, set with the issue tools, not on the board."
|
|
30659
|
+
),
|
|
30642
30660
|
options: external_exports.array(external_exports.object({ id: external_exports.string(), name: external_exports.string(), color: external_exports.string(), description: external_exports.string() })).optional().describe("single_select: the options, in order."),
|
|
30643
30661
|
iterations: external_exports.array(external_exports.object({ id: external_exports.string(), title: external_exports.string(), start_date: external_exports.string(), duration: external_exports.number().int() })).optional().describe("iteration: the iterations; duration is in days.")
|
|
30644
30662
|
});
|
|
@@ -30649,9 +30667,16 @@ var boardViewSchema = external_exports.object({
|
|
|
30649
30667
|
filter: external_exports.string().nullable()
|
|
30650
30668
|
});
|
|
30651
30669
|
function toField(raw) {
|
|
30652
|
-
const
|
|
30653
|
-
|
|
30654
|
-
|
|
30670
|
+
const isIssueField = Boolean(raw.isIssueField);
|
|
30671
|
+
const field = {
|
|
30672
|
+
id: raw.id,
|
|
30673
|
+
name: raw.name ?? "",
|
|
30674
|
+
type: (raw.dataType ?? "").toLowerCase(),
|
|
30675
|
+
issue_field: isIssueField
|
|
30676
|
+
};
|
|
30677
|
+
const rawOptions = isIssueField ? raw.issueField?.options ?? (field.type === "single_select" ? [] : void 0) : raw.options;
|
|
30678
|
+
if (rawOptions) {
|
|
30679
|
+
field.options = rawOptions.map((option) => ({
|
|
30655
30680
|
id: option.id,
|
|
30656
30681
|
name: option.name,
|
|
30657
30682
|
color: option.color ?? "",
|
|
@@ -30669,11 +30694,21 @@ function toField(raw) {
|
|
|
30669
30694
|
return field;
|
|
30670
30695
|
}
|
|
30671
30696
|
async function readBoardFields(projectId, ctx) {
|
|
30672
|
-
const
|
|
30673
|
-
|
|
30674
|
-
|
|
30675
|
-
|
|
30676
|
-
|
|
30697
|
+
const fetchFields = async (query) => {
|
|
30698
|
+
const { items } = await paginate(query, { id: projectId }, (data) => data?.node?.fields, {
|
|
30699
|
+
cwd: ctx.cwd,
|
|
30700
|
+
signal: ctx.signal
|
|
30701
|
+
});
|
|
30702
|
+
return items.filter((raw) => Boolean(raw?.id)).map(toField);
|
|
30703
|
+
};
|
|
30704
|
+
try {
|
|
30705
|
+
return await fetchFields(FIELDS_QUERY2);
|
|
30706
|
+
} catch (error62) {
|
|
30707
|
+
if (isIssueFieldsMissingError(error62)) {
|
|
30708
|
+
return await fetchFields(FALLBACK_FIELDS_QUERY);
|
|
30709
|
+
}
|
|
30710
|
+
throw error62;
|
|
30711
|
+
}
|
|
30677
30712
|
}
|
|
30678
30713
|
async function readBoardViewNodes(projectId, ctx) {
|
|
30679
30714
|
const { items } = await paginate(VIEWS_QUERY, { id: projectId }, (data) => data?.node?.views, {
|
|
@@ -30959,6 +30994,11 @@ async function ensureField(board, fields, request2, policy, ctx) {
|
|
|
30959
30994
|
if (request2.type !== "single_select" || request2.options.length === 0) return unchanged;
|
|
30960
30995
|
const merge2 = (policy === "replace" ? replaceOptions : appendOptions)(existing.options ?? [], request2.options);
|
|
30961
30996
|
if (!merge2.changed) return unchanged;
|
|
30997
|
+
if (existing.issue_field) {
|
|
30998
|
+
throw invalid7(
|
|
30999
|
+
`"${existing.name}" mirrors the organization's issue field; its options are the organization's and cannot be changed on a board.`
|
|
31000
|
+
);
|
|
31001
|
+
}
|
|
30962
31002
|
const data = await graphql(
|
|
30963
31003
|
updateOptionsMutation(optionsLiteral(merge2.options)),
|
|
30964
31004
|
{ field: existing.id },
|
|
@@ -31056,10 +31096,10 @@ var itemArgument = external_exports.string().describe(
|
|
|
31056
31096
|
"The board item: its node ID (PVTI_\u2026), or an issue or pull request as 12, #12, owner/repo#12 or its URL, found on the board that project, owner and repo choose. A draft can only be named by its ID."
|
|
31057
31097
|
);
|
|
31058
31098
|
var ITEM_NOTE = "An item is named by its node ID (PVTI_\u2026), which also tells its board (a project given beside it must be that board), or by an issue or pull request (12, #12, owner/repo#12 or its URL) that is on the board the lookup finds. A draft can only be named by its ID.";
|
|
31059
|
-
var FIELDS_NOTE = "Field values are set by field name and option or iteration name, matched without regard to case: text as a string, number as a number or numeric string, date as YYYY-MM-DD, single select as an option name, iteration as an iteration title or @current (the iteration containing today, UTC). Only text, number, date, single select and iteration fields can be set; GitHub's own (Title, Assignees, Labels, Milestone, Repository, Linked pull requests, \u2026) are set on the issue, and github_issue_edit sets assignees, labels and milestone. Every field and value is checked before anything is written.";
|
|
31099
|
+
var FIELDS_NOTE = "Field values are set by field name and option or iteration name, matched without regard to case: text as a string, number as a number or numeric string, date as YYYY-MM-DD, single select as an option name, iteration as an iteration title or @current (the iteration containing today, UTC). Only text, number, date, single select and iteration fields can be set; a column that mirrors an issue field is set with the issue tools' issue_fields, not here. GitHub's own (Title, Assignees, Labels, Milestone, Repository, Linked pull requests, \u2026) are set on the issue, and github_issue_edit sets assignees, labels and milestone. Every field and value is checked before anything is written.";
|
|
31060
31100
|
var fieldValuesSchema = external_exports.record(external_exports.string(), external_exports.union([external_exports.string(), external_exports.number()]));
|
|
31061
31101
|
var fieldValuesArgument = fieldValuesSchema.optional().describe(
|
|
31062
|
-
|
|
31102
|
+
`Field values to set, by field name: {"Status": "Todo", "Target": "2026-12-01"}. Values are text, a number, a YYYY-MM-DD date, an option name or an iteration title (@current is the one containing today). A column that mirrors an issue field is set with the issue tools' issue_fields, not here.`
|
|
31063
31103
|
);
|
|
31064
31104
|
var fieldsResultSchema = fieldValuesSchema.describe(
|
|
31065
31105
|
"The values that were set, by field name as the board spells it: an option or iteration by its name, a number as a number."
|
|
@@ -32526,7 +32566,10 @@ var FIELD_LABELS = 20;
|
|
|
32526
32566
|
var FIELD_USERS = 10;
|
|
32527
32567
|
var FIELD_PULL_REQUESTS = 10;
|
|
32528
32568
|
var FIELD_NAME = "field{... on ProjectV2FieldCommon{name}}";
|
|
32529
|
-
|
|
32569
|
+
function itemFields(includeIssueFields = true) {
|
|
32570
|
+
const issueFieldSelection = includeIssueFields ? `
|
|
32571
|
+
... on ProjectV2ItemIssueFieldValue{${FIELD_NAME} issueFieldValue{${ISSUE_FIELD_VALUE_SELECTION}}}` : "";
|
|
32572
|
+
return `id isArchived
|
|
32530
32573
|
fieldValues(first:${FIELD_VALUES}){nodes{
|
|
32531
32574
|
... on ProjectV2ItemFieldTextValue{text ${FIELD_NAME}}
|
|
32532
32575
|
... on ProjectV2ItemFieldNumberValue{number ${FIELD_NAME}}
|
|
@@ -32537,15 +32580,22 @@ fieldValues(first:${FIELD_VALUES}){nodes{
|
|
|
32537
32580
|
... on ProjectV2ItemFieldRepositoryValue{repository{nameWithOwner} ${FIELD_NAME}}
|
|
32538
32581
|
... on ProjectV2ItemFieldLabelValue{labels(first:${FIELD_LABELS}){nodes{name}} ${FIELD_NAME}}
|
|
32539
32582
|
... on ProjectV2ItemFieldUserValue{users(first:${FIELD_USERS}){nodes{login}} ${FIELD_NAME}}
|
|
32540
|
-
... on ProjectV2ItemFieldPullRequestValue{pullRequests(first:${FIELD_PULL_REQUESTS}){nodes{number}} ${FIELD_NAME}}
|
|
32583
|
+
... on ProjectV2ItemFieldPullRequestValue{pullRequests(first:${FIELD_PULL_REQUESTS}){nodes{number}} ${FIELD_NAME}}${issueFieldSelection}
|
|
32541
32584
|
}}
|
|
32542
32585
|
content{__typename
|
|
32543
32586
|
... on DraftIssue{id title body}
|
|
32544
32587
|
... on Issue{number title url issueState: state repository{nameWithOwner} issueType{name} labels(first:${CONTENT_LABELS}){nodes{name}} assignees(first:${CONTENT_ASSIGNEES}){nodes{login}} parent{number repository{nameWithOwner}} subIssuesSummary{total completed}}
|
|
32545
32588
|
... on PullRequest{number title url pullRequestState: state repository{nameWithOwner} labels(first:${CONTENT_LABELS}){nodes{name}} assignees(first:${CONTENT_ASSIGNEES}){nodes{login}}}
|
|
32546
32589
|
}`;
|
|
32547
|
-
|
|
32548
|
-
var
|
|
32590
|
+
}
|
|
32591
|
+
var ITEM_FIELDS = itemFields(true);
|
|
32592
|
+
var ITEM_FIELDS_WITHOUT_ISSUE_FIELDS = itemFields(false);
|
|
32593
|
+
var itemsQuery = (fields) => `query($id:ID!,$after:String){node(id:$id){... on ProjectV2{items(first:${PAGE_SIZE2},after:$after){pageInfo{hasNextPage endCursor} nodes{${fields}}}}}}`;
|
|
32594
|
+
var itemQuery = (fields) => `query($id:ID!){node(id:$id){... on ProjectV2Item{${fields}}}}`;
|
|
32595
|
+
var ITEMS_QUERY = itemsQuery(ITEM_FIELDS);
|
|
32596
|
+
var ITEMS_QUERY_WITHOUT_ISSUE_FIELDS = itemsQuery(ITEM_FIELDS_WITHOUT_ISSUE_FIELDS);
|
|
32597
|
+
var ITEM_QUERY = itemQuery(ITEM_FIELDS);
|
|
32598
|
+
var ITEM_QUERY_WITHOUT_ISSUE_FIELDS = itemQuery(ITEM_FIELDS_WITHOUT_ISSUE_FIELDS);
|
|
32549
32599
|
var iterationValueSchema = external_exports.object({
|
|
32550
32600
|
title: external_exports.string(),
|
|
32551
32601
|
start_date: external_exports.string(),
|
|
@@ -32576,7 +32626,7 @@ var boardItemSchema = external_exports.object({
|
|
|
32576
32626
|
item_id: external_exports.string(),
|
|
32577
32627
|
archived: external_exports.boolean(),
|
|
32578
32628
|
fields: external_exports.record(external_exports.string(), itemFieldValueSchema).describe(
|
|
32579
|
-
"Values by field name: text, number, date and single select as they are (the option's name), iteration as { title, start_date, duration }, milestone as its title, repository as owner/name, labels as names, users as logins, linked pull requests as numbers."
|
|
32629
|
+
"Values by field name: text, number, date and single select as they are (the option's name), a column mirroring an organization issue field as the issue's value (an option's name, or a list of names for a multi-select), iteration as { title, start_date, duration }, milestone as its title, repository as owner/name, labels as names, users as logins, linked pull requests as numbers."
|
|
32580
32630
|
),
|
|
32581
32631
|
content: itemContentSchema.nullable().describe("What the item stands for; null when GitHub did not return it.")
|
|
32582
32632
|
});
|
|
@@ -32584,6 +32634,7 @@ function nodesOf2(connection) {
|
|
|
32584
32634
|
return (connection?.nodes ?? []).filter((node2) => node2 != null);
|
|
32585
32635
|
}
|
|
32586
32636
|
function fieldValue(raw) {
|
|
32637
|
+
if ("issueFieldValue" in raw) return issueFieldValueOf(raw.issueFieldValue);
|
|
32587
32638
|
if ("text" in raw) return raw.text ?? null;
|
|
32588
32639
|
if ("number" in raw) return raw.number ?? null;
|
|
32589
32640
|
if ("date" in raw) return raw.date ?? null;
|
|
@@ -32647,16 +32698,34 @@ function toItem(raw) {
|
|
|
32647
32698
|
};
|
|
32648
32699
|
}
|
|
32649
32700
|
async function readBoardItems(projectId, ctx) {
|
|
32650
|
-
const
|
|
32651
|
-
|
|
32652
|
-
|
|
32653
|
-
|
|
32654
|
-
|
|
32655
|
-
|
|
32656
|
-
|
|
32701
|
+
const run = (query) => paginate(query, { id: projectId }, (data) => data?.node?.items, {
|
|
32702
|
+
cwd: ctx.cwd,
|
|
32703
|
+
signal: ctx.signal,
|
|
32704
|
+
limit: MAX_BOARD_ITEMS
|
|
32705
|
+
});
|
|
32706
|
+
let result;
|
|
32707
|
+
try {
|
|
32708
|
+
result = await run(ITEMS_QUERY);
|
|
32709
|
+
} catch (error62) {
|
|
32710
|
+
if (isIssueFieldsMissingError(error62)) {
|
|
32711
|
+
result = await run(ITEMS_QUERY_WITHOUT_ISSUE_FIELDS);
|
|
32712
|
+
} else {
|
|
32713
|
+
throw error62;
|
|
32714
|
+
}
|
|
32715
|
+
}
|
|
32716
|
+
return { items: result.items.filter((raw) => raw != null).map(toItem), truncated: result.truncated };
|
|
32657
32717
|
}
|
|
32658
32718
|
async function readBoardItem(itemId, ctx) {
|
|
32659
|
-
|
|
32719
|
+
let data;
|
|
32720
|
+
try {
|
|
32721
|
+
data = await graphql(ITEM_QUERY, { id: itemId }, ctx);
|
|
32722
|
+
} catch (error62) {
|
|
32723
|
+
if (isIssueFieldsMissingError(error62)) {
|
|
32724
|
+
data = await graphql(ITEM_QUERY_WITHOUT_ISSUE_FIELDS, { id: itemId }, ctx);
|
|
32725
|
+
} else {
|
|
32726
|
+
throw error62;
|
|
32727
|
+
}
|
|
32728
|
+
}
|
|
32660
32729
|
const raw = data?.node;
|
|
32661
32730
|
if (!raw?.id) return null;
|
|
32662
32731
|
return { item: toItem(raw), draftId: raw.content?.__typename === "DraftIssue" ? raw.content.id ?? null : null };
|
|
@@ -32881,7 +32950,9 @@ var input16 = external_exports.object({
|
|
|
32881
32950
|
item: itemArgument,
|
|
32882
32951
|
...ITEM_BOARD_SELECTION,
|
|
32883
32952
|
fields: fieldValuesArgument,
|
|
32884
|
-
clear_fields: external_exports.array(external_exports.string()).optional().describe(
|
|
32953
|
+
clear_fields: external_exports.array(external_exports.string()).optional().describe(
|
|
32954
|
+
"Names of the fields to empty. A column that mirrors an issue field is cleared with clear_issue_fields in github_issue_edit, not here."
|
|
32955
|
+
),
|
|
32885
32956
|
title: external_exports.string().optional().describe("Drafts only: the new title."),
|
|
32886
32957
|
body: external_exports.string().optional().describe("Drafts only: the new body, which replaces the whole old one; empty clears it."),
|
|
32887
32958
|
append_body: external_exports.string().optional().describe("Drafts only: text added after the current body, set off by a blank line.")
|
|
@@ -33189,7 +33260,7 @@ var projectRead = defineTool({
|
|
|
33189
33260
|
id: "github_project_read",
|
|
33190
33261
|
title: "Read GitHub project boards",
|
|
33191
33262
|
description: [
|
|
33192
|
-
'Read GitHub project boards (Projects v2). method "view": the board the lookup finds, its fields (with single-select options and iterations) and its views, with the route that found it and what every route considered.',
|
|
33263
|
+
'Read GitHub project boards (Projects v2). method "view": the board the lookup finds, its fields (with single-select options and iterations; a field mirroring an issue field is marked issue_field) and its views, with the route that found it and what every route considered.',
|
|
33193
33264
|
`method "list": an owner's boards (owner, a login or @me) or the boards linked to a repository (repo, default the working tree's), open ones unless include_closed; truncated says more matched than limit (default ${DEFAULT_LIST_LIMIT}).`,
|
|
33194
33265
|
`method "item_list": the board's items, filtered here by status (names, comma-separated, "-Name" leaves one out), content_type (issue, pr, draft), content_repo, search in the title and include_archived, with field values flattened by field name; truncated says the board holds more than ${MAX_BOARD_ITEMS} items or more matched than limit (default ${DEFAULT_ITEM_LIMIT}).`,
|
|
33195
33266
|
"A board is named by project: its number, owner/number or its URL, used as it is (closed too); a bare number takes owner, else the owner of repo or of the working tree's repository.",
|
|
@@ -37896,7 +37967,7 @@ async function main() {
|
|
|
37896
37967
|
const settings = parseSettings(process.argv.slice(2), process.env);
|
|
37897
37968
|
server = createServer(ALL_TOOLS, settings, {
|
|
37898
37969
|
ownDir: packageRoot(import.meta.url),
|
|
37899
|
-
version: true ? "0.6.
|
|
37970
|
+
version: true ? "0.6.1" : void 0
|
|
37900
37971
|
});
|
|
37901
37972
|
} catch (error62) {
|
|
37902
37973
|
if (!isToolError(error62)) throw error62;
|
package/dist/opencode.js
CHANGED
|
@@ -20585,11 +20585,18 @@ function listOf(names) {
|
|
|
20585
20585
|
return names.length > 0 ? names.map((name) => `"${name}"`).join(", ") : "none";
|
|
20586
20586
|
}
|
|
20587
20587
|
function settableNames(fields) {
|
|
20588
|
-
return listOf(fields.filter((field) => isSettable(field.type)).map((field) => field.name));
|
|
20588
|
+
return listOf(fields.filter((field) => isSettable(field.type) && !field.issue_field).map((field) => field.name));
|
|
20589
20589
|
}
|
|
20590
|
-
function settableField(fields, name) {
|
|
20590
|
+
function settableField(fields, name, action = "set") {
|
|
20591
20591
|
const field = fields.find((candidate) => sameTitle(candidate.name, name));
|
|
20592
20592
|
if (!field) throw invalid3(`The board has no field "${name}". Fields that can be set: ${settableNames(fields)}.`);
|
|
20593
|
+
if (field.issue_field) {
|
|
20594
|
+
const verb = action === "clear" ? "cleared" : "set";
|
|
20595
|
+
const fix = action === "clear" ? "Clear it with clear_issue_fields in github_issue_edit." : "Set it with issue_fields in github_issue_edit or github_issue_create.";
|
|
20596
|
+
throw invalid3(
|
|
20597
|
+
`"${field.name}" mirrors the organization's issue field; its value is the issue's own and cannot be ${verb} on a board. ${fix} Fields that can be set: ${settableNames(fields)}.`
|
|
20598
|
+
);
|
|
20599
|
+
}
|
|
20593
20600
|
if (!isSettable(field.type)) {
|
|
20594
20601
|
const hint = ISSUE_FIELD_TYPES.includes(field.type) ? " github_issue_edit sets assignees, labels and milestone on the issue." : "";
|
|
20595
20602
|
throw invalid3(
|
|
@@ -20669,7 +20676,7 @@ function planValue(field, value, now) {
|
|
|
20669
20676
|
function planFieldValues(fields, values, now = /* @__PURE__ */ new Date()) {
|
|
20670
20677
|
const writes = [];
|
|
20671
20678
|
for (const [name, value] of Object.entries(values)) {
|
|
20672
|
-
const field = settableField(fields, name);
|
|
20679
|
+
const field = settableField(fields, name, "set");
|
|
20673
20680
|
if (writes.some((write) => write.field.id === field.id)) {
|
|
20674
20681
|
throw invalid3(`fields names "${field.name}" twice; field names are matched without regard to case.`);
|
|
20675
20682
|
}
|
|
@@ -20680,7 +20687,7 @@ function planFieldValues(fields, values, now = /* @__PURE__ */ new Date()) {
|
|
|
20680
20687
|
function planClears(fields, names) {
|
|
20681
20688
|
const cleared = [];
|
|
20682
20689
|
for (const name of names) {
|
|
20683
|
-
const field = settableField(fields, name);
|
|
20690
|
+
const field = settableField(fields, name, "clear");
|
|
20684
20691
|
if (!cleared.some((each) => each.id === field.id)) cleared.push(field);
|
|
20685
20692
|
}
|
|
20686
20693
|
return cleared;
|
|
@@ -20695,38 +20702,43 @@ function isHeld(write, current) {
|
|
|
20695
20702
|
|
|
20696
20703
|
// src/core/github/issue-fields.ts
|
|
20697
20704
|
var issueFieldValuesSchema = external_exports.record(external_exports.string(), external_exports.union([external_exports.string(), external_exports.number(), external_exports.array(external_exports.string())]));
|
|
20698
|
-
var
|
|
20705
|
+
var ISSUE_FIELD_VALUE_SELECTION = "__typename ... on IssueFieldTextValue{value} ... on IssueFieldNumberValue{value} ... on IssueFieldDateValue{value} ... on IssueFieldSingleSelectValue{name} ... on IssueFieldMultiSelectValue{options{name}}";
|
|
20706
|
+
var ISSUE_FIELD_VALUES_FRAGMENT = `issueFieldValues(first:25){nodes{... on IssueFieldValueCommon{field{... on IssueFieldCommon{name}}} ${ISSUE_FIELD_VALUE_SELECTION}}}`;
|
|
20707
|
+
function issueFieldValueOf(node2) {
|
|
20708
|
+
if (!node2) return void 0;
|
|
20709
|
+
switch (node2.__typename) {
|
|
20710
|
+
case "IssueFieldTextValue":
|
|
20711
|
+
return typeof node2.value === "string" ? node2.value : void 0;
|
|
20712
|
+
case "IssueFieldNumberValue":
|
|
20713
|
+
return typeof node2.value === "number" && !Number.isNaN(node2.value) ? node2.value : void 0;
|
|
20714
|
+
case "IssueFieldDateValue":
|
|
20715
|
+
return typeof node2.value === "string" ? node2.value : void 0;
|
|
20716
|
+
case "IssueFieldSingleSelectValue":
|
|
20717
|
+
return typeof node2.name === "string" ? node2.name : void 0;
|
|
20718
|
+
case "IssueFieldMultiSelectValue": {
|
|
20719
|
+
const options = (node2.options ?? []).map((opt) => opt?.name).filter((name) => typeof name === "string");
|
|
20720
|
+
return options.length > 0 ? options : void 0;
|
|
20721
|
+
}
|
|
20722
|
+
default:
|
|
20723
|
+
return void 0;
|
|
20724
|
+
}
|
|
20725
|
+
}
|
|
20699
20726
|
function issueFieldValues(connection) {
|
|
20700
20727
|
const values = {};
|
|
20701
20728
|
for (const node2 of connection?.nodes ?? []) {
|
|
20702
20729
|
if (!node2) continue;
|
|
20703
20730
|
const fieldName = node2.field?.name?.trim();
|
|
20704
20731
|
if (!fieldName) continue;
|
|
20705
|
-
|
|
20706
|
-
|
|
20707
|
-
if (typeof node2.value === "string") values[fieldName] = node2.value;
|
|
20708
|
-
break;
|
|
20709
|
-
case "IssueFieldNumberValue":
|
|
20710
|
-
if (typeof node2.value === "number" && !Number.isNaN(node2.value)) values[fieldName] = node2.value;
|
|
20711
|
-
break;
|
|
20712
|
-
case "IssueFieldDateValue":
|
|
20713
|
-
if (typeof node2.value === "string") values[fieldName] = node2.value;
|
|
20714
|
-
break;
|
|
20715
|
-
case "IssueFieldSingleSelectValue":
|
|
20716
|
-
if (typeof node2.name === "string") values[fieldName] = node2.name;
|
|
20717
|
-
break;
|
|
20718
|
-
case "IssueFieldMultiSelectValue": {
|
|
20719
|
-
const options = (node2.options ?? []).map((opt) => opt?.name).filter((name) => typeof name === "string");
|
|
20720
|
-
if (options.length > 0) values[fieldName] = options;
|
|
20721
|
-
break;
|
|
20722
|
-
}
|
|
20723
|
-
}
|
|
20732
|
+
const val = issueFieldValueOf(node2);
|
|
20733
|
+
if (val !== void 0) values[fieldName] = val;
|
|
20724
20734
|
}
|
|
20725
20735
|
return values;
|
|
20726
20736
|
}
|
|
20727
20737
|
var SCHEMA_ERROR_PATTERNS = [
|
|
20728
20738
|
/issueFieldValues.*(?:doesn['’]t exist on type|is not defined on type).*['"`]?Issue['"`]?/i,
|
|
20729
|
-
/issueFields.*(?:doesn['’]t exist on type|is not defined on type).*['"`]?Repository['"`]?/i
|
|
20739
|
+
/issueFields.*(?:doesn['’]t exist on type|is not defined on type).*['"`]?Repository['"`]?/i,
|
|
20740
|
+
/(?:isIssueField|issueField).*(?:doesn['’]t exist on type|is not defined on type).*['"`]?ProjectV2/i,
|
|
20741
|
+
/(?:No such type\s+['"`]?(?:ProjectV2ItemIssueFieldValue|ProjectV2IssueFieldValues)['"`]?|(?:ProjectV2ItemIssueFieldValue|ProjectV2IssueFieldValues).*(?:doesn['’]t exist|is not defined))/i
|
|
20730
20742
|
];
|
|
20731
20743
|
function matchesSchemaError(text) {
|
|
20732
20744
|
return SCHEMA_ERROR_PATTERNS.some((pattern) => pattern.test(text));
|
|
@@ -21651,12 +21663,18 @@ var issueComment = defineTool({
|
|
|
21651
21663
|
// src/core/github/board-fields.ts
|
|
21652
21664
|
var VIEW_LAYOUT_SUFFIX = /_LAYOUT$/;
|
|
21653
21665
|
var FIELD_SELECTION = "__typename ... on ProjectV2FieldCommon{id name dataType} ... on ProjectV2SingleSelectField{options{id name color description}} ... on ProjectV2IterationField{configuration{iterations{id title startDate duration}}}";
|
|
21654
|
-
var
|
|
21666
|
+
var ISSUE_FIELD_SELECTION = "isIssueField issueField{__typename ... on IssueFieldSingleSelect{options{id name color description}}}";
|
|
21667
|
+
var READ_FIELD_SELECTION = `${FIELD_SELECTION} ... on ProjectV2Field{${ISSUE_FIELD_SELECTION}} ... on ProjectV2SingleSelectField{${ISSUE_FIELD_SELECTION}}`;
|
|
21668
|
+
var FIELDS_QUERY2 = `query($id:ID!,$after:String){node(id:$id){... on ProjectV2{fields(first:${PAGE_SIZE},after:$after){${PAGE_INFO} nodes{${READ_FIELD_SELECTION}}}}}}`;
|
|
21669
|
+
var FALLBACK_FIELDS_QUERY = `query($id:ID!,$after:String){node(id:$id){... on ProjectV2{fields(first:${PAGE_SIZE},after:$after){${PAGE_INFO} nodes{${FIELD_SELECTION}}}}}}`;
|
|
21655
21670
|
var VIEWS_QUERY = `query($id:ID!,$after:String){node(id:$id){... on ProjectV2{views(first:${PAGE_SIZE},after:$after){${PAGE_INFO} nodes{id number name layout filter}}}}}`;
|
|
21656
21671
|
var boardFieldSchema = external_exports.object({
|
|
21657
21672
|
id: external_exports.string(),
|
|
21658
21673
|
name: external_exports.string(),
|
|
21659
21674
|
type: external_exports.string().describe("GitHub's data type in lower case: text, number, date, single_select, iteration, title, assignees, \u2026"),
|
|
21675
|
+
issue_field: external_exports.boolean().describe(
|
|
21676
|
+
"Whether the column mirrors one of the organization's issue fields: its value is the issue's own, set with the issue tools, not on the board."
|
|
21677
|
+
),
|
|
21660
21678
|
options: external_exports.array(external_exports.object({ id: external_exports.string(), name: external_exports.string(), color: external_exports.string(), description: external_exports.string() })).optional().describe("single_select: the options, in order."),
|
|
21661
21679
|
iterations: external_exports.array(external_exports.object({ id: external_exports.string(), title: external_exports.string(), start_date: external_exports.string(), duration: external_exports.number().int() })).optional().describe("iteration: the iterations; duration is in days.")
|
|
21662
21680
|
});
|
|
@@ -21667,9 +21685,16 @@ var boardViewSchema = external_exports.object({
|
|
|
21667
21685
|
filter: external_exports.string().nullable()
|
|
21668
21686
|
});
|
|
21669
21687
|
function toField(raw) {
|
|
21670
|
-
const
|
|
21671
|
-
|
|
21672
|
-
|
|
21688
|
+
const isIssueField = Boolean(raw.isIssueField);
|
|
21689
|
+
const field = {
|
|
21690
|
+
id: raw.id,
|
|
21691
|
+
name: raw.name ?? "",
|
|
21692
|
+
type: (raw.dataType ?? "").toLowerCase(),
|
|
21693
|
+
issue_field: isIssueField
|
|
21694
|
+
};
|
|
21695
|
+
const rawOptions = isIssueField ? raw.issueField?.options ?? (field.type === "single_select" ? [] : void 0) : raw.options;
|
|
21696
|
+
if (rawOptions) {
|
|
21697
|
+
field.options = rawOptions.map((option) => ({
|
|
21673
21698
|
id: option.id,
|
|
21674
21699
|
name: option.name,
|
|
21675
21700
|
color: option.color ?? "",
|
|
@@ -21687,11 +21712,21 @@ function toField(raw) {
|
|
|
21687
21712
|
return field;
|
|
21688
21713
|
}
|
|
21689
21714
|
async function readBoardFields(projectId, ctx) {
|
|
21690
|
-
const
|
|
21691
|
-
|
|
21692
|
-
|
|
21693
|
-
|
|
21694
|
-
|
|
21715
|
+
const fetchFields = async (query) => {
|
|
21716
|
+
const { items } = await paginate(query, { id: projectId }, (data) => data?.node?.fields, {
|
|
21717
|
+
cwd: ctx.cwd,
|
|
21718
|
+
signal: ctx.signal
|
|
21719
|
+
});
|
|
21720
|
+
return items.filter((raw) => Boolean(raw?.id)).map(toField);
|
|
21721
|
+
};
|
|
21722
|
+
try {
|
|
21723
|
+
return await fetchFields(FIELDS_QUERY2);
|
|
21724
|
+
} catch (error62) {
|
|
21725
|
+
if (isIssueFieldsMissingError(error62)) {
|
|
21726
|
+
return await fetchFields(FALLBACK_FIELDS_QUERY);
|
|
21727
|
+
}
|
|
21728
|
+
throw error62;
|
|
21729
|
+
}
|
|
21695
21730
|
}
|
|
21696
21731
|
async function readBoardViewNodes(projectId, ctx) {
|
|
21697
21732
|
const { items } = await paginate(VIEWS_QUERY, { id: projectId }, (data) => data?.node?.views, {
|
|
@@ -21977,6 +22012,11 @@ async function ensureField(board, fields, request2, policy, ctx) {
|
|
|
21977
22012
|
if (request2.type !== "single_select" || request2.options.length === 0) return unchanged;
|
|
21978
22013
|
const merge2 = (policy === "replace" ? replaceOptions : appendOptions)(existing.options ?? [], request2.options);
|
|
21979
22014
|
if (!merge2.changed) return unchanged;
|
|
22015
|
+
if (existing.issue_field) {
|
|
22016
|
+
throw invalid7(
|
|
22017
|
+
`"${existing.name}" mirrors the organization's issue field; its options are the organization's and cannot be changed on a board.`
|
|
22018
|
+
);
|
|
22019
|
+
}
|
|
21980
22020
|
const data = await graphql(
|
|
21981
22021
|
updateOptionsMutation(optionsLiteral(merge2.options)),
|
|
21982
22022
|
{ field: existing.id },
|
|
@@ -22074,10 +22114,10 @@ var itemArgument = external_exports.string().describe(
|
|
|
22074
22114
|
"The board item: its node ID (PVTI_\u2026), or an issue or pull request as 12, #12, owner/repo#12 or its URL, found on the board that project, owner and repo choose. A draft can only be named by its ID."
|
|
22075
22115
|
);
|
|
22076
22116
|
var ITEM_NOTE = "An item is named by its node ID (PVTI_\u2026), which also tells its board (a project given beside it must be that board), or by an issue or pull request (12, #12, owner/repo#12 or its URL) that is on the board the lookup finds. A draft can only be named by its ID.";
|
|
22077
|
-
var FIELDS_NOTE = "Field values are set by field name and option or iteration name, matched without regard to case: text as a string, number as a number or numeric string, date as YYYY-MM-DD, single select as an option name, iteration as an iteration title or @current (the iteration containing today, UTC). Only text, number, date, single select and iteration fields can be set; GitHub's own (Title, Assignees, Labels, Milestone, Repository, Linked pull requests, \u2026) are set on the issue, and github_issue_edit sets assignees, labels and milestone. Every field and value is checked before anything is written.";
|
|
22117
|
+
var FIELDS_NOTE = "Field values are set by field name and option or iteration name, matched without regard to case: text as a string, number as a number or numeric string, date as YYYY-MM-DD, single select as an option name, iteration as an iteration title or @current (the iteration containing today, UTC). Only text, number, date, single select and iteration fields can be set; a column that mirrors an issue field is set with the issue tools' issue_fields, not here. GitHub's own (Title, Assignees, Labels, Milestone, Repository, Linked pull requests, \u2026) are set on the issue, and github_issue_edit sets assignees, labels and milestone. Every field and value is checked before anything is written.";
|
|
22078
22118
|
var fieldValuesSchema = external_exports.record(external_exports.string(), external_exports.union([external_exports.string(), external_exports.number()]));
|
|
22079
22119
|
var fieldValuesArgument = fieldValuesSchema.optional().describe(
|
|
22080
|
-
|
|
22120
|
+
`Field values to set, by field name: {"Status": "Todo", "Target": "2026-12-01"}. Values are text, a number, a YYYY-MM-DD date, an option name or an iteration title (@current is the one containing today). A column that mirrors an issue field is set with the issue tools' issue_fields, not here.`
|
|
22081
22121
|
);
|
|
22082
22122
|
var fieldsResultSchema = fieldValuesSchema.describe(
|
|
22083
22123
|
"The values that were set, by field name as the board spells it: an option or iteration by its name, a number as a number."
|
|
@@ -23544,7 +23584,10 @@ var FIELD_LABELS = 20;
|
|
|
23544
23584
|
var FIELD_USERS = 10;
|
|
23545
23585
|
var FIELD_PULL_REQUESTS = 10;
|
|
23546
23586
|
var FIELD_NAME = "field{... on ProjectV2FieldCommon{name}}";
|
|
23547
|
-
|
|
23587
|
+
function itemFields(includeIssueFields = true) {
|
|
23588
|
+
const issueFieldSelection = includeIssueFields ? `
|
|
23589
|
+
... on ProjectV2ItemIssueFieldValue{${FIELD_NAME} issueFieldValue{${ISSUE_FIELD_VALUE_SELECTION}}}` : "";
|
|
23590
|
+
return `id isArchived
|
|
23548
23591
|
fieldValues(first:${FIELD_VALUES}){nodes{
|
|
23549
23592
|
... on ProjectV2ItemFieldTextValue{text ${FIELD_NAME}}
|
|
23550
23593
|
... on ProjectV2ItemFieldNumberValue{number ${FIELD_NAME}}
|
|
@@ -23555,15 +23598,22 @@ fieldValues(first:${FIELD_VALUES}){nodes{
|
|
|
23555
23598
|
... on ProjectV2ItemFieldRepositoryValue{repository{nameWithOwner} ${FIELD_NAME}}
|
|
23556
23599
|
... on ProjectV2ItemFieldLabelValue{labels(first:${FIELD_LABELS}){nodes{name}} ${FIELD_NAME}}
|
|
23557
23600
|
... on ProjectV2ItemFieldUserValue{users(first:${FIELD_USERS}){nodes{login}} ${FIELD_NAME}}
|
|
23558
|
-
... on ProjectV2ItemFieldPullRequestValue{pullRequests(first:${FIELD_PULL_REQUESTS}){nodes{number}} ${FIELD_NAME}}
|
|
23601
|
+
... on ProjectV2ItemFieldPullRequestValue{pullRequests(first:${FIELD_PULL_REQUESTS}){nodes{number}} ${FIELD_NAME}}${issueFieldSelection}
|
|
23559
23602
|
}}
|
|
23560
23603
|
content{__typename
|
|
23561
23604
|
... on DraftIssue{id title body}
|
|
23562
23605
|
... on Issue{number title url issueState: state repository{nameWithOwner} issueType{name} labels(first:${CONTENT_LABELS}){nodes{name}} assignees(first:${CONTENT_ASSIGNEES}){nodes{login}} parent{number repository{nameWithOwner}} subIssuesSummary{total completed}}
|
|
23563
23606
|
... on PullRequest{number title url pullRequestState: state repository{nameWithOwner} labels(first:${CONTENT_LABELS}){nodes{name}} assignees(first:${CONTENT_ASSIGNEES}){nodes{login}}}
|
|
23564
23607
|
}`;
|
|
23565
|
-
|
|
23566
|
-
var
|
|
23608
|
+
}
|
|
23609
|
+
var ITEM_FIELDS = itemFields(true);
|
|
23610
|
+
var ITEM_FIELDS_WITHOUT_ISSUE_FIELDS = itemFields(false);
|
|
23611
|
+
var itemsQuery = (fields) => `query($id:ID!,$after:String){node(id:$id){... on ProjectV2{items(first:${PAGE_SIZE2},after:$after){pageInfo{hasNextPage endCursor} nodes{${fields}}}}}}`;
|
|
23612
|
+
var itemQuery = (fields) => `query($id:ID!){node(id:$id){... on ProjectV2Item{${fields}}}}`;
|
|
23613
|
+
var ITEMS_QUERY = itemsQuery(ITEM_FIELDS);
|
|
23614
|
+
var ITEMS_QUERY_WITHOUT_ISSUE_FIELDS = itemsQuery(ITEM_FIELDS_WITHOUT_ISSUE_FIELDS);
|
|
23615
|
+
var ITEM_QUERY = itemQuery(ITEM_FIELDS);
|
|
23616
|
+
var ITEM_QUERY_WITHOUT_ISSUE_FIELDS = itemQuery(ITEM_FIELDS_WITHOUT_ISSUE_FIELDS);
|
|
23567
23617
|
var iterationValueSchema = external_exports.object({
|
|
23568
23618
|
title: external_exports.string(),
|
|
23569
23619
|
start_date: external_exports.string(),
|
|
@@ -23594,7 +23644,7 @@ var boardItemSchema = external_exports.object({
|
|
|
23594
23644
|
item_id: external_exports.string(),
|
|
23595
23645
|
archived: external_exports.boolean(),
|
|
23596
23646
|
fields: external_exports.record(external_exports.string(), itemFieldValueSchema).describe(
|
|
23597
|
-
"Values by field name: text, number, date and single select as they are (the option's name), iteration as { title, start_date, duration }, milestone as its title, repository as owner/name, labels as names, users as logins, linked pull requests as numbers."
|
|
23647
|
+
"Values by field name: text, number, date and single select as they are (the option's name), a column mirroring an organization issue field as the issue's value (an option's name, or a list of names for a multi-select), iteration as { title, start_date, duration }, milestone as its title, repository as owner/name, labels as names, users as logins, linked pull requests as numbers."
|
|
23598
23648
|
),
|
|
23599
23649
|
content: itemContentSchema.nullable().describe("What the item stands for; null when GitHub did not return it.")
|
|
23600
23650
|
});
|
|
@@ -23602,6 +23652,7 @@ function nodesOf2(connection) {
|
|
|
23602
23652
|
return (connection?.nodes ?? []).filter((node2) => node2 != null);
|
|
23603
23653
|
}
|
|
23604
23654
|
function fieldValue(raw) {
|
|
23655
|
+
if ("issueFieldValue" in raw) return issueFieldValueOf(raw.issueFieldValue);
|
|
23605
23656
|
if ("text" in raw) return raw.text ?? null;
|
|
23606
23657
|
if ("number" in raw) return raw.number ?? null;
|
|
23607
23658
|
if ("date" in raw) return raw.date ?? null;
|
|
@@ -23665,16 +23716,34 @@ function toItem(raw) {
|
|
|
23665
23716
|
};
|
|
23666
23717
|
}
|
|
23667
23718
|
async function readBoardItems(projectId, ctx) {
|
|
23668
|
-
const
|
|
23669
|
-
|
|
23670
|
-
|
|
23671
|
-
|
|
23672
|
-
|
|
23673
|
-
|
|
23674
|
-
|
|
23719
|
+
const run = (query) => paginate(query, { id: projectId }, (data) => data?.node?.items, {
|
|
23720
|
+
cwd: ctx.cwd,
|
|
23721
|
+
signal: ctx.signal,
|
|
23722
|
+
limit: MAX_BOARD_ITEMS
|
|
23723
|
+
});
|
|
23724
|
+
let result;
|
|
23725
|
+
try {
|
|
23726
|
+
result = await run(ITEMS_QUERY);
|
|
23727
|
+
} catch (error62) {
|
|
23728
|
+
if (isIssueFieldsMissingError(error62)) {
|
|
23729
|
+
result = await run(ITEMS_QUERY_WITHOUT_ISSUE_FIELDS);
|
|
23730
|
+
} else {
|
|
23731
|
+
throw error62;
|
|
23732
|
+
}
|
|
23733
|
+
}
|
|
23734
|
+
return { items: result.items.filter((raw) => raw != null).map(toItem), truncated: result.truncated };
|
|
23675
23735
|
}
|
|
23676
23736
|
async function readBoardItem(itemId, ctx) {
|
|
23677
|
-
|
|
23737
|
+
let data;
|
|
23738
|
+
try {
|
|
23739
|
+
data = await graphql(ITEM_QUERY, { id: itemId }, ctx);
|
|
23740
|
+
} catch (error62) {
|
|
23741
|
+
if (isIssueFieldsMissingError(error62)) {
|
|
23742
|
+
data = await graphql(ITEM_QUERY_WITHOUT_ISSUE_FIELDS, { id: itemId }, ctx);
|
|
23743
|
+
} else {
|
|
23744
|
+
throw error62;
|
|
23745
|
+
}
|
|
23746
|
+
}
|
|
23678
23747
|
const raw = data?.node;
|
|
23679
23748
|
if (!raw?.id) return null;
|
|
23680
23749
|
return { item: toItem(raw), draftId: raw.content?.__typename === "DraftIssue" ? raw.content.id ?? null : null };
|
|
@@ -23899,7 +23968,9 @@ var input16 = external_exports.object({
|
|
|
23899
23968
|
item: itemArgument,
|
|
23900
23969
|
...ITEM_BOARD_SELECTION,
|
|
23901
23970
|
fields: fieldValuesArgument,
|
|
23902
|
-
clear_fields: external_exports.array(external_exports.string()).optional().describe(
|
|
23971
|
+
clear_fields: external_exports.array(external_exports.string()).optional().describe(
|
|
23972
|
+
"Names of the fields to empty. A column that mirrors an issue field is cleared with clear_issue_fields in github_issue_edit, not here."
|
|
23973
|
+
),
|
|
23903
23974
|
title: external_exports.string().optional().describe("Drafts only: the new title."),
|
|
23904
23975
|
body: external_exports.string().optional().describe("Drafts only: the new body, which replaces the whole old one; empty clears it."),
|
|
23905
23976
|
append_body: external_exports.string().optional().describe("Drafts only: text added after the current body, set off by a blank line.")
|
|
@@ -24207,7 +24278,7 @@ var projectRead = defineTool({
|
|
|
24207
24278
|
id: "github_project_read",
|
|
24208
24279
|
title: "Read GitHub project boards",
|
|
24209
24280
|
description: [
|
|
24210
|
-
'Read GitHub project boards (Projects v2). method "view": the board the lookup finds, its fields (with single-select options and iterations) and its views, with the route that found it and what every route considered.',
|
|
24281
|
+
'Read GitHub project boards (Projects v2). method "view": the board the lookup finds, its fields (with single-select options and iterations; a field mirroring an issue field is marked issue_field) and its views, with the route that found it and what every route considered.',
|
|
24211
24282
|
`method "list": an owner's boards (owner, a login or @me) or the boards linked to a repository (repo, default the working tree's), open ones unless include_closed; truncated says more matched than limit (default ${DEFAULT_LIST_LIMIT}).`,
|
|
24212
24283
|
`method "item_list": the board's items, filtered here by status (names, comma-separated, "-Name" leaves one out), content_type (issue, pr, draft), content_repo, search in the title and include_archived, with field values flattened by field name; truncated says the board holds more than ${MAX_BOARD_ITEMS} items or more matched than limit (default ${DEFAULT_ITEM_LIMIT}).`,
|
|
24213
24284
|
"A board is named by project: its number, owner/number or its URL, used as it is (closed too); a bare number takes owner, else the owner of repo or of the working tree's repository.",
|
package/package.json
CHANGED
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "github-tools",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.1",
|
|
5
5
|
"description": "GitHub tools for coding agents: issues, project boards, pull requests, repositories, releases and Actions.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "craftsamo",
|
package/plugin.yaml
CHANGED
|
@@ -48,11 +48,13 @@ if GitHub reports missing scopes, the user runs `gh auth refresh -s project`.
|
|
|
48
48
|
|
|
49
49
|
- **`view`** returns the board, its fields (with single-select options and
|
|
50
50
|
iterations) and its views. Read it before setting values you are unsure of:
|
|
51
|
-
field and option names come from there.
|
|
51
|
+
field and option names come from there. A field with `issue_field: true`
|
|
52
|
+
mirrors one of the organization's issue fields (see Field values).
|
|
52
53
|
- **`list`** returns an `owner`'s boards, or the boards linked to `repo`;
|
|
53
54
|
closed ones only with `include_closed`.
|
|
54
55
|
- **`item_list`** returns a board's items with their field values by field
|
|
55
|
-
name and their content (issue, pull request or draft)
|
|
56
|
+
name and their content (issue, pull request or draft); a column that
|
|
57
|
+
mirrors an issue field shows the issue's value. Filter with `status`
|
|
56
58
|
(`"Todo,In Progress"`, or `"-Done"` to leave one out), `content_type`,
|
|
57
59
|
`content_repo`, `search` (part of the title) and `include_archived`.
|
|
58
60
|
`truncated: true` means more matched than `limit` (default 100, at most
|
|
@@ -135,7 +137,12 @@ them; names, options and iterations match without regard to case.
|
|
|
135
137
|
|
|
136
138
|
Title, Assignees, Labels, Milestone, Repository, Linked pull requests and
|
|
137
139
|
GitHub's other own fields belong to the issue, not the board: set them with
|
|
138
|
-
`github_issue_edit`.
|
|
140
|
+
`github_issue_edit`. A column that mirrors one of the organization's issue
|
|
141
|
+
fields (`issue_field: true` in `github_project_read`, such as Priority or
|
|
142
|
+
Target date) holds the issue's own value, the same on every board: set it
|
|
143
|
+
with `issue_fields` and clear it with `clear_issue_fields` in
|
|
144
|
+
`github_issue_edit` (or `issue_fields` in `github_issue_create`); the board
|
|
145
|
+
tools refuse it. Every field and value is checked before anything is
|
|
139
146
|
written, so a refused call wrote nothing.
|
|
140
147
|
|
|
141
148
|
### Editing and archiving
|