Skip to content

Feature: Plugin-based issue tracker integrations (Jira, Linear, Azure DevOps, etc.) #1150

@NotMyself

Description

@NotMyself

Summary

Refactor the current Jira and Linear integrations into a plugin-based architecture that enables:

  1. Easier addition of new tracker integrations (Azure DevOps, GitHub Issues, GitLab, etc.)
  2. Shared sync logic, conflict resolution, and field mapping
  3. Consistent CLI experience across all trackers

Current State

  • Jira (cmd/bd/jira.go): Hybrid Go + Python scripts
  • Linear (cmd/bd/linear.go + internal/linear/): Fully Go-native

Both implementations duplicate:

  • Sync statistics tracking (PullStats, PushStats, SyncResult)
  • Conflict resolution strategies (prefer-local, prefer-remote, timestamp-based)
  • Command flag patterns (--pull, --push, --dry-run, --create-only)
  • Field mapping logic (status, priority, type, relations)
  • Config validation patterns

Proposed Architecture

Core Interface

// internal/tracker/interface.go
type IssueTracker interface {
    Name() string
    ValidateConfig(ctx context.Context, store storage.Storage) error
    FetchIssues(ctx context.Context, opts FetchOptions) ([]ExternalIssue, error)
    CreateIssue(ctx context.Context, issue *types.Issue) (*ExternalIssue, error)
    UpdateIssue(ctx context.Context, externalID string, issue *types.Issue) error
    FetchIssueTimestamp(ctx context.Context, externalRef string) (time.Time, error)
    IsExternalRef(externalRef string) bool
    ExtractExternalID(externalRef string) string
    BuildExternalRef(externalID string) string
}

type FieldMapper interface {
    ToBeadsStatus(externalStatus string) types.Status
    FromBeadsStatus(status types.Status) string
    ToBeadsPriority(externalPriority string) int
    FromBeadsPriority(priority int) string
    // ... etc
}

Shared Components

Component Purpose
SyncEngine Orchestrates pull/push/conflict resolution
MappingConfig Configurable field mappings with defaults
ConflictResolver Timestamp-based, prefer-local, prefer-remote
Command factory Generates consistent Cobra commands per tracker

Plugin Registry

// internal/tracker/registry.go
func Register(tracker IssueTracker)
func Get(name string) IssueTracker
func List() []string

Implementation Phases

Phase 1: Extract Common Framework (~1,625 lines)

  • internal/tracker/interface.go - Core interfaces
  • internal/tracker/types.go - Shared types (SyncStats, ExternalIssue, etc.)
  • internal/tracker/mapping.go - Default mapping framework
  • internal/tracker/sync.go - SyncEngine implementation
  • internal/tracker/conflict.go - Conflict detection and resolution
  • internal/tracker/registry.go - Plugin registry

Phase 2: Refactor Linear (~400 lines modified)

  • Implement IssueTracker interface
  • Delegate to shared SyncEngine
  • Remove duplicated conflict/sync logic

Phase 3: Refactor Jira (~300 lines modified)

  • Implement IssueTracker interface
  • Option to migrate Python scripts to Go

Phase 4: Azure DevOps Plugin (~850 lines)

  • internal/azuredevops/client.go - REST API client
  • internal/azuredevops/mapping.go - Default field mappings
  • internal/azuredevops/wiql.go - WIQL query builder

Azure DevOps mappings:

ADO State Beads Status
New open
Active in_progress
Resolved in_progress
Closed closed
ADO Type Beads Type
Bug bug
User Story feature
Task task
Epic epic

Benefits

  1. Extensibility: Adding new trackers requires only implementing the interface
  2. Consistency: All trackers share the same CLI patterns and behavior
  3. Maintainability: Bug fixes to sync logic benefit all trackers
  4. Testing: Shared logic can be tested once with mock trackers

Estimated Effort

Phase New Code Modified
Phase 1 ~1,625 lines -
Phase 2 - ~400 lines
Phase 3 - ~300 lines
Phase 4 ~850 lines -
Tests ~1,200 lines -
Total ~3,675 lines ~700 lines

Offer to Implement

I'm willing to implement this feature if there's interest. I would start with Phase 1 (common framework) and Phase 4 (Azure DevOps) as I have an immediate use case for Azure DevOps integration.

Happy to discuss the design or adjust based on feedback.


Related: This came up while working on #1145 (issue-prefix config fix).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions