Fix bd-179: Derive prefix from database filename when config missing

- Add dbPath field to SQLiteStorage to track database file path
- Create derivePrefixFromPath() helper to extract prefix from filename
- Update ID generation in CreateIssue() and generateBatchIDs() to use filename fallback
- Fix tests to explicitly set issue_prefix config for bd- prefixed tests

When config doesn't have issue_prefix set, bd now correctly derives it from
the database filename (e.g., wy-.db -> wy) instead of always defaulting to 'bd'.

Fixes: bd-179
This commit is contained in:
Steve Yegge
2025-10-20 22:18:08 -07:00
parent b6ba8e2e0c
commit db8efd534c
4 changed files with 55 additions and 10 deletions

View File

@@ -30,6 +30,11 @@ func TestLazyCounterInitialization(t *testing.T) {
ctx := context.Background()
// Set the issue prefix to "bd" for this test
if err := store.SetConfig(ctx, "issue_prefix", "bd"); err != nil {
t.Fatalf("Failed to set issue_prefix: %v", err)
}
// Create some issues with explicit IDs (simulating import)
existingIssues := []string{"bd-5", "bd-10", "bd-15"}
for _, id := range existingIssues {
@@ -182,6 +187,11 @@ func TestCounterInitializationFromExisting(t *testing.T) {
ctx := context.Background()
// Set the issue prefix to "bd" for this test
if err := store.SetConfig(ctx, "issue_prefix", "bd"); err != nil {
t.Fatalf("Failed to set issue_prefix: %v", err)
}
// Create issues with explicit IDs, out of order
explicitIDs := []string{"bd-5", "bd-100", "bd-42", "bd-7"}
for _, id := range explicitIDs {

View File

@@ -188,6 +188,13 @@ func TestMigrateIssueCountersTableEmptyDB(t *testing.T) {
// Create first issue - should work fine
ctx := context.Background()
// Set the issue prefix to "bd" for this test
err = store.SetConfig(ctx, "issue_prefix", "bd")
if err != nil {
t.Fatalf("Failed to set issue_prefix: %v", err)
}
issue := &types.Issue{
Title: "First issue",
Status: types.StatusOpen,
@@ -225,6 +232,13 @@ func TestMigrateIssueCountersTableIdempotent(t *testing.T) {
// Create some issues
ctx := context.Background()
// Set the issue prefix to "bd" for this test
err = store1.SetConfig(ctx, "issue_prefix", "bd")
if err != nil {
t.Fatalf("Failed to set issue_prefix: %v", err)
}
issue := &types.Issue{
Title: "Test issue",
Status: types.StatusOpen,

View File

@@ -18,7 +18,8 @@ import (
// SQLiteStorage implements the Storage interface using SQLite
type SQLiteStorage struct {
db *sql.DB
db *sql.DB
dbPath string
}
// New creates a new SQLite storage backend
@@ -96,7 +97,8 @@ func New(path string) (*SQLiteStorage, error) {
}
return &SQLiteStorage{
db: db,
db: db,
dbPath: path,
}, nil
}
@@ -472,6 +474,22 @@ func (s *SQLiteStorage) SyncAllCounters(ctx context.Context) error {
return nil
}
// derivePrefixFromPath derives the issue prefix from the database file path
// Database file is named like ".beads/wy-.db" -> prefix should be "wy"
func derivePrefixFromPath(dbPath string) string {
dbFileName := filepath.Base(dbPath)
// Strip ".db" extension
dbFileName = strings.TrimSuffix(dbFileName, ".db")
// Strip trailing hyphen (if any)
prefix := strings.TrimSuffix(dbFileName, "-")
// Fallback if filename is weird
if prefix == "" {
prefix = "bd"
}
return prefix
}
// CreateIssue creates a new issue
func (s *SQLiteStorage) CreateIssue(ctx context.Context, issue *types.Issue, actor string) error {
// Validate issue before creating
@@ -515,11 +533,12 @@ func (s *SQLiteStorage) CreateIssue(ctx context.Context, issue *types.Issue, act
// Generate ID if not set (inside transaction to prevent race conditions)
if issue.ID == "" {
// Get prefix from config, default to "bd"
// Get prefix from config
var prefix string
err := conn.QueryRowContext(ctx, `SELECT value FROM config WHERE key = ?`, "issue_prefix").Scan(&prefix)
if err == sql.ErrNoRows || prefix == "" {
prefix = "bd"
// Config not set - derive prefix from database filename
prefix = derivePrefixFromPath(s.dbPath)
} else if err != nil {
return fmt.Errorf("failed to get config: %w", err)
}
@@ -631,7 +650,7 @@ func validateBatchIssues(issues []*types.Issue) error {
}
// generateBatchIDs generates IDs for all issues that need them atomically
func generateBatchIDs(ctx context.Context, conn *sql.Conn, issues []*types.Issue) error {
func generateBatchIDs(ctx context.Context, conn *sql.Conn, issues []*types.Issue, dbPath string) error {
// Count how many issues need IDs
needIDCount := 0
for _, issue := range issues {
@@ -648,7 +667,8 @@ func generateBatchIDs(ctx context.Context, conn *sql.Conn, issues []*types.Issue
var prefix string
err := conn.QueryRowContext(ctx, `SELECT value FROM config WHERE key = ?`, "issue_prefix").Scan(&prefix)
if err == sql.ErrNoRows || prefix == "" {
prefix = "bd"
// Config not set - derive prefix from database filename
prefix = derivePrefixFromPath(dbPath)
} else if err != nil {
return fmt.Errorf("failed to get config: %w", err)
}
@@ -842,7 +862,7 @@ func (s *SQLiteStorage) CreateIssues(ctx context.Context, issues []*types.Issue,
}()
// Phase 3: Generate IDs for issues that need them
if err := generateBatchIDs(ctx, conn, issues); err != nil {
if err := generateBatchIDs(ctx, conn, issues, s.dbPath); err != nil {
return err
}