tracker

package
v0.62.1 Latest Latest
Warning

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

Go to latest
Published: Mar 13, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package tracker provides a plugin framework for external issue tracker integrations.

It defines interfaces (IssueTracker, FieldMapper) and a shared SyncEngine that eliminates duplication between tracker integrations (Linear, GitLab, Jira, etc.).

Design based on GitHub issue #1150 and PRs #1564-#1567, updated for Dolt-only storage.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func List

func List() []string

List returns the names of all registered trackers, sorted alphabetically.

func Register

func Register(name string, factory TrackerFactory)

Register adds a tracker factory to the global registry. Typically called from an init() function in each tracker adapter package.

Types

type Conflict

type Conflict struct {
	IssueID            string    // Beads issue ID
	LocalUpdated       time.Time // When the local version was last modified
	ExternalUpdated    time.Time // When the external version was last modified
	ExternalRef        string    // URL or identifier for the external issue
	ExternalIdentifier string    // External tracker's identifier (e.g., "TEAM-123")
	ExternalInternalID string    // External tracker's internal ID (for API calls)
}

Conflict represents a bidirectional modification conflict.

type ConflictResolution

type ConflictResolution string

ConflictResolution specifies how to handle sync conflicts.

const (
	// ConflictTimestamp resolves conflicts by keeping the newer version.
	ConflictTimestamp ConflictResolution = "timestamp"
	// ConflictLocal always keeps the local beads version.
	ConflictLocal ConflictResolution = "local"
	// ConflictExternal always keeps the external tracker's version.
	ConflictExternal ConflictResolution = "external"
)

type DependencyInfo

type DependencyInfo struct {
	FromExternalID string // External identifier of the dependent issue
	ToExternalID   string // External identifier of the dependency target
	Type           string // Beads dependency type (blocks, related, duplicates, parent-child)
}

DependencyInfo describes a dependency to create after all issues are imported.

type Engine

type Engine struct {
	Tracker   IssueTracker
	Store     storage.Storage
	Actor     string
	PullHooks *PullHooks
	PushHooks *PushHooks

	// Callbacks for UI feedback (optional).
	OnMessage func(msg string)
	OnWarning func(msg string)
	// contains filtered or unexported fields
}

Engine orchestrates synchronization between beads and an external tracker. It implements the shared Pull→Detect→Resolve→Push pattern that all tracker integrations follow, eliminating duplication between Linear, GitLab, etc.

func NewEngine

func NewEngine(tracker IssueTracker, store storage.Storage, actor string) *Engine

NewEngine creates a new sync engine for the given tracker and storage.

func (*Engine) DetectConflicts

func (e *Engine) DetectConflicts(ctx context.Context) ([]Conflict, error)

DetectConflicts identifies issues that were modified both locally and externally since the last sync.

func (*Engine) ResolveState

func (e *Engine) ResolveState(status types.Status) (string, bool)

ResolveState maps a beads status to a tracker state ID using the push state cache. Returns (stateID, ok). Only usable during a push operation after BuildStateCache has run.

func (*Engine) Sync

func (e *Engine) Sync(ctx context.Context, opts SyncOptions) (*SyncResult, error)

Sync performs a complete synchronization operation based on the given options.

type FetchOptions

type FetchOptions struct {
	// State filter: "open", "closed", or "all" (default)
	State string

	// Incremental sync: only fetch issues updated since this time.
	Since *time.Time

	// Maximum number of issues to fetch (0 = no limit).
	Limit int
}

FetchOptions specifies options for fetching issues from an external tracker.

type FieldMapper

type FieldMapper interface {
	// PriorityToBeads converts a tracker priority to beads priority (0-4).
	PriorityToBeads(trackerPriority interface{}) int

	// PriorityToTracker converts a beads priority (0-4) to the tracker's format.
	PriorityToTracker(beadsPriority int) interface{}

	// StatusToBeads converts a tracker state to a beads status.
	StatusToBeads(trackerState interface{}) types.Status

	// StatusToTracker converts a beads status to the tracker's state format.
	StatusToTracker(beadsStatus types.Status) interface{}

	// TypeToBeads converts a tracker issue type to a beads issue type.
	TypeToBeads(trackerType interface{}) types.IssueType

	// TypeToTracker converts a beads issue type to the tracker's format.
	TypeToTracker(beadsType types.IssueType) interface{}

	// IssueToBeads performs a full conversion from a tracker issue to a beads issue.
	// Returns the converted issue and any dependencies to be created.
	IssueToBeads(trackerIssue *TrackerIssue) *IssueConversion

	// IssueToTracker builds update fields from a beads issue for the external tracker.
	// Returns a map of field names to values in the tracker's format.
	IssueToTracker(issue *types.Issue) map[string]interface{}
}

