fix(sync): make snapshot protection timestamp-aware (GH#865)

The --protect-left-snapshot mechanism was protecting ALL local issues
by ID alone, ignoring timestamps. This caused newer remote changes to
be incorrectly skipped during cross-worktree sync.

Changes:
- Add BuildIDToTimestampMap() to SnapshotManager for timestamp-aware
  snapshot reading
- Change ProtectLocalExportIDs from map[string]bool to map[string]time.Time
- Add shouldProtectFromUpdate() helper that compares timestamps
- Only protect if local snapshot is newer than incoming; allow update
  if incoming is newer

This fixes data loss scenarios where:
1. Main worktree closes issue at 11:31
2. Test worktree syncs and incorrectly skips the update
3. Test worktree then pushes stale open state, overwriting mains changes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
emma
2026-01-03 13:27:19 -08:00
committed by Steve Yegge
parent a5d9793ecd
commit 62e4eaf7c1
5 changed files with 348 additions and 8 deletions

View File

@@ -262,19 +262,19 @@ NOTE: Import requires direct database access and does not work with daemon mode.
OrphanHandling: orphanHandling,
}
// If --protect-left-snapshot is set, read the left snapshot and build ID set
// This protects locally exported issues from git-history-backfill
// If --protect-left-snapshot is set, read the left snapshot and build timestamp map
// GH#865: Use timestamp-aware protection - only protect if local is newer than incoming
if protectLeftSnapshot && input != "" {
beadsDir := filepath.Dir(input)
leftSnapshotPath := filepath.Join(beadsDir, "beads.left.jsonl")
if _, err := os.Stat(leftSnapshotPath); err == nil {
sm := NewSnapshotManager(input)
leftIDs, err := sm.BuildIDSet(leftSnapshotPath)
leftTimestamps, err := sm.BuildIDToTimestampMap(leftSnapshotPath)
if err != nil {
fmt.Fprintf(os.Stderr, "Warning: failed to read left snapshot: %v\n", err)
} else if len(leftIDs) > 0 {
opts.ProtectLocalExportIDs = leftIDs
fmt.Fprintf(os.Stderr, "Protecting %d issue(s) from left snapshot\n", len(leftIDs))
} else if len(leftTimestamps) > 0 {
opts.ProtectLocalExportIDs = leftTimestamps
fmt.Fprintf(os.Stderr, "Protecting %d issue(s) from left snapshot (timestamp-aware)\n", len(leftTimestamps))
}
}
}