Skip to content

feat: add work items toolset via GraphQL API#362

Open
DarkByteZero wants to merge 12 commits intozereight:mainfrom
DarkByteZero:feat/work-item-support
Open

feat: add work items toolset via GraphQL API#362
DarkByteZero wants to merge 12 commits intozereight:mainfrom
DarkByteZero:feat/work-item-support

Conversation

@DarkByteZero
Copy link

@DarkByteZero DarkByteZero commented Mar 10, 2026

Summary

GitLab standardizes planning objects around Work Items. The legacy Issues REST API remains supported, but it does not cover all modern work item capabilities. In particular, richer work item features such as configurable statuses and custom fields are exposed through the Work Item APIs, making GraphQL the more complete integration surface today.

This PR adds a new workitems toolset (opt-in via GITLAB_TOOLSETS) with 10 tools that cover the full work item lifecycle using the GraphQL API:

  • get_work_item - Fetch a single work item with all widget data (status, hierarchy, labels, health status, dates, milestone, iteration, progress, linked items with details, time tracking, related MRs/branches, custom fields, weight with rollup, color, confidential, author, timestamps). Output is optimized to omit null/empty values.
  • list_work_items - List and filter work items with pagination. Returns lean per-item summaries.
  • create_work_item - Create any work item type with support for labels, assignees, weight, parent hierarchy, health status, start/due dates, milestone, iteration, and confidentiality.
  • update_work_item - Consolidated update tool that handles all fields in one call: title, description, labels (set/add/remove), assignees, state, weight, status (by ID), health status, dates, milestone, iteration, confidentiality, parent (set/remove), children (add/remove), linked items (related/blocks/blocked_by), and custom fields (text/number/select/date).
  • convert_work_item_type - Convert between work item types.
  • move_work_item - Move a work item to a different project.
  • list_work_item_statuses - Discover available statuses, supported conversion types, and allowed child/parent types per work item type.
  • list_custom_field_definitions - Discover available custom field definitions (name, type, ID) for a work item type.
  • list_work_item_notes - List threaded discussions/notes on a work item with pagination and sort.
  • create_work_item_note - Add a note/comment to a work item. Supports Markdown, internal notes, and threaded replies.

All 9 GitLab work item types are supported: issue, task, incident, test_case, epic, key_result, objective, requirement, ticket.

Additional changes

  • Fixed createIssue where issue_type and weight fields were silently dropped
  • Added reusable GraphQL infrastructure: executeGraphQL, resolveWorkItemGID, resolveWorkItemTypeGID, resolveProjectPath
  • The toolset is opt-in (isDefault: false) so it does not affect existing users

Test plan

  • Verified all GraphQL queries/mutations work against a live GitLab Premium instance
  • Tested get, list, create, update, convert, move, notes tools
  • Tested list_work_item_statuses returns statuses, conversion types, and hierarchy constraints
  • Tested list_custom_field_definitions returns available fields
  • Verified output is lean (no null spam, labels as strings, assignees as usernames)
  • Build passes with no TypeScript errors

New `workitems` toolset with 6 tools replacing REST-based issue operations:

- get_work_item: fetch single item with all widgets (status, hierarchy, labels,
  health status, dates, milestone, linked items, time tracking, development/MRs,
  custom fields) — output optimized, omits null/empty values
- list_work_items: list/filter with pagination, returns lean summary per item
- create_work_item: create any type with labels, assignees, weight, parent,
  health status, dates, milestone, confidential
- update_work_item: consolidated update for all fields including status (by ID),
  parent/children hierarchy, linked items (related/blocks/blocked_by),
  custom fields, health status, dates, milestone
- convert_work_item_type: type conversion via workItemConvert mutation
- list_work_item_statuses: discover available statuses per type

Supports all 9 work item types: issue, task, incident, test_case, epic,
key_result, objective, requirement, ticket.

Also fixes:
- createIssue now passes all fields via spread (issue_type, weight were dropped)
- Adds reusable GraphQL infrastructure (executeGraphQL, resolveWorkItemGID,
  resolveWorkItemTypeGID, resolveProjectPath)
@DarkByteZero
Copy link
Author

Later issues tools could be fully replaced by new WorkItem API actually.

- Add list_custom_field_definitions tool to discover available custom fields per work item type
- Enhance getWorkItem with iteration, progress, color, linked items details, weight rollup, confidential, author, createdAt/closedAt
- Enhance list_work_item_statuses to return supported conversion types and allowed child/parent types
- Add iteration_id support to create_work_item and update_work_item
- Expand all type enums to include all 9 work item types
- Remove tier annotations from descriptions
- Add move_work_item tool using GraphQL issueMove mutation
- Remove redundant "Uses GitLab GraphQL API" from tool descriptions
…ools

- list_work_item_notes: threaded discussions with pagination and sort
- create_work_item_note: supports markdown, internal notes, and threaded replies
- Replace separate REST-based resolveLabelIds and resolveUserIds with
  single resolveNamesToIds GraphQL call (labels + users in one request)
- Fix assignees: use assigneeIds (UserID GIDs) instead of assigneeUsernames
  which isn't supported by WorkItemWidgetAssigneesInput
- Fix description type in update: String -> String! (non-null required)
- Fix linkType: String! -> WorkItemRelatedLinkType! for linked items
- Remove misleading 'labels' field from UpdateWorkItemSchema (only
  add_labels/remove_labels remain)
…inked items

Add namespace.fullPath to children and linkedItems GraphQL queries so
work items from other projects include their project path in the response.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new opt-in workitems toolset that exposes 10 GraphQL-based tools for managing GitLab work items (issues, tasks, epics, incidents, etc.). It also fixes the createIssue REST function which was silently dropping issue_type and weight fields.

Changes:

  • Added 10 new work item tools (get, list, create, update, convert, move, statuses, custom fields, notes) backed by GitLab GraphQL API, with reusable GraphQL infrastructure (executeGraphQL, resolveWorkItemGID, etc.)
  • Fixed createIssue to pass all schema fields (including issue_type and weight) to the API instead of only a hardcoded subset
  • Added corresponding Zod schemas for all new work item tools in schemas.ts

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
schemas.ts Added work item Zod schemas; fixed CreateIssueOptionsSchema/CreateIssueSchema/UpdateIssueSchema to properly include issue_type and weight
index.ts Added 10 work item tool definitions, toolset registration, GraphQL helper functions, and all work item CRUD/query implementations; fixed createIssue body construction

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Fix toUpperCase() for multi-word types (e.g. "Test Case" → "TEST_CASE")
- Add pagination limit to labels query (first: 250)
- List all 9 supported types in create_work_item description
…tatus, and fix custom fields

- Add get_timeline_events and create_timeline_event tools for incident management
- Fold severity and escalation_status into update_work_item (incident only)
- Fix list_custom_field_definitions to return selectOptions and workItemTypes
- Remove redundant resolveProjectPath call in updateWorkItem
@DarkByteZero DarkByteZero requested a review from Copilot March 15, 2026 09:47
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +3380 to +3392
async function resolveProjectPath(projectId: string): Promise<string> {
projectId = decodeURIComponent(projectId);
const effectiveProjectId = getEffectiveProjectId(projectId);
const projectUrl = new URL(
`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}`
);
const projectResponse = await fetch(projectUrl.toString(), {
...getFetchConfig(),
});
await handleGitLabError(projectResponse);
const project: any = await projectResponse.json();
return project.path_with_namespace;
}
Comment on lines +1478 to +1490
// --- Incident timeline event tools ---
{
name: "get_timeline_events",
description:
"List timeline events for an incident. Returns chronological events with notes, timestamps, and tags (Start time, End time, Impact detected, etc.).",
inputSchema: toJSONSchema(GetTimelineEventsSchema),
},
{
name: "create_timeline_event",
description:
"Create a timeline event on an incident. Supports tags: 'Start time', 'End time', 'Impact detected', 'Response initiated', 'Impact mitigated', 'Cause identified'.",
inputSchema: toJSONSchema(CreateTimelineEventSchema),
},
index.ts Outdated
Comment on lines +3145 to +3168
const { workItemGID: incidentGID } = await resolveWorkItemGID(projectId, incidentIid);

const data = await executeGraphQL<{ project: any }>(
`query($fullPath: ID!, $incidentId: IssueID!) {
project(fullPath: $fullPath) {
incidentManagementTimelineEvents(incidentId: $incidentId) {
nodes {
id
note
noteHtml
action
occurredAt
createdAt
timelineEventTags {
nodes {
id
name
}
}
}
}
}
}`,
{ fullPath: projectPath, incidentId: incidentGID }
index.ts Outdated
Comment on lines +3198 to +3202
const { workItemGID: incidentGID } = await resolveWorkItemGID(projectId, incidentIid);

const variables: Record<string, any> = {
input: {
incidentId: incidentGID,
index.ts Outdated
Comment on lines +3144 to +3145
const projectPath = await resolveProjectPath(projectId);
const { workItemGID: incidentGID } = await resolveWorkItemGID(projectId, incidentIid);
index.ts Outdated
Comment on lines +2482 to +2495
// Map input names to GitLab work item type names
const typeNameMap: Record<string, string> = {
issue: "Issue",
task: "Task",
incident: "Incident",
test_case: "Test Case",
epic: "Epic",
key_result: "Key Result",
objective: "Objective",
requirement: "Requirement",
ticket: "Ticket",
};

const targetName = typeNameMap[typeName];
- Fix GID type mismatch: timeline events use Issue GID, not WorkItem GID
- Remove redundant resolveProjectPath call in getTimelineEvents
- Deduplicate typeNameMap by reusing WORK_ITEM_TYPE_NAMES constant
… enums

- Use z.coerce.number() for all iid, weight, and numeric fields
- Accept type values in any case (e.g. "ISSUE", "Issue", "issue")
- Deduplicate typeNameMap by reusing WORK_ITEM_TYPE_NAMES
- Fix GID type for timeline events (Issue vs WorkItem)
- Remove redundant resolveProjectPath in getTimelineEvents
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants