Files
beads/cmd/bd/version_tracking.go
Steve Yegge 7796f5c7f5 feat: Auto-migrate database on CLI version bump (bd-jgxi)
When CLI is upgraded (e.g., 0.24.0 → 0.24.1), the database version
is now automatically updated to match the CLI version during
PersistentPreRun. This fixes the recurring UX issue where bd doctor
shows version mismatch after every CLI upgrade.

Implementation:
- Added autoMigrateOnVersionBump() function in version_tracking.go
- Calls after trackBdVersion() in PersistentPreRun
- Best-effort and silent failures to avoid disrupting commands
- Only updates bd_version metadata field
- Includes comprehensive test coverage

Changes:
- cmd/bd/main.go: Call autoMigrateOnVersionBump() in PersistentPreRun
- cmd/bd/version_tracking.go: Implement auto-migration logic
- cmd/bd/version_tracking_test.go: Add tests for auto-migration

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-23 18:09:24 -08:00

198 lines
5.9 KiB
Go

package main
import (
"context"
"fmt"
"os"
"github.com/steveyegge/beads/internal/beads"
"github.com/steveyegge/beads/internal/configfile"
"github.com/steveyegge/beads/internal/debug"
"github.com/steveyegge/beads/internal/storage/sqlite"
)
// trackBdVersion checks if bd version has changed since last run and updates metadata.json.
// This function is best-effort - failures are silent to avoid disrupting commands.
// Sets global variables versionUpgradeDetected and previousVersion if upgrade detected.
//
// bd-loka: Built-in version tracking for upgrade awareness
func trackBdVersion() {
// Find the beads directory
beadsDir := beads.FindBeadsDir()
if beadsDir == "" {
// No .beads directory found - this is fine (e.g., bd init, bd version, etc.)
return
}
// Load current config
cfg, err := configfile.Load(beadsDir)
if err != nil {
// Silent failure - config might not exist yet
return
}
if cfg == nil {
// No config file yet - create one with current version
cfg = configfile.DefaultConfig()
cfg.LastBdVersion = Version
_ = cfg.Save(beadsDir) // Best effort
return
}
// Check if version changed
if cfg.LastBdVersion != "" && cfg.LastBdVersion != Version {
// Version upgrade detected!
versionUpgradeDetected = true
previousVersion = cfg.LastBdVersion
}
// Update metadata.json with current version (best effort)
// Only write if version actually changed to minimize I/O
// Also update on first run (when LastBdVersion is empty) to initialize tracking
if cfg.LastBdVersion != Version {
cfg.LastBdVersion = Version
_ = cfg.Save(beadsDir) // Silent failure is fine
}
}
// getVersionsSince returns all version changes since the given version.
// If sinceVersion is empty, returns all known versions.
// Returns changes in chronological order (oldest first).
//
// Note: versionChanges array is in reverse chronological order (newest first),
// so we return elements before the found index and reverse the slice.
func getVersionsSince(sinceVersion string) []VersionChange {
if sinceVersion == "" {
// Return all versions (already in reverse chronological, but kept for compatibility)
return versionChanges
}
// Find the index of sinceVersion
// versionChanges is ordered newest-first: [0.23.0, 0.22.1, 0.22.0, 0.21.0]
startIdx := -1
for i, vc := range versionChanges {
if vc.Version == sinceVersion {
startIdx = i
break
}
}
if startIdx == -1 {
// sinceVersion not found in our changelog - return all versions
// (user might be upgrading from a very old version)
return versionChanges
}
if startIdx == 0 {
// Already on the newest version
return []VersionChange{}
}
// Return versions before sinceVersion (those are newer)
// Then reverse to get chronological order (oldest first)
newerVersions := versionChanges[:startIdx]
// Reverse the slice to get chronological order
result := make([]VersionChange, len(newerVersions))
for i := range newerVersions {
result[i] = newerVersions[len(newerVersions)-1-i]
}
return result
}
// maybeShowUpgradeNotification displays a one-time upgrade notification if version changed.
// This is called by commands like 'bd ready' and 'bd list' to inform users of upgrades.
func maybeShowUpgradeNotification() {
// Only show if upgrade detected and not yet acknowledged
if !versionUpgradeDetected || upgradeAcknowledged {
return
}
// Mark as acknowledged so we only show once per session
upgradeAcknowledged = true
// Display notification
fmt.Printf("🔄 bd upgraded from v%s to v%s since last use\n", previousVersion, Version)
fmt.Println("💡 Run 'bd upgrade review' to see what changed")
fmt.Println()
}
// autoMigrateOnVersionBump automatically migrates the database when CLI version changes.
// This function is best-effort - failures are silent to avoid disrupting commands.
// Called from PersistentPreRun after trackBdVersion().
//
// bd-jgxi: Auto-migrate database on CLI version bump
func autoMigrateOnVersionBump() {
// Only migrate if version upgrade was detected
if !versionUpgradeDetected {
return
}
// Find the beads directory
beadsDir := beads.FindBeadsDir()
if beadsDir == "" {
// No .beads directory - nothing to migrate
return
}
// Load config to get database path
cfg, err := configfile.Load(beadsDir)
if err != nil || cfg == nil {
// Config load failed or doesn't exist - skip migration
debug.Logf("auto-migrate: skipping migration, config load failed: %v", err)
return
}
// Get database path
dbPath := cfg.DatabasePath(beadsDir)
// Check if database exists
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
// No database file - nothing to migrate
debug.Logf("auto-migrate: skipping migration, database does not exist: %s", dbPath)
return
}
// Open database to check current version
ctx := context.Background()
store, err := sqlite.New(ctx, dbPath)
if err != nil {
// Failed to open database - skip migration
debug.Logf("auto-migrate: failed to open database: %v", err)
return
}
// Get current database version
dbVersion, err := store.GetMetadata(ctx, "bd_version")
if err != nil {
// Failed to read version - skip migration
debug.Logf("auto-migrate: failed to read database version: %v", err)
_ = store.Close()
return
}
// Check if migration is needed
if dbVersion == Version {
// Database is already at current version
debug.Logf("auto-migrate: database already at version %s", Version)
_ = store.Close()
return
}
// Perform migration: update database version
debug.Logf("auto-migrate: migrating database from %s to %s", dbVersion, Version)
if err := store.SetMetadata(ctx, "bd_version", Version); err != nil {
// Migration failed - log and continue
debug.Logf("auto-migrate: failed to update database version: %v", err)
_ = store.Close()
return
}
// Close database
if err := store.Close(); err != nil {
debug.Logf("auto-migrate: warning: failed to close database: %v", err)
}
debug.Logf("auto-migrate: successfully migrated database to version %s", Version)
}