Skip to content

Proposal: GitHub Issues as a Sync Backend (Strategy Pattern) #56

@maxbeizer

Description

@maxbeizer

Summary

Referencing #39 — the existing td-sync server provides excellent event-based sync, but requires hosting infrastructure. This proposal introduces a strategy pattern on the sync transport so that GitHub Issues can serve as an alternative shared persistence layer. No separate server needed — team members sync td tasks via a GitHub repo's issue tracker.

How It Works

The existing sync architecture already has clean separation:

Local SQLite → action_log → GetPendingEvents() → [Event] → Push → Server
Server → Pull → [Event] → ApplyEvents() → Local SQLite

The key insight is that GetPendingEvents() and ApplyEvents() are backend-agnostic — they work with []Event. Only the transport (push/pull) is HTTP-specific. This proposal extracts a SyncBackend interface:

type SyncBackend interface {
    Push(events []Event) (PushResult, error)
    Pull(afterSeq int64) (PullResult, error)
    Status() (SyncStatus, error)
}

Two implementations:

  1. HTTPBackend — wraps the existing td-sync HTTP client (current behavior, zero regression)
  2. GitHubBackend — translates Events to/from GitHub Issues API calls

GitHub Issues Mapping

td concept GitHub representation
Issue (task) GitHub Issue
Status (open/in_progress/in_review/closed) Labels: td:open, td:in-progress, td:in-review; or GitHub closed state
Priority (p0–p3) Labels: td:p0td:p3
Type (task/bug/feature/epic) Labels: td:task, td:bug, td:feature, td:epic
Dependencies Sub-issues or issue body task list
Logs/handoffs Issue comments (structured JSON in a <details> block)
Sync sequence Issue/comment updated_at timestamps + local watermark
td ID Embedded in issue body metadata (HTML comment or YAML front matter)

User Experience

# Configure GitHub as sync backend
td config set sync.backend github
td config set sync.github.repo owner/repo

# Or via the guided flow
td sync init
# → "Choose sync backend: (1) HTTP server  (2) GitHub Issues"

# Then sync works the same as today
td sync --push
td sync --pull
td sync --status

Implementation Phases

  1. Extract SyncBackend interface — define the interface, wrap the existing HTTP client as HTTPBackend, update cmd/sync.go and cmd/autosync.go to use the interface
  2. GitHub backend — implement GitHubBackend with bidirectional Event ↔ GitHub Issue mapping, auth via gh CLI token / GITHUB_TOKEN
  3. Config & init flow — add sync.backend config field, extend td sync init with GitHub option
  4. Edge cases — conflict resolution (timestamp-based), rate limit handling, initial seed/import
  5. Documentation — setup guide, mapping reference, limitations

Why This Approach

  • Minimal disruption: Only the transport layer changes. GetPendingEvents(), ApplyEvents(), action_log, conflict detection — all reused as-is.
  • Zero-infra collaboration: Teams already on GitHub can sync td tasks without hosting a td-sync server.
  • Incremental: HTTPBackend wraps existing code first (no regression risk), then GitHubBackend is additive.
  • Extensible: The SyncBackend interface could support other backends in the future (e.g., GitLab, Linear, plain git).

Open Questions

  • Should the GitHub backend support bidirectional sync (changes made directly in GitHub UI reflected back in td)? Or is td the source of truth with GitHub as read-only mirror?
  • For dependencies, prefer sub-issues (GitHub native) or task lists in issue body (more portable)?
  • Should there be a td:managed label to distinguish td-synced issues from manually created ones?
  • Rate limit strategy: batch via GraphQL on pull, or stick with REST for simplicity?

Happy to contribute an implementation if there's interest!

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