epics

package
v3.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 10, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package epics implements MCP tools for GitLab epic operations.

The package wraps the GitLab Epics API:

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ActionSpecs

func ActionSpecs(client *gitlabclient.Client) []toolutil.ActionSpec

ActionSpecs returns canonical specs for group epic actions.

func Delete

func Delete(ctx context.Context, client *gitlabclient.Client, input DeleteInput) error

Delete permanently removes an epic using the Work Items API.

func DeleteOutput

func DeleteOutput(ctx context.Context, client *gitlabclient.Client, input DeleteInput) (toolutil.DeleteOutput, error)

DeleteOutput deletes an epic and returns the canonical success message shape.

func FormatLinksMarkdown

func FormatLinksMarkdown(out LinksOutput) string

FormatLinksMarkdown renders child epics as a Markdown table.

func FormatListMarkdown

func FormatListMarkdown(out ListOutput) string

FormatListMarkdown renders a list of epics as a Markdown table.

func FormatOutputMarkdown

func FormatOutputMarkdown(e Output) string

FormatOutputMarkdown renders a single epic as a Markdown summary.

Types

type BasicUserOutput

type BasicUserOutput struct {
	ID          int64  `json:"id"`
	Username    string `json:"username"`
	Name        string `json:"name,omitempty"`
	State       string `json:"state,omitempty"`
	Locked      bool   `json:"locked,omitempty"`
	PublicEmail string `json:"public_email,omitempty"`
	AvatarURL   string `json:"avatar_url,omitempty"`
	WebURL      string `json:"web_url,omitempty"`
	CreatedAt   string `json:"created_at,omitempty"`
}

BasicUserOutput mirrors gl.BasicUser (and the compatible gl.EpicAuthor), the compact user object embedded on the epic author and assignees keys. Per the 1:1 audit policy (full nested objects, C-IMPORTS) the SDK sub-object is replicated here rather than imported from a sibling package to preserve the zero-import-cycle constraint. locked and public_email are on the REST author and on neither SDK type: a live GET /api/v4/groups/gitlab-org/epics on 2026-09-07 answered with all eight keys of GitLab's user entity, and gl.EpicAuthor declares six. They stay empty on the Work Items path, whose author fragment selects id, username, name, state, createdAt, avatarUrl and webUrl.

type ChildItem

type ChildItem struct {
	IID  int64  `json:"iid" jsonschema:"Internal ID (IID) of the child epic"`
	Path string `json:"path,omitempty" jsonschema:"Namespace full path of the child epic"`
}

ChildItem is a summary reference to a child epic in the hierarchy. It mirrors the shape the work items domain publishes for the same widget.

type CreateInput

type CreateInput struct {
	FullPath     string  `json:"full_path" jsonschema:"Full path of the group (e.g. my-group),required"`
	Title        string  `json:"title" jsonschema:"Epic title,required"`
	Description  string  `json:"description,omitempty" jsonschema:"Epic description (Markdown supported)"`
	Confidential *bool   `json:"confidential,omitempty" jsonschema:"Whether the epic is confidential"`
	Color        string  `json:"color,omitempty" jsonschema:"Epic color (hex format, e.g. #FF0000)"`
	StartDate    string  `json:"start_date,omitempty" jsonschema:"Start date (YYYY-MM-DD)"`
	DueDate      string  `json:"due_date,omitempty" jsonschema:"Due date (YYYY-MM-DD)"`
	CreatedAt    string  `` /* 210-byte string literal not displayed */
	CreateSource string  `` /* 159-byte string literal not displayed */
	AssigneeIDs  []int64 `json:"assignee_ids,omitempty" jsonschema:"Global IDs of assignees"`
	LabelIDs     []int64 `json:"label_ids,omitempty" jsonschema:"Global IDs of labels"`
	MilestoneID  *int64  `json:"milestone_id,omitempty" jsonschema:"Global ID of the milestone to assign to the epic"`
	// Making an epic a sub-epic is the Ultimate multi-level hierarchy
	// [ListInput.ParentIDs] cites.
	ParentID     *int64             `` /* 134-byte string literal not displayed */
	LinkedItems  *CreateLinkedItems `json:"linked_items,omitempty" tier:"ultimate" jsonschema:"Epics to link to the new one, all with the same link type"`
	Weight       *int64             `json:"weight,omitempty" tier:"premium" jsonschema:"Weight of the epic"`
	HealthStatus string             `json:"health_status,omitempty" tier:"ultimate" jsonschema:"Health status (onTrack/needsAttention/atRisk)"`
}

CreateInput defines parameters for creating a new epic.

type CreateLinkedItems

type CreateLinkedItems struct {
	WorkItemIDs []int64 `json:"work_item_ids" jsonschema:"Global IDs of epics or work items to link,required"`
	LinkType    string  `json:"link_type" jsonschema:"Link type: BLOCKS, BLOCKED_BY, or RELATED,required"`
}

CreateLinkedItems specifies epics to link while the epic is being created. It mirrors the work items domain's shape for the same widget so one API carries one call shape on both tools.

type DeleteInput

type DeleteInput struct {
	FullPath string `json:"full_path" jsonschema:"Full path of the group (e.g. my-group),required"`
	IID      int64  `json:"epic_iid" jsonschema:"Epic IID within the group,required"`
}

DeleteInput defines parameters for deleting an epic.

type GetInput

type GetInput struct {
	FullPath string `json:"full_path" jsonschema:"Full path of the group (e.g. my-group),required"`
	IID      int64  `json:"epic_iid" jsonschema:"Epic IID within the group,required"`
}

GetInput defines parameters for getting a single epic.

type GetLinksInput

type GetLinksInput struct {
	FullPath string `json:"full_path" jsonschema:"Full path of the group (e.g. my-group),required"`
	IID      int64  `json:"epic_iid" jsonschema:"Epic IID within the group,required"`
}

GetLinksInput defines parameters for listing child epics (REST).

type LinkedItem

type LinkedItem struct {
	IID      int64  `json:"iid"`
	LinkType string `json:"link_type"`
	Path     string `json:"path,omitempty"`
}

LinkedItem represents a linked work item summary.

type LinksItem

type LinksItem struct {
	ID           int64            `json:"id"`
	IID          int64            `json:"iid"`
	GroupID      int64            `json:"group_id,omitempty"`
	ParentID     int64            `json:"parent_id,omitempty"`
	ParentIID    int64            `json:"parent_iid,omitempty"`
	WorkItemID   int64            `json:"work_item_id,omitempty"`
	Title        string           `json:"title"`
	Description  string           `json:"description,omitempty"`
	State        string           `json:"state"`
	WebURL       string           `json:"web_url,omitempty"`
	WebEditURL   string           `json:"web_edit_url,omitempty"`
	Author       *BasicUserOutput `json:"author,omitempty"`
	Labels       []string         `json:"labels,omitempty"`
	Confidential bool             `json:"confidential,omitempty"`
	Color        string           `json:"color,omitempty"`
	TextColor    string           `json:"text_color,omitempty"`

	StartDate                    string `json:"start_date,omitempty"`
	StartDateIsFixed             bool   `json:"start_date_is_fixed,omitempty"`
	StartDateFixed               string `json:"start_date_fixed,omitempty"`
	StartDateFromMilestones      string `json:"start_date_from_milestones,omitempty"`
	StartDateFromInheritedSource string `json:"start_date_from_inherited_source,omitempty"`
	DueDate                      string `json:"due_date,omitempty"`
	DueDateIsFixed               bool   `json:"due_date_is_fixed,omitempty"`
	DueDateFixed                 string `json:"due_date_fixed,omitempty"`
	DueDateFromMilestones        string `json:"due_date_from_milestones,omitempty"`
	DueDateFromInheritedSource   string `json:"due_date_from_inherited_source,omitempty"`
	EndDate                      string `json:"end_date,omitempty"`

	Upvotes      int64                      `json:"upvotes,omitempty"`
	Downvotes    int64                      `json:"downvotes,omitempty"`
	References   *toolutil.ReferencesOutput `json:"references,omitempty"`
	Imported     bool                       `json:"imported,omitempty"`
	ImportedFrom string                     `json:"imported_from,omitempty"`
	Links        *ResourceLinksOutput       `json:"_links,omitempty"`
	CreatedAt    string                     `json:"created_at,omitempty"`
	UpdatedAt    string                     `json:"updated_at,omitempty"`
	ClosedAt     string                     `json:"closed_at,omitempty"`
}

LinksItem is the child-epic output for the GetLinks REST endpoint. Per the 1:1 audit policy it surfaces the whole REST epic: every field of gl.Epic GitLab sends, plus the twelve it sends that gl.Epic does not declare (see [epicAPI]). The author is a full nested object.

user_notes_count, url, subscribed and reference are absent for the reasons Output and [epicAPI] record.

There is no label_details either, and unlike the four above that is this endpoint rather than the entity: ee/lib/api/epic_links.rb declares GET /groups/:id/-/epics/:epic_iid/epics with id and epic_iid alone and presents the entity with no options, so the labels array is always titles here. with_labels_details belongs to the list endpoint, which fills Output's label_details; the dual-shape decoder stays because that path needs it.

type LinksOutput

type LinksOutput struct {
	toolutil.HintableOutput
	ChildEpics []LinksItem `json:"child_epics"`
}

LinksOutput holds child epics of a parent epic (REST-backed).

func GetLinks(ctx context.Context, client *gitlabclient.Client, input GetLinksInput) (LinksOutput, error)

GetLinks retrieves all child epics of a parent epic. This handler uses the REST API because client-go v2 does not yet expose a GraphQL query for work item children.

type ListInput

type ListInput struct {
	FullPath           string   `json:"full_path" jsonschema:"Full path of the group (e.g. my-group or my-group/sub-group),required"`
	State              string   `json:"state,omitempty" jsonschema:"Filter by state (opened/closed/all)"`
	Search             string   `json:"search,omitempty" jsonschema:"Search in title and description"`
	In                 []string `` /* 135-byte string literal not displayed */
	AuthorUsername     string   `json:"author_username,omitempty" jsonschema:"Filter by author username"`
	AuthorID           *int64   `` /* 231-byte string literal not displayed */
	AssigneeUsernames  []string `json:"assignee_usernames,omitempty" jsonschema:"Filter by assignee usernames"`
	AssigneeWildcardID string   `json:"assignee_wildcard_id,omitempty" jsonschema:"Filter by assignment state rather than by user: ANY, ME, or NONE"`
	IIDs               []string `json:"iids,omitempty" jsonschema:"Fetch only these epic IIDs, each as a decimal string (e.g. [\"12\", \"34\"])"`
	IDs                []string `` /* 169-byte string literal not displayed */
	// An epic under another epic is the multi-level hierarchy
	// docs.gitlab.com/user/work_items/child_items/ puts at Tier: Ultimate
	// under "Work with multi-level hierarchies", so a filter that selects by
	// one names something no lower tier can have.
	ParentIDs           []string `` /* 154-byte string literal not displayed */
	LabelName           []string `json:"label_name,omitempty" jsonschema:"Filter by label names"`
	MilestoneTitle      []string `json:"milestone_title,omitempty" jsonschema:"Filter by the titles of the milestones assigned to the epic"`
	MilestoneWildcardID string   `` /* 136-byte string literal not displayed */
	MyReactionEmoji     string   `` /* 131-byte string literal not displayed */
	Confidential        *bool    `json:"confidential,omitempty" jsonschema:"Filter by confidentiality"`
	Subscribed          string   `` /* 138-byte string literal not displayed */
	HealthStatusFilter  string   `` /* 154-byte string literal not displayed */
	Weight              string   `json:"weight,omitempty" tier:"premium" jsonschema:"Filter by weight, as a decimal string (e.g. \"5\")"`
	WeightWildcardID    string   `` /* 135-byte string literal not displayed */
	OrderBy             string   `json:"order_by,omitempty" jsonschema:"Order epics by field (created_at, updated_at, title). Defaults to created_at"`
	Sort                string   `json:"sort,omitempty" jsonschema:"Sort order (asc or desc). Defaults to desc"`
	CreatedAfter        string   `json:"created_after,omitempty" jsonschema:"Return epics created after date (ISO 8601, e.g. 2025-01-01T00:00:00Z)"`
	CreatedBefore       string   `json:"created_before,omitempty" jsonschema:"Return epics created before date (ISO 8601, e.g. 2025-12-31T23:59:59Z)"`
	UpdatedAfter        string   `json:"updated_after,omitempty" jsonschema:"Return epics updated on or after date (ISO 8601, e.g. 2025-01-01T00:00:00Z)"`
	UpdatedBefore       string   `json:"updated_before,omitempty" jsonschema:"Return epics updated on or before date (ISO 8601, e.g. 2025-12-31T23:59:59Z)"`
	ClosedAfter         string   `json:"closed_after,omitempty" jsonschema:"Return epics closed on or after date (ISO 8601, e.g. 2025-01-01T00:00:00Z)"`
	ClosedBefore        string   `json:"closed_before,omitempty" jsonschema:"Return epics closed on or before date (ISO 8601, e.g. 2025-12-31T23:59:59Z)"`
	DueAfter            string   `json:"due_after,omitempty" jsonschema:"Return epics due on or after date (ISO 8601, e.g. 2025-01-01T00:00:00Z)"`
	DueBefore           string   `json:"due_before,omitempty" jsonschema:"Return epics due on or before date (ISO 8601, e.g. 2025-12-31T23:59:59Z)"`
	WithLabelsDetails   *bool    `` /* 266-byte string literal not displayed */
	IncludeAncestors    *bool    `json:"include_ancestors,omitempty" jsonschema:"Include epics from ancestor groups"`
	IncludeDescendants  *bool    `json:"include_descendants,omitempty" jsonschema:"Include epics from descendant groups"`
	toolutil.GraphQLCursorPaginationInput
	toolutil.PaginationInput
	toolutil.KeysetPaginationInput
}

