Add YouTrack pull/push sync with project settings and time tracking.
CI / test-and-build (push) Failing after 1m19s
CI / demo (push) Skipped

Wire manual Y menu and CLI sync for issues and work items, persist
per-project credentials, and keep local edits until an explicit push.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-24 00:17:51 +02:00
co-authored by Cursor
parent 19ae1f1b62
commit 98d2253f44
23 changed files with 2874 additions and 44 deletions
+38
View File
@@ -4,6 +4,7 @@ import (
"os"
"path/filepath"
"testing"
"time"
"task/internal/ticket"
)
@@ -65,6 +66,43 @@ func TestJSONStoreCreateListUpdate(t *testing.T) {
}
}
func TestUpdateAfterMarkSyncedIsDirty(t *testing.T) {
store := ticket.NewJSONStore(t.TempDir())
created, err := store.Create(ticket.Ticket{
Title: "Synced", ProjectID: "inbox", Status: ticket.StatusTodo,
Source: ticket.SourceYouTrack, ExternalID: "TAS-1",
})
if err != nil {
t.Fatal(err)
}
if err := store.MarkSynced(created.ID); err != nil {
t.Fatalf("MarkSynced: %v", err)
}
synced, err := store.Get(created.ID)
if err != nil {
t.Fatal(err)
}
if synced.UpdatedAt.After(synced.SyncedAt) {
t.Fatalf("freshly marked synced should not be dirty: updated=%v syncedAt=%v",
synced.UpdatedAt, synced.SyncedAt)
}
// Simulate UI edit: copy ticket (SyncedAt == UpdatedAt) and change title.
edited := synced
edited.Title = "Edited locally"
time.Sleep(time.Millisecond) // ensure clock can move
if err := store.Update(edited); err != nil {
t.Fatal(err)
}
got, err := store.Get(created.ID)
if err != nil {
t.Fatal(err)
}
if !got.UpdatedAt.After(got.SyncedAt) {
t.Fatalf("edit must bump UpdatedAt past SyncedAt: %v vs %v", got.UpdatedAt, got.SyncedAt)
}
}
func TestJSONStoreMigratesLegacyTasksJSON(t *testing.T) {
dir := t.TempDir()
projectDir := filepath.Join(dir, "projects", "inbox")