FieldMapper handles bidirectional conversion of issue fields between an external tracker and beads. Each tracker provides its own mapper.

type IssueConversion

type IssueConversion struct {
	Issue        *types.Issue
	Dependencies []DependencyInfo
}

IssueConversion holds the result of converting an external tracker issue to beads.

type IssueTracker

type IssueTracker interface {
	// Name returns the lowercase identifier for this tracker (e.g., "linear", "gitlab").
	Name() string

	// DisplayName returns the human-readable name (e.g., "Linear", "GitLab").
	DisplayName() string

	// ConfigPrefix returns the config key prefix (e.g., "linear", "gitlab").
	ConfigPrefix() string

	// Init initializes the tracker with configuration from the beads config store.
	// Called once before any sync operations.
	Init(ctx context.Context, store storage.Storage) error

	// Validate checks that the tracker is properly configured and can connect.
	Validate() error

	// Close releases any resources held by the tracker.
	Close() error

	// FetchIssues retrieves issues from the external tracker.
	FetchIssues(ctx context.Context, opts FetchOptions) ([]TrackerIssue, error)

	// FetchIssue retrieves a single issue by its external identifier.
	// Returns nil, nil if the issue doesn't exist.
	FetchIssue(ctx context.Context, identifier string) (*TrackerIssue, error)

	// CreateIssue creates a new issue in the external tracker.
	// Returns the created issue with its external ID and URL populated.
	CreateIssue(ctx context.Context, issue *types.Issue) (*TrackerIssue, error)

	// UpdateIssue updates an existing issue in the external tracker.
	// The externalID is the tracker's internal ID (not the human-readable identifier).
	UpdateIssue(ctx context.Context, externalID string, issue *types.Issue) (*TrackerIssue, error)

	// FieldMapper returns the field mapper for this tracker.
	FieldMapper() FieldMapper

	// IsExternalRef checks if an external_ref string belongs to this tracker.
	IsExternalRef(ref string) bool

	// ExtractIdentifier extracts the human-readable identifier from an external_ref.
	ExtractIdentifier(ref string) string

	// BuildExternalRef constructs an external_ref string for a tracker issue.
	BuildExternalRef(issue *TrackerIssue) string
}

IssueTracker is the plugin interface that all tracker integrations must implement. Each external system (Linear, GitLab, Jira, etc.) provides an adapter implementing this interface. The SyncEngine uses it to perform bidirectional synchronization.

func NewTracker

func NewTracker(name string) (IssueTracker, error)

NewTracker creates a new instance of the named tracker. Returns an error if the tracker is not registered.

type PullHooks

type PullHooks struct {
	// GenerateID assigns an ID to a newly-pulled issue before import.
	// If nil, issues keep whatever ID the storage layer assigns.
	// The hook receives the issue (with converted fields) and should set issue.ID.
	// Callers typically pre-load used IDs into the closure for collision avoidance.
	GenerateID func(ctx context.Context, issue *types.Issue) error

	// TransformIssue is called after FieldMapper.IssueToBeads() and before storage.
	// Use for description formatting, field normalization, etc.
	TransformIssue func(issue *types.Issue)

	// ShouldImport filters issues during pull. Return false to skip.
	// Called on the raw TrackerIssue before conversion to beads format.
	// If nil, all issues are imported.
	ShouldImport func(issue *TrackerIssue) bool
}

PullHooks contains optional callbacks that customize pull (import) behavior. Trackers opt into behaviors by setting the hooks they need.

type PullStats

type PullStats struct {
	Created     int
	Updated     int
	Skipped     int
	Incremental bool
	SyncedSince string
}

PullStats tracks pull operation results.

type PushHooks

type PushHooks struct {
	// FormatDescription transforms the description before sending to tracker.
	// Linear uses this for BuildLinearDescription (merging structured fields).
	// If nil, issue.Description is used as-is.
	FormatDescription func(issue *types.Issue) string

	// ContentEqual compares local and remote issues to skip unnecessary API calls.
	// Returns true if content is identical (skip update). If nil, uses timestamp comparison.
	ContentEqual func(local *types.Issue, remote *TrackerIssue) bool

	// ShouldPush filters issues during push. Return false to skip.
	// Called in addition to type/state/ephemeral filters. Use for prefix filtering, etc.
	// If nil, all issues (matching other filters) are pushed.
	ShouldPush func(issue *types.Issue) bool

	// BuildStateCache is called once before the push loop to pre-cache workflow states.
	// Returns an opaque cache value passed to ResolveState on each issue.
	// If nil, no caching is done.
	BuildStateCache func(ctx context.Context) (interface{}, error)

	// ResolveState maps a beads status to a tracker state ID using the cached state.
	// Only called if BuildStateCache is set. Returns (stateID, ok).
	ResolveState func(cache interface{}, status types.Status) (string, bool)
}

PushHooks contains optional callbacks that customize push (export) behavior. Trackers opt into behaviors by setting the hooks they need.

type PushStats

type PushStats struct {
	Created int
	Updated int
	Skipped int
	Errors  int
}

PushStats tracks push operation results.

type SyncOptions

type SyncOptions struct {
	// Pull imports issues from the external tracker.
	Pull bool
	// Push exports issues to the external tracker.
	Push bool
	// DryRun previews sync without making changes.
	DryRun bool
	// CreateOnly only creates new issues, doesn't update existing.
	CreateOnly bool
	// State filters issues: "open", "closed", or "all".
	State string
	// ConflictResolution specifies how to handle bidirectional conflicts.
	ConflictResolution ConflictResolution
	// TypeFilter limits which issue types are synced (empty = all).
	TypeFilter []types.IssueType
	// ExcludeTypes excludes specific issue types from sync.
	ExcludeTypes []types.IssueType
	// ExcludeEphemeral skips ephemeral/wisp issues from push (default behavior in CLI).
	ExcludeEphemeral bool
}

SyncOptions configures the behavior of a sync operation.

type SyncResult

type SyncResult struct {
	Success  bool      `json:"success"`
	Stats    SyncStats `json:"stats"`
	LastSync string    `json:"last_sync,omitempty"` // RFC3339 timestamp
	Error    string    `json:"error,omitempty"`
	Warnings []string  `json:"warnings,omitempty"`
}

SyncResult is the complete result of a 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 accumulates sync statistics.

type TrackerFactory

type TrackerFactory func() IssueTracker

TrackerFactory creates a new IssueTracker instance.

func Get

func Get(name string) TrackerFactory

Get returns the factory for the named tracker, or nil if not registered.

type TrackerIssue

type TrackerIssue struct {
	// Core identification
	ID         string // External tracker's internal ID (e.g., UUID)
	Identifier string // Human-readable identifier (e.g., "TEAM-123", "PROJ-456")
	URL        string // Web URL to the issue

	// Content
	Title       string
	Description string

	// Classification
	Priority int         // Priority value (tracker-specific, mapped via FieldMapper)
	State    interface{} // Tracker-specific state object (mapped via FieldMapper)
	Type     interface{} // Tracker-specific type (mapped via FieldMapper)
	Labels   []string    // Labels/tags

	// Assignment
	Assignee      string // Assignee name or email
	AssigneeID    string // Assignee's tracker-specific ID
	AssigneeEmail string // Assignee email if available

	// Timestamps
	CreatedAt   time.Time
	UpdatedAt   time.Time
	CompletedAt *time.Time

	// Relationships
	ParentID         string // Parent issue identifier (for subtasks/children)
	ParentInternalID string // Parent issue internal ID

	// Raw data for tracker-specific processing
	Raw interface{} // Original API response for tracker-specific access

	// Metadata for tracker-specific fields that don't map to core Issue fields.
	// Stored in Issue.Metadata for round-trip preservation.
	Metadata map[string]interface{}
}

TrackerIssue represents an issue from an external tracker in a generic format. Each tracker adapter converts its native issue type to/from this intermediate form.

Jump to

Keyboard shortcuts

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