Enforce canonical database naming (beads.db) - bd-165

- Added CanonicalDatabaseName constant (beads.db) and LegacyDatabaseNames list
- Updated bd init to use canonical name via constant
- Added daemon validation to reject non-canonical database names
- Updated bd migrate to use canonical name constant
- Enhanced FindDatabasePath to warn when using legacy database names
- All database discovery now prefers beads.db with backward compatibility

Closes bd-165
This commit is contained in:
Steve Yegge
2025-10-26 20:42:18 -07:00
parent a02729ea57
commit f24573a5f8
5 changed files with 65 additions and 30 deletions

File diff suppressed because one or more lines are too long

View File

@@ -19,6 +19,12 @@ import (
"github.com/steveyegge/beads/internal/types" "github.com/steveyegge/beads/internal/types"
) )
// CanonicalDatabaseName is the required database filename for all beads repositories
const CanonicalDatabaseName = "beads.db"
// LegacyDatabaseNames are old names that should be migrated
var LegacyDatabaseNames = []string{"bd.db", "issues.db", "bugs.db"}
// Issue represents a tracked work item with metadata, dependencies, and status. // Issue represents a tracked work item with metadata, dependencies, and status.
type ( type (
Issue = types.Issue Issue = types.Issue
@@ -184,38 +190,53 @@ func findDatabaseInTree() string {
} }
// Fall back to canonical beads.db for backward compatibility // Fall back to canonical beads.db for backward compatibility
canonicalDB := filepath.Join(beadsDir, "beads.db") canonicalDB := filepath.Join(beadsDir, CanonicalDatabaseName)
if _, err := os.Stat(canonicalDB); err == nil { if _, err := os.Stat(canonicalDB); err == nil {
return canonicalDB return canonicalDB
} }
// Found .beads/ directory, look for *.db files // Found .beads/ directory, look for *.db files
matches, err := filepath.Glob(filepath.Join(beadsDir, "*.db")) matches, err := filepath.Glob(filepath.Join(beadsDir, "*.db"))
if err == nil && len(matches) > 0 { if err == nil && len(matches) > 0 {
// Filter out backup files // Filter out backup files
var validDBs []string var validDBs []string
for _, match := range matches { for _, match := range matches {
baseName := filepath.Base(match) baseName := filepath.Base(match)
// Skip backup files (e.g., beads.db.backup, bd.db.backup) // Skip backup files (e.g., beads.db.backup, bd.db.backup)
if filepath.Ext(baseName) != ".backup" { if filepath.Ext(baseName) != ".backup" {
validDBs = append(validDBs, match) validDBs = append(validDBs, match)
}
}
if len(validDBs) > 1 {
// Multiple databases found - this is ambiguous
// Print error to stderr but return the first one for backward compatibility
fmt.Fprintf(os.Stderr, "Warning: Multiple database files found in %s:\n", beadsDir)
for _, db := range validDBs {
fmt.Fprintf(os.Stderr, " - %s\n", filepath.Base(db))
}
fmt.Fprintf(os.Stderr, "Run 'bd init' to migrate to beads.db or manually remove old databases.\n\n")
}
if len(validDBs) > 0 {
return validDBs[0]
}
} }
}
if len(validDBs) > 1 {
// Multiple databases found - this is ambiguous
// Print error to stderr but return the first one for backward compatibility
fmt.Fprintf(os.Stderr, "Warning: Multiple database files found in %s:\n", beadsDir)
for _, db := range validDBs {
fmt.Fprintf(os.Stderr, " - %s\n", filepath.Base(db))
}
fmt.Fprintf(os.Stderr, "Run 'bd init' to migrate to %s or manually remove old databases.\n\n", CanonicalDatabaseName)
}
if len(validDBs) > 0 {
// Check if using legacy name and warn
dbName := filepath.Base(validDBs[0])
if dbName != CanonicalDatabaseName {
isLegacy := false
for _, legacy := range LegacyDatabaseNames {
if dbName == legacy {
isLegacy = true
break
}
}
if isLegacy {
fmt.Fprintf(os.Stderr, "WARNING: Using legacy database name: %s\n", dbName)
fmt.Fprintf(os.Stderr, "Run 'bd migrate' to upgrade to canonical name: %s\n\n", CanonicalDatabaseName)
}
}
return validDBs[0]
}
}
} }
// Move up one directory // Move up one directory

