Documentation
¶
Overview ¶
Package jira — Board service for /rest/agile/1.0.
Wire shapes and field semantics live in the boards HTTP contract and data-model.md. This file holds the Board struct, BoardScope helper, BoardService interface, and the drain/resolve implementations.
Typed errors for the client's transport-layer mutation guards. Both fire in Client.Do before any bytes reach the wire, so every command inherits them without per-command boilerplate. They are TYPED (not synthetic *APIError values) so the error mapper can recognize them with errors.As and emit their own stable codes instead of the validation_failed catch-all.
Index ¶
- Constants
- Variables
- func Bool(v bool) *bool
- func CodeForStatus(status int) errtax.Code
- func DefaultBoardMissingMessage(profile, name string) string
- func DefaultIssueListFields() []string
- func DrainSearch(ctx context.Context, svc SearchService, req *SearchRequest, opts DrainOptions) ([]*Issue, DrainInfo, error)
- func Int(v int) *int
- func ListIssuesPage(ctx context.Context, svc IssuePageLister, opts *IssueListOptions, ...) ([]*Issue, PageCursor, error)
- func MaxAttachmentUploadBytes() int64
- func NormalizeBoardName(name string) string
- func ParseDuration(input string, workdaySeconds int) (int, error)
- func ParseStarted(input string, now time.Time, loc *time.Location) (string, error)
- func RESTPath(parts ...string) string
- func SortIssueLinks(links []IssueLinkView)
- func StatusCounts(issues []*Issue) map[string]int
- func String(v string) *string
- type APIError
- type AmbiguousBoardError
- type AmbiguousUserError
- type Attachment
- type AttachmentService
- type AvatarURLs
- type Board
- type BoardDrainOptions
- type BoardDrainResult
- type BoardScope
- type BoardService
- type BoardsCacheFile
- type Client
- func (c *Client) BaseURL() *url.URL
- func (c *Client) Do(req *http.Request, out any) (*Response, error)
- func (c *Client) NewRawRequest(ctx context.Context, method, path string, body io.Reader) (*http.Request, error)
- func (c *Client) NewRequest(ctx context.Context, method, path string, body any) (*http.Request, error)
- func (c *Client) SignRequest(req *http.Request)
- type Comment
- type CommentAddRequest
- type CommentBody
- type CommentDrainOptions
- type CommentDrainResult
- type CommentPage
- type CommentService
- type Component
- type CurrentUser
- type DrainInfo
- type DrainOptions
- type DryRunBlockedError
- type EditMeta
- type EditMetaAllowedValue
- type EditMetaField
- type EditMetaFieldSchema
- type Epic
- type EpicService
- type ErrorType
- type FieldSchema
- type FileSource
- type Issue
- type IssueCloneRequest
- type IssueCreateRequest
- type IssueDeleteOptions
- type IssueFields
- type IssueGetOptions
- type IssueLink
- type IssueLinkRequest
- type IssueLinkService
- type IssueLinkType
- type IssueLinkTypeService
- type IssueLinkView
- type IssueListOptions
- type IssueMoveRequest
- type IssuePageLister
- type IssueRef
- type IssueService
- type IssueType
- type IssueTypeUnknownError
- type IssueUpdateRequest
- type JQLField
- type JQLFunction
- type JQLReference
- type JQLService
- type JQLSuggestion
- type LabelService
- type ListCommentsOptions
- type ListOptions
- type LossyCommentWarning
- type Option
- func WithBaseURL(raw string) Option
- func WithBasicAuth(email, token string) Option
- func WithDebug(debug bool) Option
- func WithDryRun(dryRun bool) Option
- func WithHTTPClient(h *http.Client) Option
- func WithHTTPTimeout(timeout time.Duration) Option
- func WithMaxRetryWait(d time.Duration) Option
- func WithRateObserver(fn RateObserver) Option
- func WithReadOnly(readOnly bool) Option
- type PageCursor
- type ParsedQuery
- type PermissionEntry
- type PermissionsResponse
- type Priority
- type ProjectFieldSchema
- type ProjectSchemaCache
- func (c *ProjectSchemaCache) Get(profile, projectKey, issueType string) (*ProjectFieldSchema, bool)
- func (c *ProjectSchemaCache) InvalidateAll()
- func (c *ProjectSchemaCache) InvalidateProfile(profile string)
- func (c *ProjectSchemaCache) Set(profile, projectKey, issueType string, schema *ProjectFieldSchema)
- type ProjectService
- type ProjectSummary
- type QueryOptions
- type Rate
- type RateObserver
- type ReadOnlyError
- type RemoteLinkRequest
- type Response
- type SearchRequest
- type SearchResult
- type SearchService
- type Status
- type StatusCategory
- type Transition
- type TransitionRequest
- type User
- type UserService
- type Version
- type Visibility
- type VisibilityChange
- type VisibilityFlags
- type VisibilityMode
- type WatcherService
- type WatchersResponse
- type Worklog
- type WorklogAddRequest
- type WorklogPage
- type WorklogService
Constants ¶
const ( ErrorTypeAuth = errtax.TypeAuth ErrorTypeNotFound = errtax.TypeNotFound ErrorTypeValidation = errtax.TypeValidation ErrorTypeRateLimit = errtax.TypeRateLimit ErrorTypeServer = errtax.TypeServer )
The taxonomy types, re-exported under jira's historical names.
const DefaultWorkdaySeconds = 28800
Variables ¶
var ErrBoardNotFound = errors.New("board not found in cache")
ErrBoardNotFound is returned by ResolveOne when the cache contains no board matching the supplied name (case-insensitive exact match). CLI maps to exit 2 (not_found).
var ErrUserNotFound = errors.New("user not found")
ErrUserNotFound is returned by ResolveUser when /user/search yields zero matches for the supplied query. The CLI surface maps this to exit 2 per the constitution error contract (resource-not-found).
var IssueListFields = append([]string(nil), defaultIssueListFields...)
Functions ¶
func CodeForStatus ¶ added in v0.10.0
CodeForStatus derives the taxonomy code for an HTTP status. It moves in lockstep with ClassifyStatus and the errtax registry: a status added here MUST carry a registry row (the taxonomy guard enumerates the pairing) — a jira_* code with no hint is the exact gap that guard exists to catch. An unmapped status falls back to its classified type's catch-all code.
func DefaultBoardMissingMessage ¶
DefaultBoardMissingMessage returns the pinned wording the CLI emits when `default_board` references a name that does not resolve in the boards cache. Both arguments are interpolated verbatim — `profile` is the active profile name (e.g. "default" / "work"), `name` is the configured default_board value. Wording is pinned so the contract test can assert on it; any change here is a spec change too.
func DefaultIssueListFields ¶
func DefaultIssueListFields() []string
func DrainSearch ¶
func DrainSearch(ctx context.Context, svc SearchService, req *SearchRequest, opts DrainOptions) ([]*Issue, DrainInfo, error)
DrainSearch pulls every page from a SearchService.JQL call, respecting the configured bounds. Unbounded=true bypasses the bounds entirely; this is what the CLI's --unbounded flag wires.
func ListIssuesPage ¶ added in v0.6.0
func ListIssuesPage(ctx context.Context, svc IssuePageLister, opts *IssueListOptions, cur PageCursor) ([]*Issue, PageCursor, error)
ListIssuesPage runs one page of an issue search and returns the cursor for the next call. Pass the zero PageCursor for the first page; a returned cursor with More() == false means the result set is complete.
func MaxAttachmentUploadBytes ¶
func MaxAttachmentUploadBytes() int64
func NormalizeBoardName ¶
NormalizeBoardName strips leading/trailing whitespace from board names while preserving internal whitespace and Unicode verbatim. Called by the cache prime path before persisting each board, so the cache file holds clean names while the wire shape is left untouched.
func ParseStarted ¶ added in v0.10.0
ParseStarted parses a worklog start timestamp and normalizes it to the strict form Jira requires. Accepted forms: ISO-8601 with an explicit offset (kept as given), a naive date/time without one (interpreted in loc), and the relative keywords `now`, `yesterday`, and `<duration> ago` (e.g. `2h ago`), all resolved against now. Anything else is an error, so an unparseable value fails local validation instead of a Jira submit.
func SortIssueLinks ¶
func SortIssueLinks(links []IssueLinkView)
func StatusCounts ¶
Types ¶
type APIError ¶
type APIError struct {
StatusCode int
Type ErrorType
Message string
// ErrorMessages mirrors the Jira ErrorCollection.errorMessages array
// (general, non-field-scoped human messages). Nil when the body
// carried none. Never used as a branch target — wording is not
// contractual.
ErrorMessages []string
// FieldErrors mirrors the Jira ErrorCollection.errors map
// (field-name -> single human message). Nil when the body carried
// none.
FieldErrors map[string]string
// UpstreamStatus is the integer ErrorCollection.status field when the
// body carried one. Zero when absent.
UpstreamStatus int
// RetryAfterSeconds is the Retry-After header value on a 429
// response. Zero when the header was absent — callers fall back to
// exponential backoff with jitter.
RetryAfterSeconds int
// RateLimitRemaining is the X-RateLimit-Remaining header value when
// Jira sends it. Zero can mean either absent or exhausted; callers use
// it as diagnostic context only.
RateLimitRemaining int
// UpstreamRequestID is Jira's own trace id for the failed exchange,
// read from the Atl-Traceid (preferred) or X-ARequestId response
// header. It is the value Atlassian support can correlate a failure
// against; the envelope's request_id is a local value with no
// server-side meaning. Empty when neither header was present.
UpstreamRequestID string
// Cause preserves transport/body/JSON failures for errors.Is/As while
// keeping the public APIError message stable.
Cause error
}
APIError is the typed error returned for any non-2xx Jira REST response or transport failure. Beyond the HTTP status and a display message it preserves the schema-backed fields of Jira Cloud's ErrorCollection body (errorMessages[], errors map, status) and the rate-limit Retry-After header, all as optional metadata. Jira exposes no stable machine error code, so APIError carries none — callers derive a normalized code from the HTTP status.
type AmbiguousBoardError ¶
AmbiguousBoardError is returned by ResolveOne when 2+ boards match the supplied name (e.g. same name in different projects). The cmd layer renders Candidates in the disambiguation envelope. CLI maps to exit 3 (validation).
func (*AmbiguousBoardError) Error ¶
func (e *AmbiguousBoardError) Error() string
type AmbiguousUserError ¶
AmbiguousUserError is returned by ResolveUser when /user/search yields 2+ matches and we refuse to pick a winner. Candidates carries every returned User so the CLI can render the disambiguation envelope.
func (*AmbiguousUserError) CandidateRows ¶ added in v0.10.0
func (e *AmbiguousUserError) CandidateRows() []map[string]any
CandidateRows flattens the /user/search candidates into envelope candidate rows so an agent can pick a winner without re-querying. The method is CandidateRows (not Candidates) because the struct carries a Candidates field. Nil candidates and absent fields are skipped; the returned slice is never nil.
func (*AmbiguousUserError) Code ¶ added in v0.10.0
func (e *AmbiguousUserError) Code() errtax.Code
Code classifies the failure under the taxonomy's user_ambiguous code.
func (*AmbiguousUserError) Error ¶
func (e *AmbiguousUserError) Error() string
type Attachment ¶
type Attachment struct {
ID *string `json:"id,omitempty"`
Self *string `json:"self,omitempty"`
Filename *string `json:"filename,omitempty"`
MimeType *string `json:"mimeType,omitempty"`
Size *int64 `json:"size,omitempty"`
Author *User `json:"author,omitempty"`
Created *string `json:"created,omitempty"`
// Content is Atlassian's signed download URL. The CLI doesn't expose
// this in the --json envelope (the download command goes through
// /attachment/content/{id} instead), but it's preserved for callers
// that want to embed the URL directly.
Content *string `json:"content,omitempty"`
}
Attachment is a file attached to a Jira issue. Pointer-typed nullable fields follow the rest of pkg/jira (Issue, Comment, …) so JSON round-tripping preserves "field absent" vs "field present and empty".
Wire shape: GET /rest/api/3/issue/{key}?fields=attachment returns `fields.attachment[]` populated with this shape; POST .../attachments returns an array of these directly.
type AttachmentService ¶
type AttachmentService interface {
List(ctx context.Context, key string) ([]Attachment, *Response, error)
Add(ctx context.Context, key string, files []FileSource) ([]Attachment, *Response, error)
Delete(ctx context.Context, attachmentID string) (*Response, error)
Download(ctx context.Context, attachmentID string) (io.ReadCloser, *Response, error)
}
AttachmentService exposes Atlassian's attachment endpoints. Mutation methods follow the same pointer-based response convention as IssueService so callers can distinguish "no field" from "empty field".
Design notes:
- Add uses multipart/form-data with X-Atlassian-Token: no-check and form field name "file" — both required by Atlassian.
- Delete is global by attachment id (Atlassian's endpoint is /attachment/{id}, not nested under the issue).
- Download streams via io.Copy in the caller; this method returns the raw response body.
func NewAttachmentService ¶
func NewAttachmentService(client *Client) AttachmentService
NewAttachmentService binds an AttachmentService to the given client.
type AvatarURLs ¶
type AvatarURLs struct {
Size16 string `json:"16x16,omitempty"`
Size24 string `json:"24x24,omitempty"`
Size32 string `json:"32x32,omitempty"`
Size48 string `json:"48x48,omitempty"`
}
AvatarURLs maps Jira's standard avatar size keys to URLs.
type Board ¶
type Board struct {
ID *int `json:"id,omitempty"`
Self *string `json:"self,omitempty"`
Name *string `json:"name,omitempty"`
Type *string `json:"type,omitempty"`
ProjectKeys []string `json:"project_keys"`
}
Board is a Jira agile board fetched via /rest/agile/1.0/board and (for the project list) /rest/agile/1.0/board/{id}/project. Pointer-typed nullable fields mirror Issue/Comment/Attachment: distinguishes "field absent" from "present and zero".
func DecodeBoardsCache ¶
DecodeBoardsCache parses the on-disk boards-cache payload. The earlier resolver tests stored the boards as a bare JSON array; the 003 prime path persists the BoardsCache envelope. This helper accepts both so the migration is invisible to callers.
type BoardDrainOptions ¶
BoardDrainOptions configures BoardService.ListAll. Defaults: PageSize 50, MaxPages 100, MaxResults 10 000. Set Unbounded to disable both bounds.
type BoardDrainResult ¶
type BoardDrainResult struct {
Boards []*Board
LastResp *Response
PagesFetched int
RateLimitHit *APIError
Truncated bool
TruncatedReason string
}
BoardDrainResult carries the outcome of ListAll: every board fetched, the rate-limit APIError that aborted the walk if one fired (partial-success), and the truncation reason if a bound stopped the walk short of isLast.
type BoardScope ¶
BoardScope is what BoardService.ResolveOne returns: the resolved board plus the precedence path that selected it (one of "flag", "default_board", "none").
func (BoardScope) JQLClause ¶
func (s BoardScope) JQLClause() (string, bool)
JQLClause emits `project in (P1, P2, ...)` from the board's cached project keys. The bool result is the canonical "did this scope produce a clause?" signal — every consumer that needs to branch on emission (envelope's `applied` flag, JQL prepender) reads it from here so no caller has to re-derive `len(ProjectKeys) > 0`.
type BoardService ¶
type BoardService interface {
List(ctx context.Context, opts *ListOptions) ([]Board, *Response, error)
ListAll(ctx context.Context, opts BoardDrainOptions) (BoardDrainResult, error)
ProjectsForBoard(ctx context.Context, boardID int) ([]string, *Response, error)
ResolveOne(ctx context.Context, profile, name string) (BoardScope, error)
}
BoardService groups the agile board endpoints used by the boards cache primer, the `boards list` command, and the --board resolver.
func NewBoardService ¶
func NewBoardService(client *Client) BoardService
NewBoardService binds a BoardService to the supplied client.
type BoardsCacheFile ¶
type BoardsCacheFile struct {
FetchedAt string `json:"fetched_at"`
TTLSeconds int `json:"ttl_seconds"`
Truncated bool `json:"truncated"`
TruncatedReason string `json:"truncated_reason"`
PagesFetched int `json:"pages_fetched,omitempty"`
RetryAfterSeconds int `json:"retry_after_seconds,omitempty"`
Items []Board `json:"items"`
}
BoardsCacheFile is the on-disk envelope persisted under `<cache-dir>/jira-cli/<profile>/boards.json` (the OS-native cache dir: `${XDG_CACHE_HOME:-~/.cache}` on Unix, `%LocalAppData%\cache` on Windows) per data-model.md > BoardsCache. Carried inside the generic cache.Entry's Data field so the existing internal/cache machinery (atomic write, 0600 perms, TTL freshness) round-trips it unchanged.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
func NewClientE ¶
func (*Client) BaseURL ¶
BaseURL exposes the resolved base URL so services that need to build fully-qualified URLs without going through NewRequest (multipart, streaming download) can ResolveReference against the same root the rest of the package uses.
func (*Client) NewRawRequest ¶
func (*Client) NewRequest ¶
func (*Client) SignRequest ¶
SignRequest applies the active profile's Jira Cloud auth to req: HTTP Basic with the account email and API token. Services that build requests outside NewRequest (multipart upload, raw-body posts, streaming download) MUST call this rather than re-implementing the basic-auth selection inline so the auth surface stays consistent.
type Comment ¶
type Comment struct {
ID *string `json:"id,omitempty"`
Self *string `json:"self,omitempty"`
Body *adf.Document `json:"body,omitempty"`
Author *User `json:"author,omitempty"`
UpdateAuthor *User `json:"updateAuthor,omitempty"`
Created *string `json:"created,omitempty"`
Updated *string `json:"updated,omitempty"`
Visibility *Visibility `json:"visibility,omitempty"`
}
type CommentAddRequest ¶
type CommentBody ¶
type CommentBody struct {
ADF *adf.Document
// DryRun short-circuits the HTTP call. The service returns a synthetic
// {ID: "DRY-RUN"} Comment so callers can keep their envelope flow.
DryRun bool
}
CommentBody carries the comment payload. Exactly one of ADF or Markdown must be supplied; the cmd-layer binding converts Markdown → ADF before constructing the body.
type CommentDrainOptions ¶
CommentDrainOptions configures CommentService.ListAll. Mirrors DrainOptions on the search side so consumer-side opt-in walks have consistent runaway protection. Defaults: PageSize 50, MaxPages 100, MaxResults 10_000. Set Unbounded to disable both bounds (the CLI requires --unbounded; the TUI never sets it).
type CommentDrainResult ¶
type CommentDrainResult struct {
Comments []*Comment
LastResp *Response
PagesFetched int
RateLimitHit *APIError
Truncated bool
TruncatedReason string
}
CommentDrainResult carries the outcome of ListAll: every comment fetched, the final page's response (for envelope pagination metadata), and the rate-limit APIError that aborted the walk if one fired (partial-success semantics — exit 0 with a structured warning). Truncated reports whether one of the configured bounds stopped the walk short of isLast.
type CommentPage ¶
type CommentPage struct {
Comments []*Comment `json:"comments,omitempty"`
}
type CommentService ¶
type CommentService interface {
List(ctx context.Context, key string, opts *ListCommentsOptions) ([]*Comment, *Response, error)
ListAll(ctx context.Context, key string, opts CommentDrainOptions) (CommentDrainResult, error)
Add(ctx context.Context, key string, body *CommentBody) (*Comment, *Response, error)
AddWithVisibility(ctx context.Context, key string, body *CommentBody, vis VisibilityChange) (*Comment, *Response, error)
Edit(ctx context.Context, key, commentID string, body *CommentBody, vis VisibilityChange) (*Comment, *Response, error)
Delete(ctx context.Context, key, commentID string) (*Response, error)
}
CommentService groups the Jira-issue comment endpoints. Split out of IssueService per plan.md "Service Architecture" — comments deserve a service of their own now that the lifecycle covers list / add / edit / delete instead of just add.
All methods use the underlying *Client's auth, debug, retry, and rate-limit hooks. Pagination on List follows the same ListOptions shape the rest of pkg/jira already exposes.
func NewCommentService ¶
func NewCommentService(client *Client) CommentService
NewCommentService constructs a CommentService bound to the given client.
type CurrentUser ¶
type CurrentUser struct {
Self string `json:"self,omitempty"`
AccountID string `json:"accountId,omitempty"`
AccountType string `json:"accountType,omitempty"`
EmailAddress string `json:"emailAddress,omitempty"`
DisplayName string `json:"displayName,omitempty"`
Active bool `json:"active,omitempty"`
TimeZone string `json:"timeZone,omitempty"`
Locale string `json:"locale,omitempty"`
AvatarURLs AvatarURLs `json:"avatarUrls,omitempty"`
}
User identity returned by /rest/api/3/myself. Subset of the full response — only the fields we use for assign-to-me, profile bootstrap and `whoami`.
type DrainInfo ¶
type DrainInfo struct {
PagesFetched int
Truncated bool
TruncatedReason string
// NextPageToken resumes the walk after a truncation that fell on a
// page boundary. Empty when the walk completed, or when the cut fell
// mid-page (a max_results stop inside a page) — resuming from the
// page token there would silently skip the tail of the cut page.
NextPageToken string
}
DrainInfo describes how DrainSearch terminated. Truncated reports whether a bound stopped consumption short of isLast.
type DrainOptions ¶
DrainOptions configures DrainSearch — the bounded `--all` consumer.
Defaults: max-pages 100, max-results-total 10,000. Override either with explicit positive values; set Unbounded to disable both (CLI requires --unbounded; the TUI never sets it).
type DryRunBlockedError ¶ added in v0.10.0
DryRunBlockedError reports a mutation that reached the transport under --dry-run. Dry-run commands stop before submitting; a request getting this far is caught by the transport guard, not user error.
func (*DryRunBlockedError) Code ¶ added in v0.10.0
func (e *DryRunBlockedError) Code() errtax.Code
Code classifies a dry-run transport block under dry_run_blocked.
func (*DryRunBlockedError) Error ¶ added in v0.10.0
func (e *DryRunBlockedError) Error() string
type EditMeta ¶ added in v0.10.0
type EditMeta struct {
Fields map[string]EditMetaField `json:"fields,omitempty"`
}
EditMeta is the issue edit-screen metadata Jira returns under expand=editmeta on GET issue: a map keyed by field id of what the edit screen accepts. The struct keeps Jira's wire shape (camelCase keys) but trims each field to the agent-useful subset — unknown keys are dropped on unmarshal rather than carried opaquely.
type EditMetaAllowedValue ¶ added in v0.10.0
type EditMetaAllowedValue struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Value string `json:"value,omitempty"`
}
EditMetaAllowedValue is one allowed value of an edit-screen field. Jira mixes shapes per field type — options carry `value`, versions/components/ priorities carry `name` — so both are kept and the blank one omitted.
type EditMetaField ¶ added in v0.10.0
type EditMetaField struct {
Name string `json:"name,omitempty"`
Key string `json:"key,omitempty"`
Required bool `json:"required"`
// Operations lists the verbs the field accepts in an update block
// (set, add, remove, edit, copy).
Operations []string `json:"operations,omitempty"`
Schema *EditMetaFieldSchema `json:"schema,omitempty"`
// AllowedValues enumerates the values Jira accepts for the field,
// trimmed to their identifying keys. Absent when the field is
// free-form.
AllowedValues []EditMetaAllowedValue `json:"allowedValues,omitempty"`
}
EditMetaField describes one editable field on an issue's edit screen.
type EditMetaFieldSchema ¶ added in v0.10.0
type EditMetaFieldSchema struct {
Type string `json:"type,omitempty"`
// Items is the element type when Type is "array".
Items string `json:"items,omitempty"`
// Custom is the fully-qualified custom-field type identifier; empty
// for system fields.
Custom string `json:"custom,omitempty"`
}
EditMetaFieldSchema is the type identity of an editable field.
type EpicService ¶
type EpicService interface {
List(context.Context, *ListOptions) ([]*Issue, *Response, error)
Get(context.Context, string, *IssueGetOptions) (*Issue, *Response, error)
AddIssue(context.Context, string, string) (*Response, error)
RemoveIssue(context.Context, string) (*Response, error)
IssuesInEpic(context.Context, string) ([]*Issue, *Response, error)
}
func NewEpicService ¶
func NewEpicService(client *Client) EpicService
type ErrorType ¶
ErrorType aliases the shared taxonomy type so jira's status classification and the envelope mapper speak one enum.
func ClassifyStatus ¶ added in v0.10.0
ClassifyStatus reports the error type for an HTTP status. It stays an independent status→type switch (never derived from CodeForStatus plus the registry) so the taxonomy lockstep test compares two genuine sources.
type FieldSchema ¶
type FieldSchema struct {
ID string `json:"id"`
Name string `json:"name"`
Required bool `json:"required"`
Type string `json:"type"`
// Custom is the schema.custom token Jira reports for a custom
// field — the trailing identifier of
// com.atlassian.jira.plugin.system.customfieldtypes:* (e.g.
// "select", "datepicker", "float"). Empty for system fields and for
// custom fields whose type Jira does not expose. It is the branch
// key the mutation pipeline uses to encode a custom-field value.
Custom string `json:"custom,omitempty"`
}
type FileSource ¶
FileSource is what AttachmentService.Add accepts per file: a name (the filename Jira sees) plus a Reader carrying the bytes. Using io.Reader lets callers stream from *os.File without slurping into memory; the service implementation routes large payloads through io.Pipe .
type Issue ¶
type Issue struct {
ID *string `json:"id,omitempty"`
Key *string `json:"key,omitempty"`
Self *string `json:"self,omitempty"`
Fields *IssueFields `json:"fields,omitempty"`
Comments []*Comment `json:"comments,omitempty"`
Worklogs []*Worklog `json:"worklogs,omitempty"`
LinkedIssues []*Issue `json:"linked_issues,omitempty"`
Subtasks []*Issue `json:"subtasks,omitempty"`
// Transitions carries the workflow transitions Jira returns under
// expand=transitions — the moves valid from the issue's current status.
// Populated by issue view so a read answers "what can I transition to"
// without a second call.
Transitions []*Transition `json:"transitions,omitempty"`
// EditMeta carries the edit-screen metadata Jira returns under
// expand=editmeta — which fields are editable, whether they are
// required, and their allowed values. Populated by issue view so an
// edit can be planned from the same read.
EditMeta *EditMeta `json:"editmeta,omitempty"`
}
func (*Issue) UnmarshalJSON ¶
type IssueCloneRequest ¶
type IssueCreateRequest ¶
type IssueDeleteOptions ¶
type IssueDeleteOptions struct {
DeleteSubtasks bool
}
IssueDeleteOptions controls deletion behavior. DeleteSubtasks=true removes the issue and all its subtasks atomically (Jira refuses the delete otherwise when subtasks exist).
type IssueFields ¶
type IssueFields struct {
Summary *string `json:"summary,omitempty"`
IssueType *IssueType `json:"issuetype,omitempty"`
Description *adf.Document `json:"description,omitempty"`
Status *Status `json:"status,omitempty"`
Assignee *User `json:"assignee,omitempty"`
Reporter *User `json:"reporter,omitempty"`
Priority *Priority `json:"priority,omitempty"`
Labels []string `json:"labels,omitempty"`
Components []Component `json:"components,omitempty"`
Parent *Issue `json:"parent,omitempty"`
FixVersions []Version `json:"fixVersions,omitempty"`
Versions []Version `json:"versions,omitempty"`
Updated *string `json:"updated,omitempty"`
Comment *CommentPage `json:"comment,omitempty"`
Worklog *WorklogPage `json:"worklog,omitempty"`
IssueLinks []IssueLink `json:"issuelinks,omitempty"`
Subtasks []*Issue `json:"subtasks,omitempty"`
CustomFields map[string]json.RawMessage `json:"-"`
}
func (IssueFields) MarshalJSON ¶
func (f IssueFields) MarshalJSON() ([]byte, error)
func (*IssueFields) UnmarshalJSON ¶
func (f *IssueFields) UnmarshalJSON(data []byte) error
type IssueGetOptions ¶
type IssueGetOptions struct {
Expand []string
}
type IssueLinkRequest ¶
type IssueLinkRequest struct {
Type string
// TypeID addresses the link type by id instead of name; when both
// are set the id wins, matching Jira's own precedence.
TypeID string
InwardIssue string
OutwardIssue string
// Comment, when non-empty, is the native REST comment block posted
// with the link. The CLI validates its ADF body before it gets
// here; the wire forwards it verbatim.
Comment map[string]any
}
IssueLinkRequest creates an issue link via POST /rest/api/3/issueLink. Type is the link-type name (e.g., "Blocks", "Relates", "Cloners"). InwardIssue and OutwardIssue are issue keys; semantics depend on the type ("Blocks" → outwardIssue is blocked by inwardIssue).
type IssueLinkService ¶
type IssueLinkService interface {
List(context.Context, string) ([]IssueLinkView, *Response, error)
Create(context.Context, *IssueLinkRequest) (*Response, error)
Delete(context.Context, string) (*Response, error)
}
func NewIssueLinkService ¶
func NewIssueLinkService(client *Client) IssueLinkService
type IssueLinkType ¶
type IssueLinkTypeService ¶
type IssueLinkTypeService interface {
List(context.Context) ([]IssueLinkType, *Response, error)
}
func NewIssueLinkTypeService ¶
func NewIssueLinkTypeService(client *Client) IssueLinkTypeService
type IssueLinkView ¶
type IssueLinkView struct {
ID string `json:"id"`
Self string `json:"self,omitempty"`
Type IssueLinkType `json:"type"`
Direction string `json:"direction"`
OtherIssue IssueRef `json:"other_issue"`
}
type IssueListOptions ¶
type IssueListOptions struct {
ListOptions
JQL string
Fields []string
Expand []string
}
type IssueMoveRequest ¶
type IssuePageLister ¶ added in v0.6.0
type IssuePageLister interface {
List(ctx context.Context, opts *IssueListOptions) ([]*Issue, *Response, error)
}
IssuePageLister is the one-method surface ListIssuesPage needs.
type IssueService ¶
type IssueService interface {
List(context.Context, *IssueListOptions) ([]*Issue, *Response, error)
Get(context.Context, string, *IssueGetOptions) (*Issue, *Response, error)
Create(context.Context, *IssueCreateRequest) (*Issue, *Response, error)
Update(context.Context, string, *IssueUpdateRequest) (*Issue, *Response, error)
Delete(context.Context, string, *IssueDeleteOptions) (*Response, error)
Clone(context.Context, string, *IssueCloneRequest) (*Issue, *Response, error)
Move(context.Context, string, *IssueMoveRequest) (*Issue, *Response, error)
Transitions(context.Context, string) ([]*Transition, *Response, error)
Transition(context.Context, string, *TransitionRequest) (*Response, error)
AddComment(context.Context, string, *CommentAddRequest) (*Comment, *Response, error)
// Link creates an issue link between two issues (Blocks, Relates,
// etc.). Jira's bulk-edit endpoint rejects issuelinks updates;
// links require this dedicated endpoint.
Link(context.Context, *IssueLinkRequest) (*Response, error)
// AddRemoteLink attaches a web link (URL + title) to an issue —
// the Jira "Web links" feature. Different endpoint from Link.
AddRemoteLink(context.Context, string, *RemoteLinkRequest) (*Response, error)
}
func NewIssueService ¶
func NewIssueService(client *Client) IssueService
type IssueType ¶ added in v0.6.0
type IssueType struct {
Name *string `json:"name,omitempty"`
IconURL *string `json:"iconUrl,omitempty"`
Subtask *bool `json:"subtask,omitempty"`
}
IssueType is the issue's type (Epic, Story, Task, Bug, Sub-task, ...).
type IssueTypeUnknownError ¶ added in v0.10.6
type IssueTypeUnknownError struct {
IssueType string
ProjectKey string
Available []string // creatable issue-type names, for suggestions
}
IssueTypeUnknownError reports a --type value that matches no issue type on a project's create screen. The name is resolved against the fetched issuetypes list in-code, so a miss is a bad input value, NOT a Jira 404 — the CLI maps this to a validation error (exit 3) and surfaces Available as the envelope's "did you mean" suggestions. Do not give it a synthetic HTTP status: a hand-built 404 would drag a local lookup miss into the not-found bucket (see .claude/rules/output.md).
func (*IssueTypeUnknownError) Error ¶ added in v0.10.6
func (e *IssueTypeUnknownError) Error() string
type IssueUpdateRequest ¶
type IssueUpdateRequest struct {
Fields map[string]any `json:"fields"`
// Update, when non-empty, is the native REST update block of
// add/set/remove operations, sent as a sibling of fields. Jira
// validates the operation verbs server-side.
Update map[string]any `json:"update,omitempty"`
DryRun bool `json:"-"`
}
type JQLField ¶ added in v0.3.3
type JQLField struct {
Value string
DisplayName string
CustomFieldID string
Operators []string
Auto bool
}
JQLField is one queryable field. CustomFieldID is set when the field is a custom field, and holds Jira's JQL custom-field token (e.g. cf[10010], the same form as Value) — not the customfield_NNNNN REST selector. Its real use is as a discriminator: present means custom, absent means a system field. Operators are the JQL operators the field supports; Auto reports that the field's values can be fetched from the suggestions endpoint.
type JQLFunction ¶ added in v0.3.3
JQLFunction is one JQL function (e.g. currentUser()).
type JQLReference ¶ added in v0.3.3
type JQLReference struct {
Fields []JQLField
Functions []JQLFunction
ReservedWords []string
}
JQLReference is the instance's JQL metadata from /jql/autocompletedata: the fields and functions queryable on this Jira (including custom fields) plus the JQL reserved words.
type JQLService ¶ added in v0.3.3
type JQLService interface {
Parse(context.Context, []string, string) ([]ParsedQuery, *Response, error)
AutocompleteData(context.Context) (JQLReference, *Response, error)
AutocompleteSuggestions(ctx context.Context, fieldName, fieldValue string) ([]JQLSuggestion, *Response, error)
}
JQLService wraps Jira Cloud's /jql/* endpoints (parse/validate and autocomplete reference data).
func NewJQLService ¶ added in v0.3.3
func NewJQLService(client *Client) JQLService
type JQLSuggestion ¶ added in v0.6.0
JQLSuggestion is one autocomplete value for a field (e.g. a status name for `status =`). Value is what belongs in the query; DisplayName may carry markup from Jira and is for display only.
type LabelService ¶
LabelService surfaces Jira's global label list. Labels in Jira are not scoped to a project — they're a global string pool — so callers generally fetch once per profile and reuse via the local cache.
func NewLabelService ¶
func NewLabelService(client *Client) LabelService
NewLabelService constructs a LabelService bound to the given client.
type ListCommentsOptions ¶
type ListCommentsOptions struct {
ListOptions
// OrderBy lets callers override the default ordering. Empty string =
// "created" (oldest-first), matching Atlassian's native default.
OrderBy string
}
ListCommentsOptions controls pagination and ordering for comment list. Atlassian's `/issue/{key}/comment` endpoint returns oldest-first by default when `orderBy=created` is supplied; we send it explicitly so the contract is stable across instances.
type ListOptions ¶
func (ListOptions) QueryValues ¶
func (o ListOptions) QueryValues() url.Values
type LossyCommentWarning ¶
type LossyCommentWarning struct {
Type string `json:"type"`
CommentID string `json:"comment_id"`
LossyConstructs []string `json:"lossy_constructs"`
}
LossyCommentWarning is the structured warning shape emitted under envelope.warnings[] when a comment's ADF body lost fidelity during the Markdown render path. CommentID lets the consumer cross-reference the affected comment in data.comments[]; LossyConstructs lists the dropped node/mark types in sorted-unique order.
func CollectLossyCommentWarnings ¶
func CollectLossyCommentWarnings(comments []*Comment) []LossyCommentWarning
CollectLossyCommentWarnings walks comments[] and builds one warning per comment whose ADF body included nodes/marks the Markdown renderer couldn't fully express. Comments without a body are skipped silently — they're not lossy, just empty.
type Option ¶
type Option func(*Client)
func WithBaseURL ¶
func WithBasicAuth ¶
func WithDryRun ¶
WithDryRun causes Do to refuse any state-changing HTTP method (POST / PUT / PATCH / DELETE) so a --dry-run invocation cannot mutate Jira. Reads still pass through, so the same client can serve a dry-run preview that renders live data. This is the service-level safety net behind the command-layer dry-run branches: even a command path that forgets to gate a submission is stopped here.
func WithHTTPClient ¶
func WithHTTPTimeout ¶
func WithMaxRetryWait ¶ added in v0.5.0
WithMaxRetryWait sets the rate-limit retry budget: the longest a single request will spend sleeping out 429 / Retry-After responses before it gives up and returns the typed error. Zero disables auto-retry. The CLI clamps the effective wait to the remaining --timeout deadline.
func WithRateObserver ¶ added in v0.5.1
func WithRateObserver(fn RateObserver) Option
WithRateObserver registers a callback invoked with the rate-limit state of every successful response. The CLI uses it to raise a near-limit warning; it is a no-op observability seam, so a nil observer (the default) changes nothing.
func WithReadOnly ¶
WithReadOnly causes Do to refuse any non-safe HTTP method (POST / PUT / PATCH / DELETE) with a validation-typed APIError. Single point of control — the CLI sets this once based on env / per-profile config and every mutation in every command path is automatically gated.
type PageCursor ¶ added in v0.6.0
type PageCursor struct {
// contains filtered or unexported fields
}
PageCursor is an opaque continuation handle for page-at-a-time consumers (the TUI's scroll-to-fetch). Pagination mechanics live in this package per the pagination guardrail: callers only test More() and hand the cursor back to ListIssuesPage — they never see the raw token.
func CursorForTest ¶ added in v0.6.0
func CursorForTest(token string) PageCursor
CursorForTest fabricates a continuation cursor so consumers outside this package can exercise fetch-more paths in tests without touching raw tokens. An empty token yields the done cursor.
func (PageCursor) More ¶ added in v0.6.0
func (c PageCursor) More() bool
More reports whether the server has another page beyond this cursor.
type ParsedQuery ¶ added in v0.3.3
type ParsedQuery struct {
Query string `json:"query"`
Errors []string `json:"errors,omitempty"`
Warnings []string `json:"warnings,omitempty"`
Structure json.RawMessage `json:"structure,omitempty"`
}
ParsedQuery is the per-query result of a parse/validate call: the query as Jira read it, any parse errors, any warnings, and the parsed structure (kept raw — callers that don't need it ignore it).
type PermissionEntry ¶
type PermissionEntry struct {
ID string `json:"id,omitempty"`
Key string `json:"key,omitempty"`
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
Description string `json:"description,omitempty"`
HavePermission bool `json:"havePermission"`
}
PermissionEntry is a single result of /rest/api/3/mypermissions.
type PermissionsResponse ¶
type PermissionsResponse struct {
Permissions map[string]PermissionEntry `json:"permissions"`
}
PermissionsResponse wraps the /mypermissions envelope.
type ProjectFieldSchema ¶
type ProjectFieldSchema struct {
ProjectKey string `json:"project_key"`
IssueType string `json:"issue_type"`
Fields []FieldSchema `json:"fields,omitempty"`
}
type ProjectSchemaCache ¶
type ProjectSchemaCache struct {
// contains filtered or unexported fields
}
func NewProjectSchemaCache ¶
func NewProjectSchemaCache(ttl time.Duration) *ProjectSchemaCache
func (*ProjectSchemaCache) Get ¶
func (c *ProjectSchemaCache) Get(profile, projectKey, issueType string) (*ProjectFieldSchema, bool)
func (*ProjectSchemaCache) InvalidateAll ¶
func (c *ProjectSchemaCache) InvalidateAll()
func (*ProjectSchemaCache) InvalidateProfile ¶
func (c *ProjectSchemaCache) InvalidateProfile(profile string)
func (*ProjectSchemaCache) Set ¶
func (c *ProjectSchemaCache) Set(profile, projectKey, issueType string, schema *ProjectFieldSchema)
type ProjectService ¶
type ProjectService interface {
GetFieldSchema(context.Context, string, string) (*ProjectFieldSchema, *Response, error)
GetFieldSchemaForProfile(context.Context, string, string, string) (*ProjectFieldSchema, *Response, error)
// GetEditSchemaForProfile resolves the edit-screen field schema for
// one issue via GET /rest/api/3/issue/{idOrKey}/editmeta. The
// edit/move flows validate against this; createmeta covers
// create/clone. Cached per profile + issue key.
GetEditSchemaForProfile(context.Context, string, string) (*ProjectFieldSchema, *Response, error)
List(context.Context, *ListOptions) ([]ProjectSummary, *Response, error)
}
func NewProjectService ¶
func NewProjectService(client *Client, ttl time.Duration) ProjectService
type ProjectSummary ¶
type ProjectSummary struct {
ID string `json:"id"`
Key string `json:"key"`
Name string `json:"name"`
ProjectType string `json:"project_type,omitempty"`
Lead string `json:"lead,omitempty"`
}
ProjectSummary is the discovery shape for `jira cache projects` (and shell completion). Subset of /rest/api/3/project/search; only the keys agents and humans need.
type QueryOptions ¶
type QueryOptions struct {
ListOptions
JQL string
}
type Rate ¶
type Rate struct {
Remaining int
RetryAfterSeconds int
Reset time.Time
// Reason is Jira's RateLimit-Reason header (e.g. jira-burst-based,
// jira-per-issue-on-write). Empty when absent. Carried for diagnostics;
// the retry decision keys off the HTTP status and Retry-After, not this.
Reason string
// NearLimit reflects Jira's X-RateLimit-NearLimit header: the server's
// own "you are approaching the limit" signal on an otherwise-successful
// response. Surfaced as a non-fatal warning so a caller can ease off
// before it turns into a 429.
NearLimit bool
}
type RateObserver ¶ added in v0.5.1
RateObserver receives the rate-limit state of a successful response. It runs on the request's goroutine — possibly one of several under -p fan-out — so an implementation must be safe for concurrent calls.
type ReadOnlyError ¶ added in v0.10.0
ReadOnlyError reports a mutation refused because read-only mode is active (the JIRA_READ_ONLY env var or the profile's read_only=true).
func (*ReadOnlyError) Code ¶ added in v0.10.0
func (e *ReadOnlyError) Code() errtax.Code
Code classifies a read-only refusal under the taxonomy's read_only code.
func (*ReadOnlyError) Error ¶ added in v0.10.0
func (e *ReadOnlyError) Error() string
type RemoteLinkRequest ¶
RemoteLinkRequest creates a "web link" via POST /rest/api/3/issue/{key}/remotelink. Title is what the user sees; URL is where the link points.
type Response ¶
type Response struct {
Response *http.Response
StartAt int
MaxResults int
Total int
// TotalKnown reports that Total was decoded from the endpoint's own
// total field. Token-paged endpoints (enhanced search) never report
// one, and a zero Total from them is fabrication, not fact — the
// envelope only publishes a total when this is true.
TotalKnown bool
IsLast bool
NextPageToken string
TokenPage bool
Rate Rate
RawBody json.RawMessage
}
func (Response) NextCursor ¶
func (Response) UpstreamRequestID ¶ added in v0.10.0
UpstreamRequestID returns Jira's own trace id for the exchange (the Atl-Traceid / X-ARequestId response header) so envelopes can carry a value Atlassian support can correlate. Empty when the response carried neither header.
type SearchRequest ¶
type SearchResult ¶
type SearchService ¶
type SearchService interface {
JQL(context.Context, *SearchRequest) ([]*Issue, *Response, error)
ApproximateCount(context.Context, string) (int, *Response, error)
}
func NewSearchService ¶
func NewSearchService(client *Client) SearchService
type Status ¶
type Status struct {
Name *string `json:"name,omitempty"`
StatusCategory *StatusCategory `json:"statusCategory,omitempty"`
}
type StatusCategory ¶ added in v0.3.0
type StatusCategory struct {
Key *string `json:"key,omitempty"`
Name *string `json:"name,omitempty"`
ColorName *string `json:"colorName,omitempty"`
}
StatusCategory is the workflow bucket a status belongs to. Key is one of "new", "indeterminate", or "done" — stable across projects and used to color statuses by category in human output. ColorName is Jira's own color designation for the category ("blue-gray", "yellow", "green", "medium-gray"), preferred over Key when coloring so the badge matches the Jira UI.
type Transition ¶
type Transition struct {
ID *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
// HasScreen reports whether the transition presents a screen. Jira
// silently discards fields and update blocks sent to a screenless
// transition, so payload-carrying transitions must check it.
HasScreen *bool `json:"hasScreen,omitempty"`
}
type TransitionRequest ¶
type TransitionRequest struct {
ID string
Fields map[string]any
// Comment, when non-nil, is posted atomically with the status change
// via the transition body's update block.
Comment *adf.Document
// Update, when non-empty, is a native REST update block forwarded
// verbatim into the transition body. The CLI refuses a payload that
// sets both Update's comment operations and Comment, so the two
// never collide here.
Update map[string]any
DryRun bool
}
type User ¶
type User struct {
AccountID *string `json:"accountId,omitempty"`
AccountType *string `json:"accountType,omitempty"`
DisplayName *string `json:"displayName,omitempty"`
EmailAddress *string `json:"emailAddress,omitempty"`
Active *bool `json:"active,omitempty"`
Deleted *bool `json:"deleted,omitempty"`
}
type UserService ¶
type UserService interface {
Myself(context.Context) (*CurrentUser, *Response, error)
MyPermissions(ctx context.Context, projectKey string, keys []string) (*PermissionsResponse, *Response, error)
Search(ctx context.Context, query string) ([]*User, *Response, error)
ResolveAccountID(ctx context.Context, accountID string) (string, error)
ResolveUser(ctx context.Context, query string) (string, error)
}
UserService exposes user-identity endpoints.
func NewUserService ¶
func NewUserService(client *Client) UserService
NewUserService constructs a UserService bound to the given client.
type Version ¶ added in v0.10.0
type Version struct {
Name *string `json:"name,omitempty"`
}
Version is a project version reference as it appears in an issue's fixVersions / versions arrays. Only the name is modeled — it is the identity the CLI submits and diffs by.
type Visibility ¶
Visibility restricts a comment to a Jira role or a Jira group. Atlassian's data model treats Type/Value as mutually exclusive across role and group; the CLI's --visibility-role and --visibility-group flags enforce that exclusivity locally before any HTTP call ().
type VisibilityChange ¶
type VisibilityChange struct {
Mode VisibilityMode
Type string // "role" or "group"; only meaningful when Mode == VisibilityReplace
Value string // role/group name; only meaningful when Mode == VisibilityReplace
}
VisibilityChange is the typed, validated input to CommentService.Edit. Construct via ParseVisibilityChange so the mutex-flag rules are enforced before the wire body is built.
func ParseVisibilityChange ¶
func ParseVisibilityChange(flags VisibilityFlags) (VisibilityChange, error)
ParseVisibilityChange validates the flag combination and produces a VisibilityChange. Returns an error (mapped to exit 3 by the cmd layer) when:
- --visibility-role and --visibility-group are both supplied
- --clear-visibility is combined with either visibility-role or visibility-group
- --visibility-role is supplied with an empty value
- --visibility-group is supplied with an empty value
All three flags omitted → VisibilityKeep (preserve-when-omitted).
type VisibilityFlags ¶
VisibilityFlags is the cmd-layer's view of the three CLI flags. The *Set fields distinguish "flag was supplied" from "flag was supplied with empty value" — cobra's IsChanged() drives the booleans.
type VisibilityMode ¶
type VisibilityMode int
VisibilityMode is the discriminator on a VisibilityChange.
const ( // VisibilityKeep leaves the existing visibility untouched. The wire // body MUST omit the visibility key entirely so Atlassian preserves // whatever was previously set. VisibilityKeep VisibilityMode = iota // VisibilityReplace sets a new restriction (role or group). The wire // body sends `"visibility": {"type": ..., "value": ...}`. VisibilityReplace // VisibilityClear removes the existing restriction. The wire body // sends `"visibility": null` explicitly so Atlassian clears it. VisibilityClear )
type WatcherService ¶
type WatcherService interface {
List(ctx context.Context, issueKey string) (*WatchersResponse, *Response, error)
Add(ctx context.Context, issueKey, accountID string) (*Response, error)
Remove(ctx context.Context, issueKey, accountID string) (*Response, error)
}
WatcherService manages watchers on an issue.
func NewWatcherService ¶
func NewWatcherService(client *Client) WatcherService
NewWatcherService constructs a WatcherService bound to the given client.
type WatchersResponse ¶
type WatchersResponse struct {
IsWatching bool `json:"isWatching"`
WatchCount int `json:"watchCount"`
Watchers []*User `json:"watchers"`
}
WatchersResponse is the shape Atlassian returns from GET /rest/api/3/issue/{key}/watchers — the watcher list plus the caller-perspective `IsWatching` flag and instance-level `WatchCount`.
type WorklogAddRequest ¶
type WorklogPage ¶
type WorklogPage struct {
Worklogs []*Worklog `json:"worklogs,omitempty"`
}
type WorklogService ¶
type WorklogService interface {
List(context.Context, string, *ListOptions) ([]*Worklog, *Response, error)
Add(context.Context, string, *WorklogAddRequest) (*Worklog, *Response, error)
Delete(context.Context, string, string) (*Response, error)
}
func NewWorklogService ¶
func NewWorklogService(client *Client) WorklogService
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package customfield is the Jira custom-field type registry.
|
Package customfield is the Jira custom-field type registry. |