ListInput defines parameters for listing group epics.

Two GitLab APIs answer this action and the filters decide which: a request naming only what the REST epics endpoint accepts is served by it, and anything below that only Namespace.workItems can express routes the whole request through the Work Items GraphQL query. [workItemsPathReason] is the one place that decision is made, so a filter added here and forgotten there is silently dropped rather than refused.

The page request follows the same split, and [validateListPagination] enforces it: page, per_page, pagination and page_token are the REST endpoint's, first, after, last and before are the query's, and asking for one beside a filter that routes to the other is refused rather than answered with a page nobody asked for.

type ListOutput

type ListOutput struct {
	toolutil.HintableOutput
	Epics            []Output                          `json:"epics"`
	Pagination       *toolutil.GraphQLPaginationOutput `json:"pagination,omitempty"`
	OffsetPagination *toolutil.PaginationOutput        `json:"offset_pagination,omitempty"`
}

ListOutput holds a page of epics plus the pagination block of whichever API answered.

The two blocks are mutually exclusive and both are omitted when empty, so the one a response carries is what tells a caller which path served it: the REST epics endpoint pages by number and reports X-Next-Page, while the Work Items query pages by cursor and reports pageInfo. Publishing one shape for both would mean answering a full REST page with has_next_page false, which is how a caller stops paging one item short of the rest of the list.

func List

func List(ctx context.Context, client *gitlabclient.Client, input ListInput) (ListOutput, error)

List retrieves epics for a group using the Work Items API with type filter.

type Output

type Output struct {
	toolutil.HintableOutput
	ID           int64                          `json:"id"`
	IID          int64                          `json:"iid"`
	Type         string                         `json:"type"`
	State        string                         `json:"state"`
	Title        string                         `json:"title"`
	Description  string                         `json:"description,omitempty"`
	WebURL       string                         `json:"web_url,omitempty"`
	WebEditURL   string                         `json:"web_edit_url,omitempty"`
	GroupID      int64                          `json:"group_id,omitempty"`
	ParentID     int64                          `json:"parent_id,omitempty"`
	WorkItemID   int64                          `json:"work_item_id,omitempty"`
	Author       *BasicUserOutput               `json:"author,omitempty"`
	Assignees    []*BasicUserOutput             `json:"assignees,omitempty"`
	Labels       []string                       `json:"labels,omitempty"`
	LabelDetails []*toolutil.LabelDetailsOutput `json:"label_details,omitempty"`
	LinkedItems  []LinkedItem                   `json:"linked_items,omitempty" tier:"ultimate"`
	Children     []ChildItem                    `json:"children,omitempty"`
	Confidential bool                           `json:"confidential,omitempty"`
	Color        string                         `json:"color,omitempty"`
	TextColor    string                         `json:"text_color,omitempty"`

	StartDate                    string `json:"start_date,omitempty"`
	StartDateIsFixed             bool   `json:"start_date_is_fixed,omitempty"`
	StartDateFixed               string `json:"start_date_fixed,omitempty"`
	StartDateFromMilestones      string `json:"start_date_from_milestones,omitempty"`
	StartDateFromInheritedSource string `json:"start_date_from_inherited_source,omitempty"`
	DueDate                      string `json:"due_date,omitempty"`
	DueDateIsFixed               bool   `json:"due_date_is_fixed,omitempty"`
	DueDateFixed                 string `json:"due_date_fixed,omitempty"`
	DueDateFromMilestones        string `json:"due_date_from_milestones,omitempty"`
	DueDateFromInheritedSource   string `json:"due_date_from_inherited_source,omitempty"`
	EndDate                      string `json:"end_date,omitempty"`

	HealthStatus string                     `json:"health_status,omitempty" tier:"ultimate"`
	Weight       *int64                     `json:"weight,omitempty" tier:"premium"`
	MilestoneID  *int64                     `json:"milestone_id,omitempty"`
	Upvotes      int64                      `json:"upvotes,omitempty"`
	Downvotes    int64                      `json:"downvotes,omitempty"`
	References   *toolutil.ReferencesOutput `json:"references,omitempty"`
	Imported     bool                       `json:"imported,omitempty"`
	ImportedFrom string                     `json:"imported_from,omitempty"`
	Links        *ResourceLinksOutput       `json:"_links,omitempty"`
	ParentIID    int64                      `json:"parent_iid,omitempty"`
	ParentPath   string                     `json:"parent_path,omitempty"`
	CreatedAt    string                     `json:"created_at,omitempty"`
	UpdatedAt    string                     `json:"updated_at,omitempty"`
	ClosedAt     string                     `json:"closed_at,omitempty"`
}

Output represents a single epic (backed by a Work Item of type Epic, or by the REST epic for the list and child-epic links endpoints). Per the 1:1 audit policy it carries the union of what each source exposes; fields absent on a given source stay at their zero value.

There is no status and no iteration_id. Both were always null: an Epic work item carries neither the STATUS nor the ITERATION widget, per the widget list gitlab.com answered on 2026-09-07 for namespace(fullPath: "gitlab-org") { workItemTypes { nodes { name widgetDefinitions { type } } } }, and neither is a field of the REST epic. There is no user_notes_count and no url either: gl.Epic declares both, and GitLab's OpenAPI record, its doc/api/epics.md example bodies and a live gitlab.com response agree that no epic endpoint sends either one.

There is no subscribed and no reference for the reason [epicAPI] records: the entity renders each one only under an option no route this package calls passes.

func Create

func Create(ctx context.Context, client *gitlabclient.Client, input CreateInput) (Output, error)

Create creates a new epic using the Work Items API with the Epic type.

func Get

func Get(ctx context.Context, client *gitlabclient.Client, input GetInput) (Output, error)

Get retrieves a single epic by its IID using the Work Items API.

func Update

func Update(ctx context.Context, client *gitlabclient.Client, input UpdateInput) (Output, error)

Update modifies an existing epic using the Work Items API.

type ResourceLinksOutput

type ResourceLinksOutput struct {
	Self       string `json:"self,omitempty"`
	EpicIssues string `json:"epic_issues,omitempty"`
	Group      string `json:"group,omitempty"`
	Parent     string `json:"parent,omitempty"`
}

ResourceLinksOutput mirrors the `_links` object GitLab renders on a REST epic: the API URLs of the epic itself, its issues, its group and its parent. gl.Epic declares none of it.

type UpdateInput

type UpdateInput struct {
	FullPath    string `json:"full_path" jsonschema:"Full path of the group (e.g. my-group),required"`
	IID         int64  `json:"epic_iid" jsonschema:"Epic IID within the group,required"`
	Title       string `json:"title,omitempty" jsonschema:"Updated epic title"`
	Description string `json:"description,omitempty" jsonschema:"Updated description (Markdown supported)"`
	StateEvent  string `json:"state_event,omitempty" jsonschema:"State event: CLOSE or REOPEN"`
	// Reparenting reaches the same hierarchy creating one does, so it carries
	// the tier [ListInput.ParentIDs] cites.
	ParentID       *int64  `json:"parent_id,omitempty" tier:"ultimate" jsonschema:"Global ID of the parent epic work item"`
	Color          string  `json:"color,omitempty" jsonschema:"Epic color (hex format)"`
	StartDate      string  `json:"start_date,omitempty" jsonschema:"Start date (YYYY-MM-DD)"`
	DueDate        string  `json:"due_date,omitempty" jsonschema:"Due date (YYYY-MM-DD)"`
	AddLabelIDs    []int64 `json:"add_label_ids,omitempty" jsonschema:"Global IDs of labels to add"`
	RemoveLabelIDs []int64 `json:"remove_label_ids,omitempty" jsonschema:"Global IDs of labels to remove"`
	AssigneeIDs    []int64 `json:"assignee_ids,omitempty" jsonschema:"Global IDs of assignees (empty array to remove all)"`
	MilestoneID    *int64  `json:"milestone_id,omitempty" jsonschema:"Global ID of the milestone to assign to the epic"`
	Weight         *int64  `json:"weight,omitempty" tier:"premium" jsonschema:"Weight of the epic"`
	HealthStatus   string  `json:"health_status,omitempty" tier:"ultimate" jsonschema:"Health status (onTrack/needsAttention/atRisk)"`
}

UpdateInput defines parameters for updating an existing epic.

There is no status here. An Epic work item carries no STATUS widget, so the mutation refuses the field: the widget list gitlab.com answered on 2026-09-07 for namespace(fullPath: "gitlab-org") { workItemTypes { nodes { name widgetDefinitions { type } } } } gives Epic AI_SESSION, ASSIGNEES, AWARD_EMOJI, COLOR, CURRENT_USER_TODOS, CUSTOM_FIELDS, DESCRIPTION, HEALTH_STATUS, HIERARCHY, LABELS, LINKED_ITEMS, MILESTONE, NOTES, NOTIFICATIONS, PARTICIPANTS, START_AND_DUE_DATE, TIME_TRACKING, VERIFICATION_STATUS and WEIGHT, and neither STATUS nor ITERATION nor CRM_CONTACTS. Issue and Task carry all three, which is where the field was copied from.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL