@craftsamo/github-tools 0.6.1 → 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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-tools",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
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.1",
3
+ "version": "0.7.0",
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
@@ -220,6 +220,9 @@ after `github_project_create` set the Roadmap view's "Date fields" to Start and
220
220
  Target in the web UI. A column that mirrors an organization's issue field is
221
221
  read like any other, but its value is the issue's own: the board tools refuse
222
222
  to set or clear it, and `github_issue_edit` does both.
223
+ `github_project_field_ensure` with `issue_field: true` adds such a column, and
224
+ `github_project_create` with `dates: "issue_fields"` uses the issue fields Start
225
+ date and Target date as an organization board's dates.
223
226
 
224
227
  ## Reads and writes
225
228
 
package/dist/cli.js CHANGED
@@ -20737,7 +20737,7 @@ function issueFieldValues(connection) {
20737
20737
  }
20738
20738
  var SCHEMA_ERROR_PATTERNS = [
20739
20739
  /issueFieldValues.*(?:doesn['’]t exist on type|is not defined on type).*['"`]?Issue['"`]?/i,
20740
- /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
20741
  /(?:isIssueField|issueField).*(?:doesn['’]t exist on type|is not defined on type).*['"`]?ProjectV2/i,
20742
20742
  /(?:No such type\s+['"`]?(?:ProjectV2ItemIssueFieldValue|ProjectV2IssueFieldValues)['"`]?|(?:ProjectV2ItemIssueFieldValue|ProjectV2IssueFieldValues).*(?:doesn['’]t exist|is not defined))/i
20743
20743
  ];
@@ -20756,7 +20756,9 @@ function isIssueFieldsMissingError(error62) {
20756
20756
  var issueFieldInputSchema = issueFieldValuesSchema;
20757
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.`;
20758
20758
  var MAX_ISSUE_FIELDS = 25;
20759
- 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}}}}}}`;
20760
20762
  var VALUES_QUERY = `query($id:ID!){node(id:$id){... on Issue{${ISSUE_FIELD_VALUES_FRAGMENT}}}}`;
20761
20763
  var issueFieldTypes = {
20762
20764
  TEXT: "text",
@@ -20792,6 +20794,20 @@ async function readIssueFieldDefinitions(repo, ctx) {
20792
20794
  }
20793
20795
  return (data?.repository?.issueFields?.nodes ?? []).flatMap((raw) => definitionOf(raw) ?? []);
20794
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
+ }
20795
20811
  function invalid4(message) {
20796
20812
  return new ToolError("invalid_argument", message);
20797
20813
  }
@@ -20799,7 +20815,7 @@ function fieldNames(definitions) {
20799
20815
  return listOf(definitions.map((field) => field.name));
20800
20816
  }
20801
20817
  function fieldOf(repo, definitions, name) {
20802
- const field = definitions.find((candidate) => sameTitle(candidate.name, name));
20818
+ const field = findIssueField(definitions, name);
20803
20819
  if (!field) throw invalid4(`${repo.full} has no issue field "${name}". Fields: ${fieldNames(definitions)}.`);
20804
20820
  return field;
20805
20821
  }
@@ -21970,6 +21986,7 @@ var STATUS_FIELD = "Status";
21970
21986
  var FIELD_REPLY = `projectV2Field{${FIELD_SELECTION}}`;
21971
21987
  var createFieldMutation = (type, options) => `mutation($project:ID!,$name:String!){createProjectV2Field(input:{projectId:$project,dataType:${type.toUpperCase()},name:$name${options}}){${FIELD_REPLY}}}`;
21972
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}}}}`;
21973
21990
  function invalid7(message) {
21974
21991
  return new ToolError("invalid_argument", message);
21975
21992
  }
@@ -21982,6 +21999,29 @@ function fieldRequest(name, type, options) {
21982
21999
  }
21983
22000
  return { name: trimmed, type, options: parsed };
21984
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
+ }
21985
22025
  function optionsLiteral(options) {
21986
22026
  const items = options.map((option) => {
21987
22027
  if (!OPTION_COLORS.some((color) => color === option.color)) {
@@ -22039,6 +22079,23 @@ async function ensureField(board, fields, request2, policy, ctx) {
22039
22079
  const field = fieldOf2(data?.updateProjectV2Field ?? null, existing.name);
22040
22080
  return { field, created: false, added_options: merge2.added, updated_options: merge2.updated };
22041
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
+ }
22042
22099
 
22043
22100
  // src/core/projects/support.ts
22044
22101
  var BOARD_SELECTION = {
@@ -22062,60 +22119,65 @@ function boardSummary(board) {
22062
22119
  // src/core/projects/template.ts
22063
22120
  var STATUS_TODO = "Todo";
22064
22121
  var STATUS_WAITING = "Waiting";
22065
- var START_FIELD = "Start";
22066
- var TARGET_FIELD = "Target";
22067
- 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" };
22068
22125
  var DEFAULT_VIEW_NAME = "View 1";
22069
- var ROADMAP_NOTE = "Set the Roadmap view's date fields to Start and Target in the web UI; the API cannot set them.";
22070
- var STANDARD_FIELDS = [
22071
- {
22072
- name: STATUS_FIELD,
22073
- type: "single_select",
22074
- options: [
22075
- { name: STATUS_TODO, color: "BLUE", description: "Ready to start; nothing is in the way." },
22076
- { 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 },
22077
22151
  {
22078
- name: STATUS_WAITING,
22079
- color: "GRAY",
22080
- 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
22081
22158
  },
22082
- { name: "Done", color: "PURPLE", description: "Finished." },
22083
- { 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
+ }
22084
22178
  ]
22085
- },
22086
- { name: START_FIELD, type: "date", options: [] },
22087
- { name: TARGET_FIELD, type: "date", options: [] }
22088
- ];
22089
- var STANDARD_VIEWS = [
22090
- { name: "Roadmap", layout: "roadmap", filter: "-status:Cancelled", sort_by: START_FIELD },
22091
- {
22092
- name: "Kanban",
22093
- layout: "board",
22094
- filter: NOT_DONE,
22095
- column_by: STATUS_FIELD,
22096
- fields: ["Title", "Assignees", "Repository", "Parent issue", TARGET_FIELD],
22097
- sort_by: TARGET_FIELD
22098
- },
22099
- {
22100
- name: "Backlog",
22101
- layout: "table",
22102
- filter: NOT_DONE,
22103
- fields: [
22104
- "Title",
22105
- STATUS_FIELD,
22106
- "Assignees",
22107
- "Repository",
22108
- "Milestone",
22109
- "Parent issue",
22110
- "Sub-issues progress",
22111
- START_FIELD,
22112
- TARGET_FIELD,
22113
- "Linked pull requests"
22114
- ],
22115
- group_by: STATUS_FIELD,
22116
- sort_by: TARGET_FIELD
22117
- }
22118
- ];
22179
+ };
22180
+ }
22119
22181
 
22120
22182
  // src/core/projects/item-support.ts
22121
22183
  var ITEM_BOARD_SELECTION = {
@@ -23350,6 +23412,9 @@ var input10 = external_exports.object({
23350
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)."
23351
23413
  ),
23352
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
+ ),
23353
23418
  link_repo: external_exports.string().optional().describe("owner/name of a repository to link the board to; needs write permission.")
23354
23419
  });
23355
23420
  var output10 = external_exports.object({
@@ -23381,23 +23446,41 @@ function openBoardTitled(boards, title) {
23381
23446
  }
23382
23447
  return matches[0];
23383
23448
  }
23384
- async function applyFields(board, fresh, report, ctx) {
23449
+ async function applyFields(board, fresh, mode, dates, definitions, report, ctx) {
23385
23450
  const fields = await readBoardFields(board.id, ctx);
23386
- for (const request2 of STANDARD_FIELDS) {
23387
- const { field, created, added_options, updated_options } = await ensureField(
23388
- board,
23389
- fields,
23390
- request2,
23391
- fresh && request2.name === STATUS_FIELD ? "replace" : "append",
23392
- ctx
23393
- );
23394
- 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
+ }
23395
23478
  }
23396
23479
  }
23397
- async function applyViews(board, report, notes, ctx) {
23480
+ async function applyViews(board, templateViews, roadmapNote2, report, notes, ctx) {
23398
23481
  const before = await readBoardViewNodes(board.id, ctx);
23399
23482
  const readers = viewFieldReaders(board, ctx);
23400
- for (const request2 of STANDARD_VIEWS) {
23483
+ for (const request2 of templateViews) {
23401
23484
  if (before.some((view5) => sameTitle(view5.name, request2.name))) {
23402
23485
  report.push({ view: request2.name, created: false });
23403
23486
  continue;
@@ -23409,7 +23492,7 @@ async function applyViews(board, report, notes, ctx) {
23409
23492
  throw error62;
23410
23493
  }
23411
23494
  report.push({ view: request2.name, created: true });
23412
- if (request2.layout === "roadmap") notes.push(ROADMAP_NOTE);
23495
+ if (request2.layout === "roadmap") notes.push(roadmapNote2);
23413
23496
  }
23414
23497
  return before;
23415
23498
  }
@@ -23419,6 +23502,7 @@ var projectCreate = defineTool({
23419
23502
  description: [
23420
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.`,
23421
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.`,
23422
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.",
23423
23507
  "Once the board exists, a failed step does not fail the call: it is listed in errors and the board is still returned.",
23424
23508
  `notes lists what the API cannot do, such as setting the Roadmap view's date fields. ${PROJECT_SCOPE_NOTE}`
@@ -23444,6 +23528,23 @@ var projectCreate = defineTool({
23444
23528
  linkRepo = await writableContext(repo, ctx);
23445
23529
  }
23446
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);
23447
23548
  const reused = openBoardTitled((await ownerBoards(owner.login, ctx, owner.type)).boards, title);
23448
23549
  if (reused) assertBoardWritable(reused);
23449
23550
  const source = reused || !templateRef ? void 0 : (await resolveBoard({ project: templateRef, owner: ownerArg }, ctx)).project;
@@ -23456,8 +23557,11 @@ var projectCreate = defineTool({
23456
23557
  const notes = [];
23457
23558
  let deletedDefaultView = false;
23458
23559
  if (standard) {
23459
- await steps.record("fields", () => applyFields(board, fresh, fields, ctx));
23460
- 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
+ );
23461
23565
  const defaultView = fresh ? before?.find((view5) => view5.name === DEFAULT_VIEW_NAME) : void 0;
23462
23566
  if (defaultView) {
23463
23567
  deletedDefaultView = await steps.record("default_view", async () => {
@@ -23488,9 +23592,14 @@ var projectCreate = defineTool({
23488
23592
  var input11 = external_exports.object({
23489
23593
  ...BOARD_SELECTION,
23490
23594
  name: external_exports.string().describe("The field's name, matched without regard to case."),
23491
- 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
+ ),
23492
23598
  options: external_exports.array(external_exports.string()).optional().describe(
23493
- `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."
23494
23603
  )
23495
23604
  });
23496
23605
  var output11 = external_exports.object({
@@ -23499,12 +23608,25 @@ var output11 = external_exports.object({
23499
23608
  created: external_exports.boolean().describe("Whether this call made the field."),
23500
23609
  added_options: external_exports.array(external_exports.string()).describe("The names of the options this call added; [] when none.")
23501
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
+ }
23502
23623
  var projectFieldEnsure = defineTool({
23503
23624
  id: "github_project_field_ensure",
23504
23625
  title: "Ensure a GitHub project field",
23505
23626
  description: [
23506
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.",
23507
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.",
23508
23630
  BOARD_LOOKUP_NOTE,
23509
23631
  PROJECT_SCOPE_NOTE
23510
23632
  ].join(" "),
@@ -23514,11 +23636,11 @@ var projectFieldEnsure = defineTool({
23514
23636
  input: input11,
23515
23637
  output: output11,
23516
23638
  async execute(args, ctx) {
23517
- const request2 = fieldRequest(args.name, args.type, args.options);
23639
+ const ensure = planEnsure(args);
23518
23640
  const { project } = await resolveBoard(args, ctx);
23519
23641
  assertBoardWritable(project);
23520
23642
  const fields = await readBoardFields(project.id, ctx);
23521
- const { field, created, added_options } = await ensureField(project, fields, request2, "append", ctx);
23643
+ const { field, created, added_options } = await ensure(project, fields, ctx);
23522
23644
  return { project: boardSummary(project), field, created, added_options };
23523
23645
  }
23524
23646
  });