@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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-tools",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "GitHub tools for coding agents: issues, project boards, pull requests, repositories, releases and Actions.",
5
5
  "author": {
6
6
  "name": "craftsamo",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-tools",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "GitHub tools for coding agents: issues, project boards, pull requests, repositories, releases and Actions.",
5
5
  "author": {
6
6
  "name": "craftsamo",
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 ISSUE_FIELD_VALUES_FRAGMENT = "issueFieldValues(first:25){nodes{__typename ... on IssueFieldValueCommon{field{... on IssueFieldCommon{name}}} ... on IssueFieldTextValue{value} ... on IssueFieldNumberValue{value} ... on IssueFieldDateValue{value} ... on IssueFieldSingleSelectValue{name} ... on IssueFieldMultiSelectValue{options{name}}}}";
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
- switch (node2.__typename) {
20707
- case "IssueFieldTextValue":
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 FIELDS_QUERY2 = `query($id:ID!,$after:String){node(id:$id){... on ProjectV2{fields(first:${PAGE_SIZE},after:$after){${PAGE_INFO} nodes{${FIELD_SELECTION}}}}}}`;
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 field = { id: raw.id, name: raw.name ?? "", type: (raw.dataType ?? "").toLowerCase() };
21685
- if (raw.options) {
21686
- field.options = raw.options.map((option) => ({
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 { items } = await paginate(FIELDS_QUERY2, { id: projectId }, (data) => data?.node?.fields, {
21705
- cwd: ctx.cwd,
21706
- signal: ctx.signal
21707
- });
21708
- return items.filter((raw) => Boolean(raw?.id)).map(toField);
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
- '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).'
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
- var ITEM_FIELDS = `id isArchived
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
- var ITEMS_QUERY = `query($id:ID!,$after:String){node(id:$id){... on ProjectV2{items(first:${PAGE_SIZE2},after:$after){pageInfo{hasNextPage endCursor} nodes{${ITEM_FIELDS}}}}}}`;
23580
- var ITEM_QUERY = `query($id:ID!){node(id:$id){... on ProjectV2Item{${ITEM_FIELDS}}}}`;
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 { items, truncated } = await paginate(
23683
- ITEMS_QUERY,
23684
- { id: projectId },
23685
- (data) => data?.node?.items,
23686
- { cwd: ctx.cwd, signal: ctx.signal, limit: MAX_BOARD_ITEMS }
23687
- );
23688
- return { items: items.filter((raw) => raw != null).map(toItem), truncated };
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
- const data = await graphql(ITEM_QUERY, { id: itemId }, ctx);
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("Names of the fields to empty."),
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 ISSUE_FIELD_VALUES_FRAGMENT = "issueFieldValues(first:25){nodes{__typename ... on IssueFieldValueCommon{field{... on IssueFieldCommon{name}}} ... on IssueFieldTextValue{value} ... on IssueFieldNumberValue{value} ... on IssueFieldDateValue{value} ... on IssueFieldSingleSelectValue{name} ... on IssueFieldMultiSelectValue{options{name}}}}";
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
- switch (node2.__typename) {
29800
- case "IssueFieldTextValue":
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 FIELDS_QUERY2 = `query($id:ID!,$after:String){node(id:$id){... on ProjectV2{fields(first:${PAGE_SIZE},after:$after){${PAGE_INFO} nodes{${FIELD_SELECTION}}}}}}`;
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 field = { id: raw.id, name: raw.name ?? "", type: (raw.dataType ?? "").toLowerCase() };
30653
- if (raw.options) {
30654
- field.options = raw.options.map((option) => ({
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 { items } = await paginate(FIELDS_QUERY2, { id: projectId }, (data) => data?.node?.fields, {
30673
- cwd: ctx.cwd,
30674
- signal: ctx.signal
30675
- });
30676
- return items.filter((raw) => Boolean(raw?.id)).map(toField);
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
- '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).'
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
- var ITEM_FIELDS = `id isArchived
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
- var ITEMS_QUERY = `query($id:ID!,$after:String){node(id:$id){... on ProjectV2{items(first:${PAGE_SIZE2},after:$after){pageInfo{hasNextPage endCursor} nodes{${ITEM_FIELDS}}}}}}`;
32548
- var ITEM_QUERY = `query($id:ID!){node(id:$id){... on ProjectV2Item{${ITEM_FIELDS}}}}`;
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 { items, truncated } = await paginate(
32651
- ITEMS_QUERY,
32652
- { id: projectId },
32653
- (data) => data?.node?.items,
32654
- { cwd: ctx.cwd, signal: ctx.signal, limit: MAX_BOARD_ITEMS }
32655
- );
32656
- return { items: items.filter((raw) => raw != null).map(toItem), truncated };
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
- const data = await graphql(ITEM_QUERY, { id: itemId }, ctx);
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("Names of the fields to empty."),
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.0" : void 0
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 ISSUE_FIELD_VALUES_FRAGMENT = "issueFieldValues(first:25){nodes{__typename ... on IssueFieldValueCommon{field{... on IssueFieldCommon{name}}} ... on IssueFieldTextValue{value} ... on IssueFieldNumberValue{value} ... on IssueFieldDateValue{value} ... on IssueFieldSingleSelectValue{name} ... on IssueFieldMultiSelectValue{options{name}}}}";
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
- switch (node2.__typename) {
20706
- case "IssueFieldTextValue":
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 FIELDS_QUERY2 = `query($id:ID!,$after:String){node(id:$id){... on ProjectV2{fields(first:${PAGE_SIZE},after:$after){${PAGE_INFO} nodes{${FIELD_SELECTION}}}}}}`;
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 field = { id: raw.id, name: raw.name ?? "", type: (raw.dataType ?? "").toLowerCase() };
21671
- if (raw.options) {
21672
- field.options = raw.options.map((option) => ({
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 { items } = await paginate(FIELDS_QUERY2, { id: projectId }, (data) => data?.node?.fields, {
21691
- cwd: ctx.cwd,
21692
- signal: ctx.signal
21693
- });
21694
- return items.filter((raw) => Boolean(raw?.id)).map(toField);
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
- '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).'
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
- var ITEM_FIELDS = `id isArchived
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
- var ITEMS_QUERY = `query($id:ID!,$after:String){node(id:$id){... on ProjectV2{items(first:${PAGE_SIZE2},after:$after){pageInfo{hasNextPage endCursor} nodes{${ITEM_FIELDS}}}}}}`;
23566
- var ITEM_QUERY = `query($id:ID!){node(id:$id){... on ProjectV2Item{${ITEM_FIELDS}}}}`;
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 { items, truncated } = await paginate(
23669
- ITEMS_QUERY,
23670
- { id: projectId },
23671
- (data) => data?.node?.items,
23672
- { cwd: ctx.cwd, signal: ctx.signal, limit: MAX_BOARD_ITEMS }
23673
- );
23674
- return { items: items.filter((raw) => raw != null).map(toItem), truncated };
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
- const data = await graphql(ITEM_QUERY, { id: itemId }, ctx);
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("Names of the fields to empty."),
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@craftsamo/github-tools",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "GitHub tools for coding agents: issues, project boards, pull requests, repositories, releases and Actions.",
5
5
  "author": "craftsamo",
6
6
  "license": "MIT",
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.0",
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
@@ -1,5 +1,5 @@
1
1
  name: github-tools
2
- version: 0.6.0
2
+ version: 0.6.1
3
3
  description: "GitHub tools for coding agents: issues, project boards, pull requests, repositories, releases and Actions."
4
4
  manifest_version: 1
5
5
  requires_hermes: ">=0.22"
@@ -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). Filter with `status`
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`. Every field and value is checked before anything is
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