Documentation
¶
Overview ¶
Package linear provides client and data types for the Linear GraphQL API.
This package handles all interactions with Linear's issue tracking system, including fetching, creating, and updating issues. It provides bidirectional mapping between Linear's data model and Beads' internal types.
Index ¶
- Constants
- func BuildLinearDescription(issue *types.Issue) string
- func BuildLinearToLocalUpdates(li *Issue, config *MappingConfig) map[string]interface{}
- func CanonicalizeLinearExternalRef(externalRef string) (canonical string, ok bool)
- func ExtractLinearIdentifier(url string) string
- func GenerateIssueIDs(issues []*types.Issue, prefix, creator string, opts IDGenerationOptions) error
- func IsLinearExternalRef(externalRef string) bool
- func LabelToIssueType(labels *Labels, config *MappingConfig) types.IssueType
- func MapEpicToProjectState(status types.Status) string
- func NormalizeIssueForLinearHash(issue *types.Issue) *types.Issue
- func ParseBeadsStatus(s string) types.Status
- func ParseIssueType(s string) types.IssueType
- func PriorityToBeads(linearPriority int, config *MappingConfig) int
- func PriorityToLinear(beadsPriority int, config *MappingConfig) int
- func ProjectToEpic(lp *Project) *types.Issue
- func RelationToBeadsDep(relationType string, config *MappingConfig) string
- func StateToBeadsStatus(state *State, config *MappingConfig) types.Status
- func StatusToLinearStateType(status types.Status) string
- type Client
- func (c *Client) CreateIssue(ctx context.Context, title, description string, priority int, stateID string, ...) (*Issue, error)
- func (c *Client) CreateProject(ctx context.Context, name, description, state string) (*Project, error)
- func (c *Client) Execute(ctx context.Context, req *GraphQLRequest) (json.RawMessage, error)
- func (c *Client) FetchIssueByIdentifier(ctx context.Context, identifier string) (*Issue, error)
- func (c *Client) FetchIssues(ctx context.Context, state string) ([]Issue, error)
- func (c *Client) FetchIssuesSince(ctx context.Context, state string, since time.Time) ([]Issue, error)
- func (c *Client) FetchProjects(ctx context.Context, state string) ([]Project, error)
- func (c *Client) FetchTeams(ctx context.Context) ([]Team, error)
- func (c *Client) GetTeamStates(ctx context.Context) ([]State, error)
- func (c *Client) UpdateIssue(ctx context.Context, issueID string, updates map[string]interface{}) (*Issue, error)
- func (c *Client) UpdateProject(ctx context.Context, projectID string, updates map[string]interface{}) (*Project, error)
- func (c *Client) WithEndpoint(endpoint string) *Client
- func (c *Client) WithHTTPClient(httpClient *http.Client) *Client
- func (c *Client) WithProjectID(projectID string) *Client
- type ConfigLoader
- type Conflict
- type DependencyInfo
- type GraphQLError
- type GraphQLRequest
- type GraphQLResponse
- type IDGenerationOptions
- type Issue
- type IssueConversion
- type IssueCreateResponse
- type IssueUpdateResponse
- type IssuesResponse
- type Label
- type Labels
- type MappingConfig
- type Parent
- type Project
- type ProjectCreateResponse
- type ProjectUpdateResponse
- type ProjectsResponse
- type PullStats
- type PushStats
- type Relation
- type Relations
- type State
- type StateCache
- type StatesWrapper
- type SyncResult
- type SyncStats
- type Team
- type TeamResponse
- type TeamStates
- type TeamsResponse
- type Tracker
- func (t *Tracker) BuildExternalRef(issue *tracker.TrackerIssue) string
- func (t *Tracker) Close() error
- func (t *Tracker) ConfigPrefix() string
- func (t *Tracker) CreateIssue(ctx context.Context, issue *types.Issue) (*tracker.TrackerIssue, error)
- func (t *Tracker) DisplayName() string
- func (t *Tracker) ExtractIdentifier(ref string) string
- func (t *Tracker) FetchIssue(ctx context.Context, identifier string) (*tracker.TrackerIssue, error)
- func (t *Tracker) FetchIssues(ctx context.Context, opts tracker.FetchOptions) ([]tracker.TrackerIssue, error)
- func (t *Tracker) FieldMapper() tracker.FieldMapper
- func (t *Tracker) Init(ctx context.Context, store storage.Storage) error
- func (t *Tracker) IsExternalRef(ref string) bool
- func (t *Tracker) Name() string
- func (t *Tracker) UpdateIssue(ctx context.Context, externalID string, issue *types.Issue) (*tracker.TrackerIssue, error)
- func (t *Tracker) Validate() error
- type User
Constants ¶
const ( // DefaultAPIEndpoint is the Linear GraphQL API endpoint. DefaultAPIEndpoint = "https://api.linear.app/graphql" // DefaultTimeout is the default HTTP request timeout. DefaultTimeout = 30 * time.Second // MaxRetries is the maximum number of retries for rate-limited requests. MaxRetries = 3 // RetryDelay is the base delay between retries (exponential backoff). RetryDelay = time.Second // MaxPageSize is the maximum number of issues to fetch per page. MaxPageSize = 100 )
API configuration constants.
Variables ¶
This section is empty.
Functions ¶
func BuildLinearDescription ¶
BuildLinearDescription formats a Beads issue for Linear's description field. This mirrors the payload used during push to keep hash comparisons consistent.
func BuildLinearToLocalUpdates ¶
func BuildLinearToLocalUpdates(li *Issue, config *MappingConfig) map[string]interface{}
BuildLinearToLocalUpdates creates an updates map from a Linear issue to apply to a local Beads issue. This is used when Linear wins a conflict.
func CanonicalizeLinearExternalRef ¶
CanonicalizeLinearExternalRef returns a stable Linear issue URL without the slug. Example: https://linear.app/team/issue/TEAM-123/title -> https://linear.app/team/issue/TEAM-123 Returns ok=false if the URL isn't a recognizable Linear issue URL.
func ExtractLinearIdentifier ¶
ExtractLinearIdentifier extracts the Linear issue identifier (e.g., "TEAM-123") from a Linear URL.
func GenerateIssueIDs ¶
func GenerateIssueIDs(issues []*types.Issue, prefix, creator string, opts IDGenerationOptions) error
GenerateIssueIDs generates unique hash-based IDs for issues that don't have one. Tracks used IDs to prevent collisions within the batch (and optionally against existing IDs). The creator parameter is used as part of the hash input (e.g., "linear-import").
func IsLinearExternalRef ¶
IsLinearExternalRef checks if an external_ref URL is a Linear issue URL.
func LabelToIssueType ¶
func LabelToIssueType(labels *Labels, config *MappingConfig) types.IssueType
LabelToIssueType infers issue type from label names. Uses configurable mapping from linear.label_type_map.* config.
func MapEpicToProjectState ¶
MapEpicToProjectState maps a Beads status to Linear project state.
func NormalizeIssueForLinearHash ¶
NormalizeIssueForLinearHash returns a copy of the issue using Linear's description formatting and clears fields not present in Linear's model to avoid false conflicts.
func ParseBeadsStatus ¶
ParseBeadsStatus converts a status string to types.Status.
func ParseIssueType ¶
ParseIssueType converts an issue type string to types.IssueType.
func PriorityToBeads ¶
func PriorityToBeads(linearPriority int, config *MappingConfig) int
PriorityToBeads maps Linear priority (0-4) to Beads priority (0-4). Linear: 0=no priority, 1=urgent, 2=high, 3=medium, 4=low Beads: 0=critical, 1=high, 2=medium, 3=low, 4=backlog Uses configurable mapping from linear.priority_map.* config.
func PriorityToLinear ¶
func PriorityToLinear(beadsPriority int, config *MappingConfig) int
PriorityToLinear maps Beads priority (0-4) to Linear priority (0-4). Uses configurable mapping by inverting linear.priority_map.* config.
func ProjectToEpic ¶
ProjectToEpic converts a Linear Project to a Beads Epic issue.
func RelationToBeadsDep ¶
func RelationToBeadsDep(relationType string, config *MappingConfig) string
RelationToBeadsDep converts a Linear relation to a Beads dependency type. Uses configurable mapping from linear.relation_map.* config.
func StateToBeadsStatus ¶
func StateToBeadsStatus(state *State, config *MappingConfig) types.Status
StateToBeadsStatus maps Linear state type to Beads status. Checks both state type (backlog, unstarted, etc.) and state name for custom workflows. Uses configurable mapping from linear.state_map.* config.
func StatusToLinearStateType ¶
StatusToLinearStateType converts Beads status to Linear state type for filtering. This is used when pushing issues to Linear to find the appropriate state.
Types ¶
type Client ¶
type Client struct {
APIKey string
TeamID string
ProjectID string // Optional: filter issues to a specific project
Endpoint string // GraphQL endpoint URL (defaults to DefaultAPIEndpoint)
HTTPClient *http.Client
}
Client provides methods to interact with the Linear GraphQL API.
func (*Client) CreateIssue ¶
func (c *Client) CreateIssue(ctx context.Context, title, description string, priority int, stateID string, labelIDs []string) (*Issue, error)
CreateIssue creates a new issue in Linear.
func (*Client) CreateProject ¶
func (c *Client) CreateProject(ctx context.Context, name, description, state string) (*Project, error)
CreateProject creates a new project in Linear.
func (*Client) Execute ¶
func (c *Client) Execute(ctx context.Context, req *GraphQLRequest) (json.RawMessage, error)
Execute sends a GraphQL request to the Linear API. Handles rate limiting with exponential backoff.
func (*Client) FetchIssueByIdentifier ¶
FetchIssueByIdentifier retrieves a single issue from Linear by its identifier (e.g., "TEAM-123"). Returns nil if the issue is not found.
func (*Client) FetchIssues ¶
FetchIssues retrieves issues from Linear with optional filtering by state. state can be: "open" (unstarted/started), "closed" (completed/canceled), or "all". If ProjectID is set on the client, only issues from that project are returned.
func (*Client) FetchIssuesSince ¶
func (c *Client) FetchIssuesSince(ctx context.Context, state string, since time.Time) ([]Issue, error)
FetchIssuesSince retrieves issues from Linear that have been updated since the given time. This enables incremental sync by only fetching issues modified after the last sync. The state parameter can be: "open", "closed", or "all". If ProjectID is set on the client, only issues from that project are returned.
func (*Client) FetchProjects ¶
FetchProjects retrieves projects from Linear with optional filtering by state. state can be: "planned", "started", "paused", "completed", "canceled", or "all"/"".
func (*Client) FetchTeams ¶
FetchTeams retrieves all teams accessible with the current API key. This is useful for discovering the team ID needed for configuration.
func (*Client) GetTeamStates ¶
GetTeamStates fetches the workflow states for the configured team.
func (*Client) UpdateIssue ¶
func (c *Client) UpdateIssue(ctx context.Context, issueID string, updates map[string]interface{}) (*Issue, error)
UpdateIssue updates an existing issue in Linear.
func (*Client) UpdateProject ¶
func (c *Client) UpdateProject(ctx context.Context, projectID string, updates map[string]interface{}) (*Project, error)
UpdateProject updates an existing project in Linear.
func (*Client) WithEndpoint ¶
WithEndpoint returns a new client configured to use the specified endpoint. This is useful for testing with mock servers or connecting to self-hosted instances.
func (*Client) WithHTTPClient ¶
WithHTTPClient returns a new client configured to use the specified HTTP client. This is useful for testing or customizing timeouts and transport settings.
func (*Client) WithProjectID ¶
WithProjectID returns a new client configured to filter issues by the specified project. When set, FetchIssues and FetchIssuesSince will only return issues belonging to this project.
type ConfigLoader ¶
ConfigLoader is an interface for loading configuration values. This allows the mapping package to be decoupled from the storage layer.
type Conflict ¶
type Conflict struct {
IssueID string // Beads issue ID
LocalUpdated time.Time // When the local version was last modified
LinearUpdated time.Time // When the Linear version was last modified
LinearExternalRef string // URL to the Linear issue
LinearIdentifier string // Linear issue identifier (e.g., "TEAM-123")
LinearInternalID string // Linear's internal UUID (for API updates)
}
Conflict represents a conflict between local and Linear versions. A conflict occurs when both the local and Linear versions have been modified since the last sync.
type DependencyInfo ¶
type DependencyInfo struct {
FromLinearID string // Linear identifier of the dependent issue (e.g., "TEAM-123")
ToLinearID string // Linear identifier of the dependency target
Type string // Beads dependency type (blocks, related, duplicates, parent-child)
}
DependencyInfo represents a dependency to be created after issue import. Stored separately since we need all issues imported before linking dependencies.
type GraphQLError ¶
type GraphQLError struct {
Message string `json:"message"`
Path []string `json:"path,omitempty"`
Extensions struct {
Code string `json:"code,omitempty"`
} `json:"extensions,omitempty"`
}
GraphQLError represents a GraphQL error.
type GraphQLRequest ¶
type GraphQLRequest struct {
Query string `json:"query"`
Variables map[string]interface{} `json:"variables,omitempty"`
}
GraphQLRequest represents a GraphQL request payload.
type GraphQLResponse ¶
type GraphQLResponse struct {
Data []byte `json:"data"`
Errors []GraphQLError `json:"errors,omitempty"`
}
GraphQLResponse represents a generic GraphQL response.
type IDGenerationOptions ¶
type IDGenerationOptions struct {
BaseLength int // Starting hash length (3-8)
MaxLength int // Maximum hash length (3-8)
UsedIDs map[string]bool // Pre-populated set to avoid collisions (e.g., DB IDs)
}
IDGenerationOptions configures Linear hash ID generation.
type Issue ¶
type Issue struct {
ID string `json:"id"`
Identifier string `json:"identifier"` // e.g., "TEAM-123"
Title string `json:"title"`
Description string `json:"description"`
URL string `json:"url"`
Priority int `json:"priority"` // 0=no priority, 1=urgent, 2=high, 3=medium, 4=low
State *State `json:"state"`
Assignee *User `json:"assignee"`
Labels *Labels `json:"labels"`
Project *Project `json:"project,omitempty"`
Parent *Parent `json:"parent,omitempty"`
Relations *Relations `json:"relations,omitempty"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
CompletedAt string `json:"completedAt,omitempty"`
}
Issue represents an issue from the Linear API.
type IssueConversion ¶
type IssueConversion struct {
Issue interface{} // *types.Issue - avoiding circular import
Dependencies []DependencyInfo
}
IssueConversion holds the result of converting a Linear issue to Beads. It includes the issue and any dependencies that should be created.
func IssueToBeads ¶
func IssueToBeads(li *Issue, config *MappingConfig) *IssueConversion
IssueToBeads converts a Linear issue to a Beads issue.
type IssueCreateResponse ¶
type IssueCreateResponse struct {
IssueCreate struct {
Success bool `json:"success"`
Issue Issue `json:"issue"`
} `json:"issueCreate"`
}
IssueCreateResponse represents the response from issueCreate mutation.
type IssueUpdateResponse ¶
type IssueUpdateResponse struct {
IssueUpdate struct {
Success bool `json:"success"`
Issue Issue `json:"issue"`
} `json:"issueUpdate"`
}
IssueUpdateResponse represents the response from issueUpdate mutation.
type IssuesResponse ¶
type IssuesResponse struct {
Issues struct {
Nodes []Issue `json:"nodes"`
PageInfo struct {
HasNextPage bool `json:"hasNextPage"`
EndCursor string `json:"endCursor"`
} `json:"pageInfo"`
} `json:"issues"`
}
IssuesResponse represents the response from issues query.
type Labels ¶
type Labels struct {
Nodes []Label `json:"nodes"`
}
Labels represents paginated labels on an issue.
type MappingConfig ¶
type MappingConfig struct {
// PriorityMap maps Linear priority (0-4) to Beads priority (0-4).
// Key is Linear priority as string, value is Beads priority.
PriorityMap map[string]int
// StateMap maps Linear state types/names to Beads statuses.
// Key is lowercase state type or name, value is Beads status string.
StateMap map[string]string
// LabelTypeMap maps Linear label names to Beads issue types.
// Key is lowercase label name, value is Beads issue type.
LabelTypeMap map[string]string
// RelationMap maps Linear relation types to Beads dependency types.
// Key is Linear relation type, value is Beads dependency type.
RelationMap map[string]string
}
MappingConfig holds configurable mappings between Linear and Beads. All maps use lowercase keys for case-insensitive matching.
func DefaultMappingConfig ¶
func DefaultMappingConfig() *MappingConfig
DefaultMappingConfig returns sensible default mappings.
func LoadMappingConfig ¶
func LoadMappingConfig(loader ConfigLoader) *MappingConfig
LoadMappingConfig loads mapping configuration from a config loader. Config keys follow the pattern: linear.<category>_map.<key> = <value> Examples:
linear.priority_map.0 = 4 (Linear "no priority" -> Beads backlog) linear.state_map.started = in_progress linear.label_type_map.bug = bug linear.relation_map.blocks = blocks
type Project ¶
type Project struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
SlugId string `json:"slugId"`
URL string `json:"url"`
State string `json:"state"` // "planned", "started", "paused", "completed", "canceled"
Progress float64 `json:"progress"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
CompletedAt string `json:"completedAt,omitempty"`
}
Project represents a project in Linear.
type ProjectCreateResponse ¶
type ProjectCreateResponse struct {
ProjectCreate struct {
Success bool `json:"success"`
Project Project `json:"project"`
} `json:"projectCreate"`
}
ProjectCreateResponse represents the response from projectCreate mutation.
type ProjectUpdateResponse ¶
type ProjectUpdateResponse struct {
ProjectUpdate struct {
Success bool `json:"success"`
Project Project `json:"project"`
} `json:"projectUpdate"`
}
ProjectUpdateResponse represents the response from projectUpdate mutation.
type ProjectsResponse ¶
type ProjectsResponse struct {
Projects struct {
Nodes []Project `json:"nodes"`
PageInfo struct {
HasNextPage bool `json:"hasNextPage"`
EndCursor string `json:"endCursor"`
} `json:"pageInfo"`
} `json:"projects"`
}
ProjectsResponse represents the response from projects query.
type PullStats ¶
type PullStats struct {
Created int
Updated int
Skipped int
Incremental bool // Whether this was an incremental sync
SyncedSince string // Timestamp we synced since (if incremental)
}
PullStats tracks pull operation statistics.
type Relation ¶
type Relation struct {
ID string `json:"id"`
Type string `json:"type"` // "blocks", "blockedBy", "duplicate", "related"
RelatedIssue struct {
ID string `json:"id"`
Identifier string `json:"identifier"`
} `json:"relatedIssue"`
}
Relation represents a relation between issues in Linear.
type Relations ¶
type Relations struct {
Nodes []Relation `json:"nodes"`
}
Relations wraps the nodes array for relations.
type State ¶
type State struct {
ID string `json:"id"`
Name string `json:"name"`
Type string `json:"type"` // "backlog", "unstarted", "started", "completed", "canceled"
}
State represents a workflow state in Linear.
type StateCache ¶
type StateCache struct {
States []State
StatesByID map[string]State
OpenStateID string // First "unstarted" or "backlog" state
}
StateCache caches workflow states for the team to avoid repeated API calls.
func BuildStateCache ¶
func BuildStateCache(ctx context.Context, client *Client) (*StateCache, error)
BuildStateCache fetches and caches team states.
func BuildStateCacheFromTracker ¶
func BuildStateCacheFromTracker(ctx context.Context, t *Tracker) (*StateCache, error)
BuildStateCacheFromTracker builds a StateCache using the tracker's internal client. This allows CLI code to set up PushHooks.BuildStateCache without accessing the client directly.
func (*StateCache) FindStateForBeadsStatus ¶
func (sc *StateCache) FindStateForBeadsStatus(status types.Status) string
FindStateForBeadsStatus returns the best Linear state ID for a Beads status.
type StatesWrapper ¶
type StatesWrapper struct {
Nodes []State `json:"nodes"`
}
StatesWrapper wraps the nodes array for states.
type SyncResult ¶
type SyncResult struct {
Success bool `json:"success"`
Stats SyncStats `json:"stats"`
LastSync string `json:"last_sync,omitempty"`
Error string `json:"error,omitempty"`
Warnings []string `json:"warnings,omitempty"`
}
SyncResult represents the result of a Linear sync operation.
type SyncStats ¶
type SyncStats struct {
Pulled int `json:"pulled"`
Pushed int `json:"pushed"`
Created int `json:"created"`
Updated int `json:"updated"`
Skipped int `json:"skipped"`
Errors int `json:"errors"`
Conflicts int `json:"conflicts"`
}
SyncStats tracks statistics for a Linear sync operation.
type Team ¶
type Team struct {
ID string `json:"id"` // UUID
Name string `json:"name"` // Display name
Key string `json:"key"` // Short key used in issue identifiers (e.g., "ENG")
}
Team represents a team in Linear.
type TeamResponse ¶
type TeamResponse struct {
Team TeamStates `json:"team"`
}
TeamResponse represents the response from team query.
type TeamStates ¶
type TeamStates struct {
ID string `json:"id"`
States *StatesWrapper `json:"states"`
}
TeamStates represents workflow states for a team.
type TeamsResponse ¶
type TeamsResponse struct {
Teams struct {
Nodes []Team `json:"nodes"`
} `json:"teams"`
}
TeamsResponse represents the response from teams query.
type Tracker ¶
type Tracker struct {
// contains filtered or unexported fields
}
Tracker implements tracker.IssueTracker for Linear.
func (*Tracker) BuildExternalRef ¶
func (t *Tracker) BuildExternalRef(issue *tracker.TrackerIssue) string
func (*Tracker) ConfigPrefix ¶
func (*Tracker) CreateIssue ¶
func (*Tracker) DisplayName ¶
func (*Tracker) ExtractIdentifier ¶
func (*Tracker) FetchIssue ¶
func (*Tracker) FetchIssues ¶
func (t *Tracker) FetchIssues(ctx context.Context, opts tracker.FetchOptions) ([]tracker.TrackerIssue, error)
func (*Tracker) FieldMapper ¶
func (t *Tracker) FieldMapper() tracker.FieldMapper