@craftsamo/github-tools 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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|Organization)['"`]?/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));
@@ -20744,7 +20756,9 @@ function isIssueFieldsMissingError(error62) {
20744
20756
  var issueFieldInputSchema = issueFieldValuesSchema;
20745
20757
  var ISSUE_FIELDS_ARGUMENT = `The organization's issue fields to set, by field name (without regard to case): {"Priority": "High", "Target date": "2026-12-01"}. Each takes a text, a number, a date as YYYY-MM-DD, an option name, or a list of option names for a multi-select field.`;
20746
20758
  var MAX_ISSUE_FIELDS = 25;
20747
- var DEFINITIONS_QUERY = `query($owner:String!,$name:String!){repository(owner:$owner,name:$name){issueFields(first:${MAX_ISSUE_FIELDS}){nodes{__typename ... on IssueFieldCommon{name dataType} ... on IssueFieldText{id} ... on IssueFieldNumber{id} ... on IssueFieldDate{id} ... on IssueFieldSingleSelect{id options{id name}} ... on IssueFieldMultiSelect{id options{id name}}}}}}`;
20759
+ var DEFINITION_SELECTION = "__typename ... on IssueFieldCommon{name dataType} ... on IssueFieldText{id} ... on IssueFieldNumber{id} ... on IssueFieldDate{id} ... on IssueFieldSingleSelect{id options{id name}} ... on IssueFieldMultiSelect{id options{id name}}";
20760
+ var DEFINITIONS_QUERY = `query($owner:String!,$name:String!){repository(owner:$owner,name:$name){issueFields(first:${MAX_ISSUE_FIELDS}){nodes{${DEFINITION_SELECTION}}}}}`;
20761
+ var ORGANIZATION_DEFINITIONS_QUERY = `query($login:String!){repositoryOwner(login:$login){__typename ... on Organization{issueFields(first:${MAX_ISSUE_FIELDS}){nodes{${DEFINITION_SELECTION}}}}}}`;
20748
20762
  var VALUES_QUERY = `query($id:ID!){node(id:$id){... on Issue{${ISSUE_FIELD_VALUES_FRAGMENT}}}}`;
20749
20763
  var issueFieldTypes = {
20750
20764
  TEXT: "text",
@@ -20780,6 +20794,20 @@ async function readIssueFieldDefinitions(repo, ctx) {
20780
20794
  }
20781
20795
  return (data?.repository?.issueFields?.nodes ?? []).flatMap((raw) => definitionOf(raw) ?? []);
20782
20796
  }
20797
+ async function readOrganizationIssueFields(login, ctx) {
20798
+ let data;
20799
+ try {
20800
+ data = await graphql(ORGANIZATION_DEFINITIONS_QUERY, { login }, ctx);
20801
+ } catch (error62) {
20802
+ if (isIssueFieldsMissingError(error62)) return null;
20803
+ throw error62;
20804
+ }
20805
+ if (data?.repositoryOwner?.__typename !== "Organization") return null;
20806
+ return (data.repositoryOwner.issueFields?.nodes ?? []).flatMap((raw) => definitionOf(raw) ?? []);
20807
+ }
20808
+ function findIssueField(definitions, name) {
20809
+ return definitions.find((candidate) => sameTitle(candidate.name, name));
20810
+ }
20783
20811
  function invalid4(message) {
20784
20812
  return new ToolError("invalid_argument", message);
20785
20813
  }
@@ -20787,7 +20815,7 @@ function fieldNames(definitions) {
20787
20815
  return listOf(definitions.map((field) => field.name));
20788
20816
  }
20789
20817
  function fieldOf(repo, definitions, name) {
20790
- const field = definitions.find((candidate) => sameTitle(candidate.name, name));
20818
+ const field = findIssueField(definitions, name);
20791
20819
  if (!field) throw invalid4(`${repo.full} has no issue field "${name}". Fields: ${fieldNames(definitions)}.`);
20792
20820
  return field;
20793
20821
  }
@@ -21665,12 +21693,18 @@ var issueComment = defineTool({
21665
21693
  // src/core/github/board-fields.ts
21666
21694
  var VIEW_LAYOUT_SUFFIX = /_LAYOUT$/;
21667
21695
  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}}}}}}`;
21696
+ var ISSUE_FIELD_SELECTION = "isIssueField issueField{__typename ... on IssueFieldSingleSelect{options{id name color description}}}";
21697
+ var READ_FIELD_SELECTION = `${FIELD_SELECTION} ... on ProjectV2Field{${ISSUE_FIELD_SELECTION}} ... on ProjectV2SingleSelectField{${ISSUE_FIELD_SELECTION}}`;
21698
+ 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}}}}}}`;
21699
+ 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
21700
  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
21701
  var boardFieldSchema = external_exports.object({
21671
21702
  id: external_exports.string(),
21672
21703
  name: external_exports.string(),
21673
21704
  type: external_exports.string().describe("GitHub's data type in lower case: text, number, date, single_select, iteration, title, assignees, \u2026"),
21705
+ issue_field: external_exports.boolean().describe(
21706
+ "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."
21707
+ ),
21674
21708
  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
21709
  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
21710
  });
@@ -21681,9 +21715,16 @@ var boardViewSchema = external_exports.object({
21681
21715
  filter: external_exports.string().nullable()
21682
21716
  });
21683
21717
  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) => ({
21718
+ const isIssueField = Boolean(raw.isIssueField);
21719
+ const field = {
21720
+ id: raw.id,
21721
+ name: raw.name ?? "",
21722
+ type: (raw.dataType ?? "").toLowerCase(),
21723
+ issue_field: isIssueField
21724
+ };
21725
+ const rawOptions = isIssueField ? raw.issueField?.options ?? (field.type === "single_select" ? [] : void 0) : raw.options;
21726
+ if (rawOptions) {
21727
+ field.options = rawOptions.map((option) => ({
21687
21728
  id: option.id,
21688
21729
  name: option.name,
21689
21730
  color: option.color ?? "",
@@ -21701,11 +21742,21 @@ function toField(raw) {
21701
21742
  return field;
21702
21743
  }
21703
21744
  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);
21745
+ const fetchFields = async (query) => {
21746
+ const { items } = await paginate(query, { id: projectId }, (data) => data?.node?.fields, {
21747
+ cwd: ctx.cwd,
21748
+ signal: ctx.signal
21749
+ });
21750
+ return items.filter((raw) => Boolean(raw?.id)).map(toField);
21751
+ };
21752
+ try {
21753
+ return await fetchFields(FIELDS_QUERY2);
21754
+ } catch (error62) {
21755
+ if (isIssueFieldsMissingError(error62)) {
21756
+ return await fetchFields(FALLBACK_FIELDS_QUERY);
21757
+ }
21758
+ throw error62;
21759
+ }
21709
21760
  }
21710
21761
  async function readBoardViewNodes(projectId, ctx) {
21711
21762
  const { items } = await paginate(VIEWS_QUERY, { id: projectId }, (data) => data?.node?.views, {
@@ -21935,6 +21986,7 @@ var STATUS_FIELD = "Status";
21935
21986
  var FIELD_REPLY = `projectV2Field{${FIELD_SELECTION}}`;
21936
21987
  var createFieldMutation = (type, options) => `mutation($project:ID!,$name:String!){createProjectV2Field(input:{projectId:$project,dataType:${type.toUpperCase()},name:$name${options}}){${FIELD_REPLY}}}`;
21937
21988
  var updateOptionsMutation = (options) => `mutation($field:ID!){updateProjectV2Field(input:{fieldId:$field,singleSelectOptions:${options}}){${FIELD_REPLY}}}`;
21989
+ var createIssueFieldColumnMutation = `mutation($project:ID!,$field:ID!){createProjectV2IssueField(input:{projectId:$project,issueFieldId:$field}){projectV2Field{${READ_FIELD_SELECTION}}}}`;
21938
21990
  function invalid7(message) {
21939
21991
  return new ToolError("invalid_argument", message);
21940
21992
  }
@@ -21947,6 +21999,29 @@ function fieldRequest(name, type, options) {
21947
21999
  }
21948
22000
  return { name: trimmed, type, options: parsed };
21949
22001
  }
22002
+ function issueFieldColumnRequest(name, type) {
22003
+ const trimmed = name.trim();
22004
+ if (!trimmed) throw invalid7("name cannot be blank.");
22005
+ return { name: trimmed, type };
22006
+ }
22007
+ async function readOwnerIssueFields(owner, ctx) {
22008
+ return owner.owner_type === "organization" ? readOrganizationIssueFields(owner.owner, ctx) : null;
22009
+ }
22010
+ function requireIssueField(owner, definitions, request2) {
22011
+ if (owner.owner_type !== "organization") {
22012
+ throw invalid7(`Issue fields belong to an organization; ${owner.owner} is a user.`);
22013
+ }
22014
+ if (!definitions) throw invalid7("This GitHub has no issue fields.");
22015
+ const definition = findIssueField(definitions, request2.name);
22016
+ if (!definition) {
22017
+ const names = listOf(definitions.map((field) => field.name));
22018
+ throw invalid7(`${owner.owner} has no issue field "${request2.name}". Issue fields: ${names}.`);
22019
+ }
22020
+ if (request2.type && request2.type !== definition.type) {
22021
+ throw invalid7(`"${definition.name}" is ${definition.type}, not ${request2.type}.`);
22022
+ }
22023
+ return definition;
22024
+ }
21950
22025
  function optionsLiteral(options) {
21951
22026
  const items = options.map((option) => {
21952
22027
  if (!OPTION_COLORS.some((color) => color === option.color)) {
@@ -21991,6 +22066,11 @@ async function ensureField(board, fields, request2, policy, ctx) {
21991
22066
  if (request2.type !== "single_select" || request2.options.length === 0) return unchanged;
21992
22067
  const merge2 = (policy === "replace" ? replaceOptions : appendOptions)(existing.options ?? [], request2.options);
21993
22068
  if (!merge2.changed) return unchanged;
22069
+ if (existing.issue_field) {
22070
+ throw invalid7(
22071
+ `"${existing.name}" mirrors the organization's issue field; its options are the organization's and cannot be changed on a board.`
22072
+ );
22073
+ }
21994
22074
  const data = await graphql(
21995
22075
  updateOptionsMutation(optionsLiteral(merge2.options)),
21996
22076
  { field: existing.id },
@@ -21999,6 +22079,23 @@ async function ensureField(board, fields, request2, policy, ctx) {
21999
22079
  const field = fieldOf2(data?.updateProjectV2Field ?? null, existing.name);
22000
22080
  return { field, created: false, added_options: merge2.added, updated_options: merge2.updated };
22001
22081
  }
22082
+ async function ensureIssueFieldColumn(board, fields, definitions, request2, ctx) {
22083
+ const definition = requireIssueField(board, definitions, request2);
22084
+ const existing = fields.find((field2) => sameTitle(field2.name, definition.name));
22085
+ if (existing?.issue_field) return { field: existing, created: false, added_options: [], updated_options: [] };
22086
+ if (existing) {
22087
+ throw invalid7(
22088
+ `The board has its own field "${existing.name}"; an issue field column cannot take the same name. Rename or delete the board's field in the web UI, or use the board's field.`
22089
+ );
22090
+ }
22091
+ const data = await graphql(
22092
+ createIssueFieldColumnMutation,
22093
+ { project: board.id, field: definition.id },
22094
+ ctx
22095
+ );
22096
+ const field = fieldOf2(data?.createProjectV2IssueField ?? null, definition.name);
22097
+ return { field, created: true, added_options: [], updated_options: [] };
22098
+ }
22002
22099
 
22003
22100
  // src/core/projects/support.ts
22004
22101
  var BOARD_SELECTION = {
@@ -22022,60 +22119,65 @@ function boardSummary(board) {
22022
22119
  // src/core/projects/template.ts
22023
22120
  var STATUS_TODO = "Todo";
22024
22121
  var STATUS_WAITING = "Waiting";
22025
- var START_FIELD = "Start";
22026
- var TARGET_FIELD = "Target";
22027
- var NOT_DONE = "-status:Done -status:Cancelled";
22122
+ var DATE_MODES = ["board", "issue_fields"];
22123
+ var BOARD_DATE_FIELDS = { start: "Start", target: "Target" };
22124
+ var ISSUE_DATE_FIELDS = { start: "Start date", target: "Target date" };
22028
22125
  var DEFAULT_VIEW_NAME = "View 1";
22029
- var ROADMAP_NOTE = "Set the Roadmap view's date fields to Start and Target in the web UI; the API cannot set them.";
22030
- var STANDARD_FIELDS = [
22031
- {
22032
- name: STATUS_FIELD,
22033
- type: "single_select",
22034
- options: [
22035
- { name: STATUS_TODO, color: "BLUE", description: "Ready to start; nothing is in the way." },
22036
- { name: "In Progress", color: "YELLOW", description: "Being worked on now." },
22126
+ function roadmapNote(dates) {
22127
+ return `Set the Roadmap view's date fields to ${dates.start} and ${dates.target} in the web UI; the API cannot set them.`;
22128
+ }
22129
+ var ROADMAP_NOTE = roadmapNote(BOARD_DATE_FIELDS);
22130
+ var STATUS_REQUEST = {
22131
+ name: STATUS_FIELD,
22132
+ type: "single_select",
22133
+ options: [
22134
+ { name: STATUS_TODO, color: "BLUE", description: "Ready to start; nothing is in the way." },
22135
+ { name: "In Progress", color: "YELLOW", description: "Being worked on now." },
22136
+ {
22137
+ name: STATUS_WAITING,
22138
+ color: "GRAY",
22139
+ description: "Cannot move until something else clears: an open blocking issue, a review or a reply."
22140
+ },
22141
+ { name: "Done", color: "PURPLE", description: "Finished." },
22142
+ { name: "Cancelled", color: "RED", description: "Will not be done; kept for the record." }
22143
+ ]
22144
+ };
22145
+ function standardTemplate(dates) {
22146
+ const notDone = "-status:Done -status:Cancelled";
22147
+ return {
22148
+ roadmapNote: roadmapNote(dates),
22149
+ views: [
22150
+ { name: "Roadmap", layout: "roadmap", filter: "-status:Cancelled", sort_by: dates.start },
22037
22151
  {
22038
- name: STATUS_WAITING,
22039
- color: "GRAY",
22040
- description: "Cannot move until something else clears: an open blocking issue, a review or a reply."
22152
+ name: "Kanban",
22153
+ layout: "board",
22154
+ filter: notDone,
22155
+ column_by: STATUS_FIELD,
22156
+ fields: ["Title", "Assignees", "Repository", "Parent issue", dates.target],
22157
+ sort_by: dates.target
22041
22158
  },
22042
- { name: "Done", color: "PURPLE", description: "Finished." },
22043
- { name: "Cancelled", color: "RED", description: "Will not be done; kept for the record." }
22159
+ {
22160
+ name: "Backlog",
22161
+ layout: "table",
22162
+ filter: notDone,
22163
+ fields: [
22164
+ "Title",
22165
+ STATUS_FIELD,
22166
+ "Assignees",
22167
+ "Repository",
22168
+ "Milestone",
22169
+ "Parent issue",
22170
+ "Sub-issues progress",
22171
+ dates.start,
22172
+ dates.target,
22173
+ "Linked pull requests"
22174
+ ],
22175
+ group_by: STATUS_FIELD,
22176
+ sort_by: dates.target
22177
+ }
22044
22178
  ]
22045
- },
22046
- { name: START_FIELD, type: "date", options: [] },
22047
- { name: TARGET_FIELD, type: "date", options: [] }
22048
- ];
22049
- var STANDARD_VIEWS = [
22050
- { name: "Roadmap", layout: "roadmap", filter: "-status:Cancelled", sort_by: START_FIELD },
22051
- {
22052
- name: "Kanban",
22053
- layout: "board",
22054
- filter: NOT_DONE,
22055
- column_by: STATUS_FIELD,
22056
- fields: ["Title", "Assignees", "Repository", "Parent issue", TARGET_FIELD],
22057
- sort_by: TARGET_FIELD
22058
- },
22059
- {
22060
- name: "Backlog",
22061
- layout: "table",
22062
- filter: NOT_DONE,
22063
- fields: [
22064
- "Title",
22065
- STATUS_FIELD,
22066
- "Assignees",
22067
- "Repository",
22068
- "Milestone",
22069
- "Parent issue",
22070
- "Sub-issues progress",
22071
- START_FIELD,
22072
- TARGET_FIELD,
22073
- "Linked pull requests"
22074
- ],
22075
- group_by: STATUS_FIELD,
22076
- sort_by: TARGET_FIELD
22077
- }
22078
- ];
22179
+ };
22180
+ }
22079
22181
 
22080
22182
  // src/core/projects/item-support.ts
22081
22183
  var ITEM_BOARD_SELECTION = {
@@ -22088,10 +22190,10 @@ var itemArgument = external_exports.string().describe(
22088
22190
  "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
22191
  );
22090
22192
  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.";
22193
+ 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
22194
  var fieldValuesSchema = external_exports.record(external_exports.string(), external_exports.union([external_exports.string(), external_exports.number()]));
22093
22195
  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).'
22196
+ `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
22197
  );
22096
22198
  var fieldsResultSchema = fieldValuesSchema.describe(
22097
22199
  "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."
@@ -23310,6 +23412,9 @@ var input10 = external_exports.object({
23310
23412
  "A board to copy, without its draft items, when the board has to be made: its number, owner/number or URL (a bare number or a title is looked up among owner's boards)."
23311
23413
  ),
23312
23414
  standard: external_exports.boolean().optional().describe("Bring the board to the standard template: its fields and views (default true)."),
23415
+ dates: external_exports.enum(DATE_MODES).optional().describe(
23416
+ `"board" (default) makes the board's own date fields Start and Target; "issue_fields" uses the organization's issue fields Start date and Target date instead, so an issue carries the same dates on every board and they are set with the issue tools (a pull request or draft has none). Organization boards only.`
23417
+ ),
23313
23418
  link_repo: external_exports.string().optional().describe("owner/name of a repository to link the board to; needs write permission.")
23314
23419
  });
23315
23420
  var output10 = external_exports.object({
@@ -23341,23 +23446,41 @@ function openBoardTitled(boards, title) {
23341
23446
  }
23342
23447
  return matches[0];
23343
23448
  }
23344
- async function applyFields(board, fresh, report, ctx) {
23449
+ async function applyFields(board, fresh, mode, dates, definitions, report, ctx) {
23345
23450
  const fields = await readBoardFields(board.id, ctx);
23346
- for (const request2 of STANDARD_FIELDS) {
23347
- const { field, created, added_options, updated_options } = await ensureField(
23348
- board,
23349
- fields,
23350
- request2,
23351
- fresh && request2.name === STATUS_FIELD ? "replace" : "append",
23352
- ctx
23353
- );
23354
- report.push({ field: field.name, created, added_options, updated_options });
23451
+ const status = await ensureField(board, fields, STATUS_REQUEST, fresh ? "replace" : "append", ctx);
23452
+ report.push({
23453
+ field: status.field.name,
23454
+ created: status.created,
23455
+ added_options: status.added_options,
23456
+ updated_options: status.updated_options
23457
+ });
23458
+ if (mode === "issue_fields") {
23459
+ for (const name of [dates.start, dates.target]) {
23460
+ const outcome = await ensureIssueFieldColumn(board, fields, definitions, { name, type: "date" }, ctx);
23461
+ report.push({
23462
+ field: outcome.field.name,
23463
+ created: outcome.created,
23464
+ added_options: outcome.added_options,
23465
+ updated_options: outcome.updated_options
23466
+ });
23467
+ }
23468
+ } else {
23469
+ for (const name of [dates.start, dates.target]) {
23470
+ const outcome = await ensureField(board, fields, { name, type: "date", options: [] }, "append", ctx);
23471
+ report.push({
23472
+ field: outcome.field.name,
23473
+ created: outcome.created,
23474
+ added_options: outcome.added_options,
23475
+ updated_options: outcome.updated_options
23476
+ });
23477
+ }
23355
23478
  }
23356
23479
  }
23357
- async function applyViews(board, report, notes, ctx) {
23480
+ async function applyViews(board, templateViews, roadmapNote2, report, notes, ctx) {
23358
23481
  const before = await readBoardViewNodes(board.id, ctx);
23359
23482
  const readers = viewFieldReaders(board, ctx);
23360
- for (const request2 of STANDARD_VIEWS) {
23483
+ for (const request2 of templateViews) {
23361
23484
  if (before.some((view5) => sameTitle(view5.name, request2.name))) {
23362
23485
  report.push({ view: request2.name, created: false });
23363
23486
  continue;
@@ -23369,7 +23492,7 @@ async function applyViews(board, report, notes, ctx) {
23369
23492
  throw error62;
23370
23493
  }
23371
23494
  report.push({ view: request2.name, created: true });
23372
- if (request2.layout === "roadmap") notes.push(ROADMAP_NOTE);
23495
+ if (request2.layout === "roadmap") notes.push(roadmapNote2);
23373
23496
  }
23374
23497
  return before;
23375
23498
  }
@@ -23379,6 +23502,7 @@ var projectCreate = defineTool({
23379
23502
  description: [
23380
23503
  `Create a project board for a user or organization, or reuse the open board of that owner and exact title (case-insensitive; several are invalid_argument), and bring it to the standard template. The title defaults to ${DEFAULT_BOARD_TITLE}. A board that has to be made is empty, or a copy of template without its draft items.`,
23381
23504
  "With standard (the default): the fields Status (Todo, In Progress, Waiting, Done, Cancelled, each with a color and a description), Start and Target (dates), and the views Roadmap, Kanban and Backlog. On a board this call made, Status gets exactly those options, keeping the ids of GitHub's own Todo, In Progress and Done, and GitHub's default view View 1 is deleted once the three views exist. On a reused or copied board only the missing fields, options and views are added, and nothing is deleted. A reused board must be one the viewer may update.",
23505
+ `dates: "issue_fields" uses the organization's issue fields Start date and Target date as the board's dates, refused before the board is made when the owner is a user or lacks them.`,
23382
23506
  "link_repo links the board to a repository (needs write permission there) unless it is already linked. GitHub links only boards of the repository's own owner, so a repository of another owner is invalid_argument, before the board is made.",
23383
23507
  "Once the board exists, a failed step does not fail the call: it is listed in errors and the board is still returned.",
23384
23508
  `notes lists what the API cannot do, such as setting the Roadmap view's date fields. ${PROJECT_SCOPE_NOTE}`
@@ -23404,6 +23528,23 @@ var projectCreate = defineTool({
23404
23528
  linkRepo = await writableContext(repo, ctx);
23405
23529
  }
23406
23530
  const owner = await readBoardOwner(login, ctx);
23531
+ const dateMode = args.dates ?? "board";
23532
+ let dateNames = BOARD_DATE_FIELDS;
23533
+ let definitions = null;
23534
+ if (dateMode === "issue_fields") {
23535
+ if (!standard) throw invalid5("dates applies only with standard.");
23536
+ definitions = await readOwnerIssueFields({ owner: owner.login, owner_type: owner.type }, ctx);
23537
+ const startDef = requireIssueField({ owner: owner.login, owner_type: owner.type }, definitions, {
23538
+ name: ISSUE_DATE_FIELDS.start,
23539
+ type: "date"
23540
+ });
23541
+ const targetDef = requireIssueField({ owner: owner.login, owner_type: owner.type }, definitions, {
23542
+ name: ISSUE_DATE_FIELDS.target,
23543
+ type: "date"
23544
+ });
23545
+ dateNames = { start: startDef.name, target: targetDef.name };
23546
+ }
23547
+ const template = standardTemplate(dateNames);
23407
23548
  const reused = openBoardTitled((await ownerBoards(owner.login, ctx, owner.type)).boards, title);
23408
23549
  if (reused) assertBoardWritable(reused);
23409
23550
  const source = reused || !templateRef ? void 0 : (await resolveBoard({ project: templateRef, owner: ownerArg }, ctx)).project;
@@ -23416,8 +23557,11 @@ var projectCreate = defineTool({
23416
23557
  const notes = [];
23417
23558
  let deletedDefaultView = false;
23418
23559
  if (standard) {
23419
- await steps.record("fields", () => applyFields(board, fresh, fields, ctx));
23420
- const before = await steps.record("views", () => applyViews(board, views, notes, ctx));
23560
+ await steps.record("fields", () => applyFields(board, fresh, dateMode, dateNames, definitions, fields, ctx));
23561
+ const before = await steps.record(
23562
+ "views",
23563
+ () => applyViews(board, template.views, template.roadmapNote, views, notes, ctx)
23564
+ );
23421
23565
  const defaultView = fresh ? before?.find((view5) => view5.name === DEFAULT_VIEW_NAME) : void 0;
23422
23566
  if (defaultView) {
23423
23567
  deletedDefaultView = await steps.record("default_view", async () => {
@@ -23448,9 +23592,14 @@ var projectCreate = defineTool({
23448
23592
  var input11 = external_exports.object({
23449
23593
  ...BOARD_SELECTION,
23450
23594
  name: external_exports.string().describe("The field's name, matched without regard to case."),
23451
- type: external_exports.enum(FIELD_TYPES).describe("The field's type; a field of that name with another type is invalid_argument."),
23595
+ type: external_exports.enum(FIELD_TYPES).optional().describe(
23596
+ "The field's type; a field of that name with another type is invalid_argument. Required unless issue_field is true, where it only checks the issue field's type."
23597
+ ),
23452
23598
  options: external_exports.array(external_exports.string()).optional().describe(
23453
- `single_select only: Name, Name:COLOR or Name:COLOR:Description (the description is everything after the second colon). COLOR is one of ${OPTION_COLORS.join(", ")}, in any case; the default is GRAY.`
23599
+ `single_select only, not with issue_field: Name, Name:COLOR or Name:COLOR:Description (the description is everything after the second colon). COLOR is one of ${OPTION_COLORS.join(", ")}, in any case; the default is GRAY.`
23600
+ ),
23601
+ issue_field: external_exports.boolean().optional().describe(
23602
+ "Add a column that mirrors the organization's issue field of that name instead of a field of the board's own; its options and values are the organization's and the issue's."
23454
23603
  )
23455
23604
  });
23456
23605
  var output11 = external_exports.object({
@@ -23459,12 +23608,25 @@ var output11 = external_exports.object({
23459
23608
  created: external_exports.boolean().describe("Whether this call made the field."),
23460
23609
  added_options: external_exports.array(external_exports.string()).describe("The names of the options this call added; [] when none.")
23461
23610
  });
23611
+ function planEnsure(args) {
23612
+ if (args.issue_field) {
23613
+ if (args.options && args.options.length > 0) {
23614
+ throw new ToolError("invalid_argument", "options are the organization's with issue_field; leave them out.");
23615
+ }
23616
+ const request3 = issueFieldColumnRequest(args.name, args.type);
23617
+ return async (board, fields, ctx) => ensureIssueFieldColumn(board, fields, await readOwnerIssueFields(board, ctx), request3, ctx);
23618
+ }
23619
+ if (!args.type) throw new ToolError("invalid_argument", "type is required unless issue_field is true.");
23620
+ const request2 = fieldRequest(args.name, args.type, args.options);
23621
+ return (board, fields, ctx) => ensureField(board, fields, request2, "append", ctx);
23622
+ }
23462
23623
  var projectFieldEnsure = defineTool({
23463
23624
  id: "github_project_field_ensure",
23464
23625
  title: "Ensure a GitHub project field",
23465
23626
  description: [
23466
23627
  "Make sure a field exists on a project board the viewer may update, without ever making a second one: a missing field is created, and the missing options of an existing single select are appended after its own, which keep their id, name, color, description and order, so nothing already set moves.",
23467
23628
  "A new single_select field needs options. Status can be extended but not created. A field of that name with another type, options on a field that is not single_select, a repeated option name and a color GitHub does not list are invalid_argument, before anything is written.",
23629
+ "With issue_field: true (organization boards only) the column mirrors the organization's issue field of that name, such as Priority, Start date or Target date; it is added when the board lacks it, and a field of the board's own with that name is invalid_argument.",
23468
23630
  BOARD_LOOKUP_NOTE,
23469
23631
  PROJECT_SCOPE_NOTE
23470
23632
  ].join(" "),
@@ -23474,11 +23636,11 @@ var projectFieldEnsure = defineTool({
23474
23636
  input: input11,
23475
23637
  output: output11,
23476
23638
  async execute(args, ctx) {
23477
- const request2 = fieldRequest(args.name, args.type, args.options);
23639
+ const ensure = planEnsure(args);
23478
23640
  const { project } = await resolveBoard(args, ctx);
23479
23641
  assertBoardWritable(project);
23480
23642
  const fields = await readBoardFields(project.id, ctx);
23481
- const { field, created, added_options } = await ensureField(project, fields, request2, "append", ctx);
23643
+ const { field, created, added_options } = await ensure(project, fields, ctx);
23482
23644
  return { project: boardSummary(project), field, created, added_options };
23483
23645
  }
23484
23646
  });
@@ -23558,7 +23720,10 @@ var FIELD_LABELS = 20;
23558
23720
  var FIELD_USERS = 10;
23559
23721
  var FIELD_PULL_REQUESTS = 10;
23560
23722
  var FIELD_NAME = "field{... on ProjectV2FieldCommon{name}}";
23561
- var ITEM_FIELDS = `id isArchived
23723
+ function itemFields(includeIssueFields = true) {
23724
+ const issueFieldSelection = includeIssueFields ? `
23725
+ ... on ProjectV2ItemIssueFieldValue{${FIELD_NAME} issueFieldValue{${ISSUE_FIELD_VALUE_SELECTION}}}` : "";
23726
+ return `id isArchived
23562
23727
  fieldValues(first:${FIELD_VALUES}){nodes{
23563
23728
  ... on ProjectV2ItemFieldTextValue{text ${FIELD_NAME}}
23564
23729
  ... on ProjectV2ItemFieldNumberValue{number ${FIELD_NAME}}
@@ -23569,15 +23734,22 @@ fieldValues(first:${FIELD_VALUES}){nodes{
23569
23734
  ... on ProjectV2ItemFieldRepositoryValue{repository{nameWithOwner} ${FIELD_NAME}}
23570
23735
  ... on ProjectV2ItemFieldLabelValue{labels(first:${FIELD_LABELS}){nodes{name}} ${FIELD_NAME}}
23571
23736
  ... on ProjectV2ItemFieldUserValue{users(first:${FIELD_USERS}){nodes{login}} ${FIELD_NAME}}
23572
- ... on ProjectV2ItemFieldPullRequestValue{pullRequests(first:${FIELD_PULL_REQUESTS}){nodes{number}} ${FIELD_NAME}}
23737
+ ... on ProjectV2ItemFieldPullRequestValue{pullRequests(first:${FIELD_PULL_REQUESTS}){nodes{number}} ${FIELD_NAME}}${issueFieldSelection}
23573
23738
  }}
23574
23739
  content{__typename
23575
23740
  ... on DraftIssue{id title body}
23576
23741
  ... 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
23742
  ... on PullRequest{number title url pullRequestState: state repository{nameWithOwner} labels(first:${CONTENT_LABELS}){nodes{name}} assignees(first:${CONTENT_ASSIGNEES}){nodes{login}}}
23578
23743
  }`;
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}}}}`;
23744
+ }
23745
+ var ITEM_FIELDS = itemFields(true);
23746
+ var ITEM_FIELDS_WITHOUT_ISSUE_FIELDS = itemFields(false);
23747
+ var itemsQuery = (fields) => `query($id:ID!,$after:String){node(id:$id){... on ProjectV2{items(first:${PAGE_SIZE2},after:$after){pageInfo{hasNextPage endCursor} nodes{${fields}}}}}}`;
23748
+ var itemQuery = (fields) => `query($id:ID!){node(id:$id){... on ProjectV2Item{${fields}}}}`;
23749
+ var ITEMS_QUERY = itemsQuery(ITEM_FIELDS);
23750
+ var ITEMS_QUERY_WITHOUT_ISSUE_FIELDS = itemsQuery(ITEM_FIELDS_WITHOUT_ISSUE_FIELDS);
23751
+ var ITEM_QUERY = itemQuery(ITEM_FIELDS);
23752
+ var ITEM_QUERY_WITHOUT_ISSUE_FIELDS = itemQuery(ITEM_FIELDS_WITHOUT_ISSUE_FIELDS);
23581
23753
  var iterationValueSchema = external_exports.object({
23582
23754
  title: external_exports.string(),
23583
23755
  start_date: external_exports.string(),
@@ -23608,7 +23780,7 @@ var boardItemSchema = external_exports.object({
23608
23780
  item_id: external_exports.string(),
23609
23781
  archived: external_exports.boolean(),
23610
23782
  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."
23783
+ "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
23784
  ),
23613
23785
  content: itemContentSchema.nullable().describe("What the item stands for; null when GitHub did not return it.")
23614
23786
  });
@@ -23616,6 +23788,7 @@ function nodesOf2(connection) {
23616
23788
  return (connection?.nodes ?? []).filter((node2) => node2 != null);
23617
23789
  }
23618
23790
  function fieldValue(raw) {
23791
+ if ("issueFieldValue" in raw) return issueFieldValueOf(raw.issueFieldValue);
23619
23792
  if ("text" in raw) return raw.text ?? null;
23620
23793
  if ("number" in raw) return raw.number ?? null;
23621
23794
  if ("date" in raw) return raw.date ?? null;
@@ -23679,16 +23852,34 @@ function toItem(raw) {
23679
23852
  };
23680
23853
  }
23681
23854
  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 };
23855
+ const run = (query) => paginate(query, { id: projectId }, (data) => data?.node?.items, {
23856
+ cwd: ctx.cwd,
23857
+ signal: ctx.signal,
23858
+ limit: MAX_BOARD_ITEMS
23859
+ });
23860
+ let result;
23861
+ try {
23862
+ result = await run(ITEMS_QUERY);
23863
+ } catch (error62) {
23864
+ if (isIssueFieldsMissingError(error62)) {
23865
+ result = await run(ITEMS_QUERY_WITHOUT_ISSUE_FIELDS);
23866
+ } else {
23867
+ throw error62;
23868
+ }
23869
+ }
23870
+ return { items: result.items.filter((raw) => raw != null).map(toItem), truncated: result.truncated };
23689
23871
  }
23690
23872
  async function readBoardItem(itemId, ctx) {
23691
- const data = await graphql(ITEM_QUERY, { id: itemId }, ctx);
23873
+ let data;
23874
+ try {
23875
+ data = await graphql(ITEM_QUERY, { id: itemId }, ctx);
23876
+ } catch (error62) {
23877
+ if (isIssueFieldsMissingError(error62)) {
23878
+ data = await graphql(ITEM_QUERY_WITHOUT_ISSUE_FIELDS, { id: itemId }, ctx);
23879
+ } else {
23880
+ throw error62;
23881
+ }
23882
+ }
23692
23883
  const raw = data?.node;
23693
23884
  if (!raw?.id) return null;
23694
23885
  return { item: toItem(raw), draftId: raw.content?.__typename === "DraftIssue" ? raw.content.id ?? null : null };
@@ -23913,7 +24104,9 @@ var input16 = external_exports.object({
23913
24104
  item: itemArgument,
23914
24105
  ...ITEM_BOARD_SELECTION,
23915
24106
  fields: fieldValuesArgument,
23916
- clear_fields: external_exports.array(external_exports.string()).optional().describe("Names of the fields to empty."),
24107
+ clear_fields: external_exports.array(external_exports.string()).optional().describe(
24108
+ "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."
24109
+ ),
23917
24110
  title: external_exports.string().optional().describe("Drafts only: the new title."),
23918
24111
  body: external_exports.string().optional().describe("Drafts only: the new body, which replaces the whole old one; empty clears it."),
23919
24112
  append_body: external_exports.string().optional().describe("Drafts only: text added after the current body, set off by a blank line.")
@@ -24221,7 +24414,7 @@ var projectRead = defineTool({
24221
24414
  id: "github_project_read",
24222
24415
  title: "Read GitHub project boards",
24223
24416
  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.',
24417
+ '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
24418
  `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
24419
  `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
24420
  "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.",