View File

@@ -1162,11 +1162,23 @@ func runDaemonLoop(interval time.Duration, autoCommit, autoPush bool, logPath, p
for _, db := range validDBs { for _, db := range validDBs {
log.log(" - %s", filepath.Base(db)) log.log(" - %s", filepath.Base(db))
} }
log.log("Run 'bd init' to migrate to beads.db or manually remove old databases") log.log("")
log.log("Beads requires a single canonical database: %s", beads.CanonicalDatabaseName)
log.log("Run 'bd init' to migrate legacy databases")
os.Exit(1) os.Exit(1)
} }
} }
// Validate using canonical name
dbBaseName := filepath.Base(daemonDBPath)
if dbBaseName != beads.CanonicalDatabaseName {
log.log("Error: Non-canonical database name: %s", dbBaseName)
log.log("Expected: %s", beads.CanonicalDatabaseName)
log.log("")
log.log("Run 'bd init' to migrate to canonical name")
os.Exit(1)
}
log.log("Using database: %s", daemonDBPath) log.log("Using database: %s", daemonDBPath)
store, err := sqlite.New(daemonDBPath) store, err := sqlite.New(daemonDBPath)

View File

@@ -9,6 +9,7 @@ import (
"github.com/fatih/color" "github.com/fatih/color"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/steveyegge/beads"
"github.com/steveyegge/beads/internal/configfile" "github.com/steveyegge/beads/internal/configfile"
"github.com/steveyegge/beads/internal/storage/sqlite" "github.com/steveyegge/beads/internal/storage/sqlite"
) )
@@ -48,7 +49,7 @@ and database file. Optionally specify a custom issue prefix.`,
// Use global dbPath if set via --db flag or BEADS_DB env var, otherwise default to .beads/beads.db // Use global dbPath if set via --db flag or BEADS_DB env var, otherwise default to .beads/beads.db
initDBPath := dbPath initDBPath := dbPath
if initDBPath == "" { if initDBPath == "" {
initDBPath = filepath.Join(".beads", "beads.db") initDBPath = filepath.Join(".beads", beads.CanonicalDatabaseName)
} }
// Migrate old database files if they exist // Migrate old database files if they exist

View File

@@ -10,6 +10,7 @@ import (
"github.com/fatih/color" "github.com/fatih/color"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/steveyegge/beads"
"github.com/steveyegge/beads/internal/storage/sqlite" "github.com/steveyegge/beads/internal/storage/sqlite"
_ "modernc.org/sqlite" _ "modernc.org/sqlite"
) )
@@ -73,7 +74,7 @@ This command:
} }
// Check if beads.db exists and is current // Check if beads.db exists and is current
targetPath := filepath.Join(beadsDir, "beads.db") targetPath := filepath.Join(beadsDir, beads.CanonicalDatabaseName)
var currentDB *dbInfo var currentDB *dbInfo
var oldDBs []*dbInfo var oldDBs []*dbInfo
@@ -271,7 +272,7 @@ This command:
if jsonOutput { if jsonOutput {
outputJSON(map[string]interface{}{ outputJSON(map[string]interface{}{
"status": "success", "status": "success",
"current_database": "beads.db", "current_database": beads.CanonicalDatabaseName,
"version": Version, "version": Version,
"migrated": needsMigration, "migrated": needsMigration,
"version_updated": needsVersionUpdate, "version_updated": needsVersionUpdate,