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

@@ -3,7 +3,7 @@
{"id":"bd-100","title":"Add 'bd repos' command for multi-repo aggregation","description":"When using daemon in multi-repo mode, users need commands to view/manage work across all active repositories.\n\nAdd 'bd repos' subcommand with:\n\n1. bd repos list\n - Show all repositories daemon has cached\n - Display: path, prefix, issue count, last activity\n - Example output:\n ~/src/project1 [p1-] 45 issues (active)\n ~/src/project2 [p2-] 12 issues (2m ago)\n\n2. bd repos ready --all \n - Aggregate ready work across all repos\n - Group by repo or show combined list\n - Support priority/assignee filters\n\n3. bd repos stats\n - Combined statistics across all repos\n - Total issues, breakdown by status/priority\n - Per-repo breakdown\n\n4. bd repos clear-cache\n - Close all cached storage connections\n - Useful for freeing resources\n\nImplementation notes:\n- Requires daemon to track active storage instances\n- May need RPC protocol additions for multi-repo queries\n- Should gracefully handle repos that no longer exist\n\nDepends on: Global daemon flag (makes this more useful)\n\nContext: This provides the UX layer on top of existing multi-repo support. The daemon can already serve multiple repos - this makes it easy to work with them.","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-10-17T20:43:49.816998-07:00","updated_at":"2025-10-20T16:02:06.011546-07:00","closed_at":"2025-10-18T00:04:42.197247-07:00"}
{"id":"bd-101","title":"Add daemon auto-start on first use","description":"Currently users must manually start daemon with 'bd daemon'. For better UX, auto-start daemon when first bd command is run.\n\nImplementation:\n\n1. In PersistentPreRun, check if daemon is running\n2. If not, check if auto-start is enabled (default: true)\n3. Start daemon with appropriate flags (--global if configured)\n4. Wait for socket to be ready (with timeout)\n5. Retry connection to newly-started daemon\n6. Silently fail back to direct mode if daemon won't start\n\nConfiguration:\n- BEADS_AUTO_START_DAEMON env var (default: true)\n- --no-auto-daemon flag to disable\n- Config file option: auto_start_daemon = true\n\nSafety considerations:\n- Don't auto-start if daemon failed recently (exponential backoff)\n- Log auto-start to daemon.log\n- Clear error messages if auto-start fails\n- Never auto-start if --no-daemon flag is set\n\nBenefits:\n- Zero-configuration experience\n- Daemon benefits (speed, multi-repo) automatic\n- Still supports direct mode as fallback\n\nDepends on: Global daemon flag would make this more useful","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-10-17T20:43:50.961453-07:00","updated_at":"2025-10-20T16:02:06.011895-07:00","closed_at":"2025-10-17T23:33:57.173903-07:00"}
{"id":"bd-102","title":"Add workspace config file for multi-repo management (optional enhancement)","description":"For users who want explicit control over multi-repo setup without daemon, add optional workspace config file.\n\nConfig file: ~/.beads/workspaces.toml\n\nExample:\n[workspaces]\ncurrent = \"global\"\n\n[workspace.global]\ndb = \"~/.beads/global.db\"\ndescription = \"System-wide tasks\"\n\n[workspace.project1] \ndb = \"~/src/project1/.beads/db.sqlite\"\ndescription = \"Main product\"\n\n[workspace.project2]\ndb = \"~/src/project2/.beads/db.sqlite\"\ndescription = \"Internal tools\"\n\nCommands:\nbd workspace list # Show all workspaces\nbd workspace add NAME PATH # Add workspace\nbd workspace remove NAME # Remove workspace \nbd workspace use NAME # Switch active workspace\nbd workspace current # Show current workspace\nbd --workspace NAME \u003ccommand\u003e # Override for single command\n\nImplementation:\n- Load config in PersistentPreRun\n- Override dbPath based on current workspace\n- Store workspace state in config file\n- Support both workspace config AND auto-discovery\n- Workspace config takes precedence over auto-discovery\n\nPriority rationale:\n- Priority 3 (low) because daemon approach already solves this\n- Only implement if users request explicit workspace management\n- Adds complexity vs daemon's automatic discovery\n\nAlternative: Users can use BEADS_DB env var for manual workspace switching today.","status":"closed","priority":3,"issue_type":"feature","created_at":"2025-10-17T20:43:52.348572-07:00","updated_at":"2025-10-20T16:04:27.216482-07:00","closed_at":"2025-10-20T16:04:27.216482-07:00"}
{"id":"bd-103","title":"Add cross-repo issue references (future enhancement)","description":"Support referencing issues across different beads repositories. Useful for tracking dependencies between separate projects.\n\nProposed syntax:\n- Local reference: bd-100 (current behavior)\n- Cross-repo by path: ~/src/other-project#bd-456\n- Cross-repo by workspace name: @project2:bd-789\n\nUse cases:\n1. Frontend project depends on backend API issue\n2. Shared library changes blocking multiple projects\n3. System administrator tracking work across machines\n4. Monorepo with separate beads databases per component\n\nImplementation challenges:\n- Storage layer needs to query external databases\n- Dependency resolution across repos\n- What if external repo not available?\n- How to handle in JSONL export/import?\n- Security: should repos be able to read others?\n\nDesign questions to resolve first:\n1. Read-only references vs full cross-repo dependencies?\n2. How to handle repo renames/moves?\n3. Absolute paths vs workspace names vs git remotes?\n4. Should bd-43 auto-discover related repos?\n\nRecommendation: \n- Gather user feedback first\n- Start with read-only references\n- Implement as plugin/extension?\n\nContext: This is mentioned in bd-43 as approach #2. Much more complex than daemon multi-repo approach. Only implement if there's strong user demand.\n\nPriority: Backlog (4) - wait for user feedback before designing","status":"open","priority":4,"issue_type":"feature","created_at":"2025-10-17T20:43:54.04594-07:00","updated_at":"2025-10-20T16:02:06.012571-07:00"}
{"id":"bd-103","title":"Add cross-repo issue references (future enhancement)","description":"Support referencing issues across different beads repositories. Useful for tracking dependencies between separate projects.\n\nProposed syntax:\n- Local reference: bd-100 (current behavior)\n- Cross-repo by path: ~/src/other-project#bd-456\n- Cross-repo by workspace name: @project2:bd-789\n\nUse cases:\n1. Frontend project depends on backend API issue\n2. Shared library changes blocking multiple projects\n3. System administrator tracking work across machines\n4. Monorepo with separate beads databases per component\n\nImplementation challenges:\n- Storage layer needs to query external databases\n- Dependency resolution across repos\n- What if external repo not available?\n- How to handle in JSONL export/import?\n- Security: should repos be able to read others?\n\nDesign questions to resolve first:\n1. Read-only references vs full cross-repo dependencies?\n2. How to handle repo renames/moves?\n3. Absolute paths vs workspace names vs git remotes?\n4. Should bd-43 auto-discover related repos?\n\nRecommendation: \n- Gather user feedback first\n- Start with read-only references\n- Implement as plugin/extension?\n\nContext: This is mentioned in bd-43 as approach #2. Much more complex than daemon multi-repo approach. Only implement if there's strong user demand.\n\nPriority: Backlog (4) - wait for user feedback before designing","status":"closed","priority":4,"issue_type":"feature","created_at":"2025-10-17T20:43:54.04594-07:00","updated_at":"2025-10-20T22:00:31.966891-07:00","closed_at":"2025-10-20T22:00:31.966891-07:00"}
{"id":"bd-104","title":"Add batch deletion command for issues","description":"Support deleting multiple issues efficiently instead of one at a time.\n\n**Use Cases:**\n- Cleaning up duplicate/spam issues (e.g., bd-83 to bd-93 watchdog spam)\n- Removing test-only issues after feature removal\n- Bulk cleanup of obsolete/spurious bugs\n- Renumbering prep: delete ranges before compaction\n\n**Proposed Syntax Options:**\n\n**Option 1: Multiple IDs as arguments**\n```bash\nbd delete vc-1 vc-2 vc-3 --force\nbd delete vc-{1..20} --force # Shell expansion\n```\n\n**Option 2: Read from file (RECOMMENDED)**\n```bash\nbd delete --from-file deletions.txt --force --dry-run # Preview\nbd delete --from-file deletions.txt --force # Execute\n# File format: one issue ID per line\n```\n\n**Option 3: Query-based deletion**\n```bash\nbd delete --where \"priority=3 AND type=chore\" --force\nbd delete --label test-only --force\nbd delete --prefix bd- --status open --force\n```\n\n**Must-Have Features:**\n\n1. **Dry-run mode**: `--dry-run` to preview what would be deleted\n - Show issue IDs, titles, dependency counts\n - Warn about issues with dependents\n\n2. **Dependency handling**:\n - `--cascade`: Delete dependents recursively\n - `--force`: Delete even if dependents exist (orphans them)\n - Default: Fail if any issue has dependents\n\n3. **Summary output**:\n ```\n Deleted 162 issues\n Removed 347 dependencies\n Removed 89 labels\n Orphaned 5 issues (use --cascade to delete)\n ```\n\n4. **Transaction safety**: All-or-nothing for file/query input\n - Either all deletions succeed or none do\n - Rollback on error\n\n**Nice-to-Have Features:**\n\n1. **Interactive confirmation** for large batches (\u003e10 issues)\n ```\n About to delete 162 issues. Continue? [y/N]\n (Use --force to skip confirmation)\n ```\n\n2. **Progress indicator** for large batches (\u003e50 deletions)\n ```\n Deleting issues... [####------] 42/162 (26%)\n ```\n\n3. **Undo support**:\n ```bash\n bd undelete --last-batch # Restore from snapshots\n bd undelete bd-83 # Restore single issue\n ```\n\n**Implementation Notes:**\n\n- Leverage existing `DeleteIssue()` in storage layer\n- Wrap in transaction for atomicity\n- Consider adding `DeleteIssues(ctx, []string)` for efficiency\n- May need to query dependents before deletion\n- File format should support comments (#) and blank lines\n- JSON output mode should list all deleted IDs\n\n**Example Workflow:**\n```bash\n# Identify issues to delete\nbd list --label test-only --json | jq -r '.[].id' \u003e /tmp/delete.txt\n\n# Preview deletion\nbd delete --from-file /tmp/delete.txt --dry-run\n\n# Execute with cascade\nbd delete --from-file /tmp/delete.txt --cascade --force\n\n# Verify\nbd stats\n```\n\n**Security Considerations:**\n- Require explicit `--force` flag to prevent accidents\n- Warn when deleting issues with dependencies\n- Log deletions to audit trail\n- Consider requiring confirmation for \u003e100 deletions even with --force\n\n**Requested by:** Another agent during cleanup of bd-83 to bd-93 watchdog spam","notes":"Fixed critical issues found in code review:\n1. Dry-run mode now properly uses dryRun parameter instead of deleting data\n2. Text references are pre-collected before deletion so they update correctly\n3. Added orphan deduplication to prevent duplicate IDs\n4. Added rows.Err() checks in all row iteration loops\n5. Updated defer to ignore rollback error per Go best practices","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-10-17T20:49:30.921943-07:00","updated_at":"2025-10-20T16:02:06.012905-07:00","closed_at":"2025-10-17T21:03:29.165515-07:00"}
{"id":"bd-105","title":"bd list shows 0 issues despite database containing 115 issues","description":"When running 'bd list --status all' it shows 'Found 0 issues' even though 'bd stats' shows 115 total issues and 'sqlite3 .beads/vc.db \"SELECT COUNT(*) FROM issues\"' returns 115.\n\nReproduction:\n1. cd ~/src/vc/vc\n2. bd stats # Shows 115 issues\n3. bd list --status all # Shows 0 issues\n4. sqlite3 .beads/vc.db 'SELECT COUNT(*) FROM issues;' # Shows 115\n\nExpected: bd list should show all 115 issues\nActual: Shows 'Found 0 issues:'\n\nThis occurs with both /opt/homebrew/bin/bd (v0.9.9) and ~/src/vc/adar/beads/bd (v0.9.10)","design":"Possible causes:\n- Default filter excluding all issues\n- Database query issue in list command\n- Auto-discovery finding wrong database (but stats works?)\n- Recent deletion operation corrupted some index","acceptance_criteria":"bd list --status all shows all issues that bd stats counts","status":"closed","priority":0,"issue_type":"bug","created_at":"2025-10-17T21:19:08.225181-07:00","updated_at":"2025-10-20T16:02:06.013254-07:00","closed_at":"2025-10-17T21:55:40.788625-07:00"}
{"id":"bd-106","title":"Confusing version mismatch warnings with contradictory messages","description":"The version mismatch warning shows contradictory messages depending on which binary version is used:\n\nWhen using v0.9.10 binary with v0.9.9 database:\n'Your bd binary (v0.9.10) differs from the database version (v0.9.9)'\n'Your binary appears to be OUTDATED.'\n\nWhen using v0.9.9 binary with v0.9.10 database:\n'Your bd binary (v0.9.9) differs from the database version (v0.9.10)'\n'Your binary appears NEWER than the database.'\n\nThe first message is incorrect - v0.9.10 \u003e v0.9.9, so the binary is NEWER, not outdated.\n\nReproduction:\n1. Use ~/src/vc/adar/beads/bd (v0.9.10) with a v0.9.9 database\n2. Observe warning says binary is OUTDATED when it's actually newer\n\nExpected: Correct version comparison\nActual: Inverted comparison logic","design":"Fix version comparison in warning message generation. Should compare semantic versions correctly.","acceptance_criteria":"Warning correctly identifies which component (binary vs database) is newer/older","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-10-17T21:19:19.540274-07:00","updated_at":"2025-10-20T16:02:06.013528-07:00","closed_at":"2025-10-17T22:14:27.015397-07:00"}
@@ -78,11 +78,12 @@
{"id":"bd-17","title":"Improve session management","description":"Current session management is basic. Need to improve with better expiration handling.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-20T16:02:06.030687-07:00","closed_at":"2025-10-16T10:07:34.005199-07:00"}
{"id":"bd-172","title":"Fix MCP server None/null crashes in list/ready/stats","description":"","status":"closed","priority":0,"issue_type":"bug","created_at":"2025-10-19T23:22:56.591289-07:00","updated_at":"2025-10-20T16:02:06.03089-07:00","closed_at":"2025-10-19T23:23:25.755169-07:00"}
{"id":"bd-173","title":"Add 'bd onboard' command to configure AGENTS.md for AI agents","description":"Create a 'bd onboard' command that automatically sets up AGENTS.md (and adds a note to CLAUDE.md if present) with instructions for AI agents to use beads instead of markdown TODOs.\n\nProblem: Users report that AI agents get confused between beads issue tracking and markdown-based planning, leading to duplicate tracking systems and confusion about which to use.\n\nSolution: Add 'bd onboard' command that:\n1. Appends (or creates) AGENTS.md with beads workflow instructions\n2. Includes command examples, issue types, priorities, and workflow\n3. Adds note to CLAUDE.md if it exists, directing agents to use beads\n4. Makes it clear agents should abandon markdown TODOs in favor of bd\n5. Idempotent - safe to run multiple times\n\nThe onboarding content should emphasize:\n- Use bd for ALL task tracking, not markdown TODOs\n- How to check ready work, create/update/close issues\n- When to use discovered-from dependencies\n- Priority and issue type conventions\n- Auto-sync behavior\n\nThis will reduce onboarding friction and prevent agents from creating parallel tracking systems.","status":"closed","priority":1,"issue_type":"feature","created_at":"2025-10-20T14:34:52.483607-07:00","updated_at":"2025-10-20T16:02:06.031106-07:00","closed_at":"2025-10-20T14:57:39.014109-07:00"}
{"id":"bd-174","title":"Test auto-export timing","description":"","status":"open","priority":4,"issue_type":"task","created_at":"2025-10-20T16:00:58.756761-07:00","updated_at":"2025-10-20T16:02:06.031313-07:00"}
{"id":"bd-175","title":"Test real auto-export","description":"","status":"open","priority":4,"issue_type":"task","created_at":"2025-10-20T16:01:16.192532-07:00","updated_at":"2025-10-20T16:02:06.03196-07:00"}
{"id":"bd-174","title":"Test auto-export timing","description":"","status":"closed","priority":4,"issue_type":"task","created_at":"2025-10-20T16:00:58.756761-07:00","updated_at":"2025-10-20T22:00:31.964329-07:00","closed_at":"2025-10-20T22:00:31.964329-07:00"}
{"id":"bd-175","title":"Test real auto-export","description":"","status":"closed","priority":4,"issue_type":"task","created_at":"2025-10-20T16:01:16.192532-07:00","updated_at":"2025-10-20T22:00:31.967571-07:00","closed_at":"2025-10-20T22:00:31.967571-07:00"}
{"id":"bd-176","title":"Fix cross-platform signal handling in daemon.go","description":"Replace hard-coded Unix signals with platform-aware helpers: signal.Notify with daemonSignals, use isReloadSignal(), use sendStopSignal() instead of process.Signal(syscall.SIGTERM)","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-10-20T21:09:05.317357-07:00","updated_at":"2025-10-20T21:24:56.70424-07:00","closed_at":"2025-10-20T21:24:56.70424-07:00"}
{"id":"bd-177","title":"Fix cross-platform process management in daemon.go","description":"Replace direct syscall.SysProcAttr assignments with configureDaemonProcess() helper in migrateToGlobalDaemon() and other start/stop flows","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-10-20T21:09:09.213639-07:00","updated_at":"2025-10-20T21:27:10.908884-07:00","closed_at":"2025-10-20T21:27:10.908884-07:00"}
{"id":"bd-178","title":"Add Windows CI build to GitHub Actions","description":"Add windows-latest job that builds bd.exe and runs unit tests to catch cross-platform issues","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-20T21:09:12.978711-07:00","updated_at":"2025-10-20T21:27:40.120681-07:00","closed_at":"2025-10-20T21:27:40.120681-07:00"}
{"id":"bd-179","title":"bd uses wrong default prefix when database filename indicates different prefix","description":"When creating issues without explicit --id flag, bd defaults to 'bd-' prefix even when the database filename and existing issues use a different prefix (e.g., 'wy-').","design":"## Bug Report\n\n**Environment:**\n- bd version: 0.9.11 (based on metadata)\n- Database: .beads/wy-.db\n- Directory: /Users/stevey/ai/flutter/wyvern\n\n**Observed Behavior:**\nWhen running `bd create task \"title\"` without --id flag, new issues get assigned `bd-*` IDs even though:\n- Database filename is `.beads/wy-.db` (implies wy- prefix)\n- All existing issues use `wy-*` prefix (wy-1 through wy-77)\n- No prefix metadata stored in database\n\n**Evidence:**\n```sql\nsqlite3 .beads/wy-.db \"SELECT * FROM issue_counters;\"\nwy|72\nbd|29\n```\n\nThe bd counter was incremented when I accidentally created bd-23 through bd-29 (now deleted).\n\n**Expected Behavior:**\nbd should derive the default prefix from:\n1. Database filename (`wy-.db` → `wy-` prefix), OR\n2. Most recently used prefix in the database, OR \n3. Prefix stored in metadata table\n\nAccording to `bd init --help`:\n\u003e -p, --prefix string Issue prefix (default: current directory name)\n\nBut database filename convention appears to be `\u003cprefix\u003e.db`, so wy-.db should mean wy- is the intended prefix.\n\n**Workaround:**\nUse explicit `--id` flag: `bd create task \"title\" --id wy-73`\n\n**Reproduction:**\n```bash\n# In a directory with .beads/wy-.db containing wy-* issues\nbd create task \"test\"\n# Creates bd-* issue instead of wy-* issue\n```\n\n**Suggested Fix:**\n1. Store the intended prefix in metadata table during `bd init`\n2. When creating issues, check metadata for preferred prefix\n3. Fallback to extracting prefix from database filename\n4. Last resort: use directory name\n\n**Related Code:**\nLikely in issue counter/ID generation logic where new IDs are assigned.","status":"in_progress","priority":2,"issue_type":"task","created_at":"2025-10-20T22:10:24.611471-07:00","updated_at":"2025-10-20T22:12:12.098428-07:00"}
{"id":"bd-18","title":"Fix: bd init --prefix test -q flag not recognized","description":"The init command doesn't recognize the -q flag. When running 'bd init --prefix test -q', it fails silently or behaves unexpectedly. The flag should either be implemented for quiet mode or removed from documentation if not supported.","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-20T16:02:06.032275-07:00","closed_at":"2025-10-17T00:09:18.921816-07:00"}
{"id":"bd-19","title":"Implement storage driver interface for pluggable backends","description":"Create abstraction layer for storage to support multiple backends (SQLite, Postgres, Turso, in-memory testing, etc.).\n\n**Current state:** All storage logic hardcoded to SQLite in internal/storage/sqlite/sqlite.go\n\n**Proposed design:**\n\n```go\n// internal/storage/storage.go\ntype Store interface {\n // Issue CRUD\n CreateIssue(issue *Issue) error\n GetIssue(id string) (*Issue, error)\n UpdateIssue(id string, updates *Issue) error\n DeleteIssue(id string) error\n ListIssues(filter *Filter) ([]*Issue, error)\n \n // Dependencies\n AddDependency(from, to string, depType DependencyType) error\n RemoveDependency(from, to string, depType DependencyType) error\n GetDependencies(id string) ([]*Dependency, error)\n \n // Counters, stats\n GetNextID(prefix string) (string, error)\n GetStats() (*Stats, error)\n \n Close() error\n}\n```\n\n**Benefits:**\n- Better testing (mock/in-memory stores)\n- Future flexibility (Postgres, cloud APIs, etc.)\n- Clean architecture (business logic decoupled from storage)\n- Enable Turso or other backends without refactoring everything\n\n**Implementation steps:**\n1. Define Store interface in internal/storage/storage.go\n2. Refactor SQLiteStore to implement interface\n3. Update all commands to use interface, not concrete type\n4. Add MemoryStore for testing\n5. Add driver selection via config (storage.driver = sqlite|turso|postgres)\n6. Update tests to use interface\n\n**Note:** This is valuable even without adopting Turso. Good architecture practice.\n\n**Context:** From GH issue #2 RFC evaluation. Driver interface is low-cost, high-value regardless of whether we add alternative backends.","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-20T16:02:06.032562-07:00","closed_at":"2025-10-17T23:46:22.447301-07:00"}
{"id":"bd-2","title":"Sub-task under A","description":"","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-20T16:02:06.032798-07:00","closed_at":"2025-10-16T10:07:34.130096-07:00"}

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
}