Files
beads/internal/storage/provider.go
Peter Chanthamynavong c11fa799be fix(orphans): honor --db flag for cross-repo orphan detection (#1200)
* fix(orphans): honor --db flag for cross-repo orphan detection

Problem:
- `bd orphans --db /path` ignored the --db flag entirely
- FindOrphanedIssues() hardcoded local .beads/ directory

Solution:
- Introduce IssueProvider interface for abstract issue lookup
- Add StorageProvider adapter wrapping Storage instances
- Update FindOrphanedIssues to accept provider instead of path
- Wire orphans command to create provider from --db flag

Closes: steveyegge/beads#1196

* test(orphans): add cross-repo and provider tests for --db flag fix

- Add TestFindOrphanedIssues_WithMockProvider (table-driven, UT-01 through UT-09)
- Add TestFindOrphanedIssues_CrossRepo (validates --db flag honored)
- Add TestFindOrphanedIssues_LocalProvider (backward compat RT-01)
- Add TestFindOrphanedIssues_ProviderError (error handling UT-07)
- Add TestFindOrphanedIssues_IntegrationCrossRepo (IT-02 full)
- Add TestLocalProvider_* unit tests

Coverage for IssueProvider interface and cross-repo orphan detection.

* docs: add bd orphans command to CLI reference

Document the orphan detection command including the cross-repo
workflow enabled by the --db flag fix in this PR.
2026-01-21 19:52:31 -08:00

60 lines
1.8 KiB
Go

// Package storage defines the interface for issue storage backends.
package storage
import (
"context"
"github.com/steveyegge/beads/internal/types"
)
// StorageProvider wraps a Storage interface to provide IssueProvider functionality.
// This adapts the full Storage interface to the minimal IssueProvider interface
// needed for orphan detection.
type StorageProvider struct {
storage Storage
prefix string // Cached prefix (empty = not cached yet)
}
// NewStorageProvider creates an IssueProvider backed by a Storage instance.
func NewStorageProvider(s Storage) *StorageProvider {
return &StorageProvider{storage: s}
}
// GetOpenIssues returns issues that are open or in_progress.
func (p *StorageProvider) GetOpenIssues(ctx context.Context) ([]*types.Issue, error) {
// Use SearchIssues with empty query and status filter
// We need to search for both "open" and "in_progress" issues
openStatus := types.StatusOpen
openIssues, err := p.storage.SearchIssues(ctx, "", types.IssueFilter{Status: &openStatus})
if err != nil {
return nil, err
}
inProgressStatus := types.StatusInProgress
inProgressIssues, err := p.storage.SearchIssues(ctx, "", types.IssueFilter{Status: &inProgressStatus})
if err != nil {
return nil, err
}
// Combine results
return append(openIssues, inProgressIssues...), nil
}
// GetIssuePrefix returns the configured issue prefix.
func (p *StorageProvider) GetIssuePrefix() string {
// Cache the prefix on first access
if p.prefix == "" {
ctx := context.Background()
prefix, err := p.storage.GetConfig(ctx, "issue_prefix")
if err != nil || prefix == "" {
p.prefix = "bd" // default
} else {
p.prefix = prefix
}
}
return p.prefix
}
// Ensure StorageProvider implements types.IssueProvider
var _ types.IssueProvider = (*StorageProvider)(nil)