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:
HTTPBackend — wraps the existing td-sync HTTP client (current behavior, zero regression)
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:p0 … td: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
- 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
- GitHub backend — implement
GitHubBackend with bidirectional Event ↔ GitHub Issue mapping, auth via gh CLI token / GITHUB_TOKEN
- Config & init flow — add
sync.backend config field, extend td sync init with GitHub option
- Edge cases — conflict resolution (timestamp-based), rate limit handling, initial seed/import
- 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!
Summary
Referencing #39 — the existing
td-syncserver 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:
The key insight is that
GetPendingEvents()andApplyEvents()are backend-agnostic — they work with[]Event. Only the transport (push/pull) is HTTP-specific. This proposal extracts aSyncBackendinterface:Two implementations:
HTTPBackend— wraps the existing td-sync HTTP client (current behavior, zero regression)GitHubBackend— translates Events to/from GitHub Issues API callsGitHub Issues Mapping
td:open,td:in-progress,td:in-review; or GitHub closed statetd:p0…td:p3td:task,td:bug,td:feature,td:epic<details>block)updated_attimestamps + local watermarkUser Experience
Implementation Phases
SyncBackendinterface — define the interface, wrap the existing HTTP client asHTTPBackend, updatecmd/sync.goandcmd/autosync.goto use the interfaceGitHubBackendwith bidirectional Event ↔ GitHub Issue mapping, auth viaghCLI token /GITHUB_TOKENsync.backendconfig field, extendtd sync initwith GitHub optionWhy This Approach
GetPendingEvents(),ApplyEvents(), action_log, conflict detection — all reused as-is.SyncBackendinterface could support other backends in the future (e.g., GitLab, Linear, plain git).Open Questions
td:managedlabel to distinguish td-synced issues from manually created ones?Happy to contribute an implementation if there's interest!