Fix bd-ca0b: bd sync now auto-resolves conflicts instead of failing

Previously, bd sync would fail with "Pre-export validation failed:
refusing to export: JSONL is newer than database" when JSONL was
modified (e.g., after git pull).

Now bd sync intelligently handles this by:
- Detecting when JSONL is newer than the database
- Automatically importing before exporting
- Continuing with the normal sync flow

This makes the workflow much smoother - users can just run 'bd sync'
and it figures out what needs to be done.

Changes:
- Added isJSONLNewer() helper to check file timestamps
- Modified sync command to auto-import when JSONL is newer
- Extracted timestamp check logic for reusability

Resolves: bd-ca0b
🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-11-20 20:33:52 -05:00
parent bba764987a
commit a1e507520c
2 changed files with 32 additions and 11 deletions

View File

@@ -12,23 +12,34 @@ import (
"github.com/steveyegge/beads/internal/types"
)
// isJSONLNewer checks if JSONL file is newer than database file.
// Returns true if JSONL is newer, false otherwise.
func isJSONLNewer(jsonlPath string) bool {
jsonlInfo, jsonlStatErr := os.Stat(jsonlPath)
if jsonlStatErr != nil {
return false
}
beadsDir := filepath.Dir(jsonlPath)
dbPath := filepath.Join(beadsDir, "beads.db")
dbInfo, dbStatErr := os.Stat(dbPath)
if dbStatErr != nil {
return false
}
return jsonlInfo.ModTime().After(dbInfo.ModTime())
}
// validatePreExport performs integrity checks before exporting database to JSONL.
// Returns error if critical issues found that would cause data loss.
func validatePreExport(ctx context.Context, store storage.Storage, jsonlPath string) error {
// Check if JSONL is newer than database - if so, must import first
jsonlInfo, jsonlStatErr := os.Stat(jsonlPath)
if jsonlStatErr == nil {
beadsDir := filepath.Dir(jsonlPath)
dbPath := filepath.Join(beadsDir, "beads.db")
dbInfo, dbStatErr := os.Stat(dbPath)
if dbStatErr == nil {
// If JSONL is newer, refuse export - caller must import first
if jsonlInfo.ModTime().After(dbInfo.ModTime()) {
return fmt.Errorf("refusing to export: JSONL is newer than database (import first to avoid data loss)")
}
}
if isJSONLNewer(jsonlPath) {
return fmt.Errorf("refusing to export: JSONL is newer than database (import first to avoid data loss)")
}
jsonlInfo, jsonlStatErr := os.Stat(jsonlPath)
// Get database issue count (fast path with COUNT(*) if available)
dbCount, err := countDBIssuesFast(ctx, store)
if err != nil {

View File

@@ -126,6 +126,16 @@ Use --merge to merge the sync branch back to main branch.`,
if dryRun {
fmt.Println("→ [DRY RUN] Would export pending changes to JSONL")
} else {
// Smart conflict resolution: if JSONL is newer, auto-import first
if isJSONLNewer(jsonlPath) {
fmt.Println("→ JSONL is newer than database, importing first...")
if err := importFromJSONL(ctx, jsonlPath, renameOnImport); err != nil {
fmt.Fprintf(os.Stderr, "Error auto-importing: %v\n", err)
os.Exit(1)
}
fmt.Println("✓ Auto-import complete")
}
// Pre-export integrity checks
if err := ensureStoreActive(); err == nil && store != nil {
if err := validatePreExport(ctx, store, jsonlPath); err != nil {