bd cleanup: Remove 493 closed issues
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -12,23 +12,34 @@ import (
|
|||||||
"github.com/steveyegge/beads/internal/types"
|
"github.com/steveyegge/beads/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// isJSONLNewer checks if JSONL file is newer than database file.
|
||||||
|
// Returns true if JSONL is newer, false otherwise.
|
||||||
|
func isJSONLNewer(jsonlPath string) bool {
|
||||||
|
jsonlInfo, jsonlStatErr := os.Stat(jsonlPath)
|
||||||
|
if jsonlStatErr != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
beadsDir := filepath.Dir(jsonlPath)
|
||||||
|
dbPath := filepath.Join(beadsDir, "beads.db")
|
||||||
|
dbInfo, dbStatErr := os.Stat(dbPath)
|
||||||
|
if dbStatErr != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonlInfo.ModTime().After(dbInfo.ModTime())
|
||||||
|
}
|
||||||
|
|
||||||
// validatePreExport performs integrity checks before exporting database to JSONL.
|
// validatePreExport performs integrity checks before exporting database to JSONL.
|
||||||
// Returns error if critical issues found that would cause data loss.
|
// Returns error if critical issues found that would cause data loss.
|
||||||
func validatePreExport(ctx context.Context, store storage.Storage, jsonlPath string) error {
|
func validatePreExport(ctx context.Context, store storage.Storage, jsonlPath string) error {
|
||||||
// Check if JSONL is newer than database - if so, must import first
|
// Check if JSONL is newer than database - if so, must import first
|
||||||
jsonlInfo, jsonlStatErr := os.Stat(jsonlPath)
|
if isJSONLNewer(jsonlPath) {
|
||||||
if jsonlStatErr == nil {
|
return fmt.Errorf("refusing to export: JSONL is newer than database (import first to avoid data loss)")
|
||||||
beadsDir := filepath.Dir(jsonlPath)
|
|
||||||
dbPath := filepath.Join(beadsDir, "beads.db")
|
|
||||||
dbInfo, dbStatErr := os.Stat(dbPath)
|
|
||||||
if dbStatErr == nil {
|
|
||||||
// If JSONL is newer, refuse export - caller must import first
|
|
||||||
if jsonlInfo.ModTime().After(dbInfo.ModTime()) {
|
|
||||||
return fmt.Errorf("refusing to export: JSONL is newer than database (import first to avoid data loss)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jsonlInfo, jsonlStatErr := os.Stat(jsonlPath)
|
||||||
|
|
||||||
// Get database issue count (fast path with COUNT(*) if available)
|
// Get database issue count (fast path with COUNT(*) if available)
|
||||||
dbCount, err := countDBIssuesFast(ctx, store)
|
dbCount, err := countDBIssuesFast(ctx, store)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -339,18 +339,19 @@ var listCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if jsonOutput {
|
if jsonOutput {
|
||||||
// Populate labels for JSON output
|
// Get labels and dependency counts in bulk (single query instead of N queries)
|
||||||
for _, issue := range issues {
|
|
||||||
issue.Labels, _ = store.GetLabels(ctx, issue.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get dependency counts in bulk (single query instead of N queries)
|
|
||||||
issueIDs := make([]string, len(issues))
|
issueIDs := make([]string, len(issues))
|
||||||
for i, issue := range issues {
|
for i, issue := range issues {
|
||||||
issueIDs[i] = issue.ID
|
issueIDs[i] = issue.ID
|
||||||
}
|
}
|
||||||
|
labelsMap, _ := store.GetLabelsForIssues(ctx, issueIDs)
|
||||||
depCounts, _ := store.GetDependencyCounts(ctx, issueIDs)
|
depCounts, _ := store.GetDependencyCounts(ctx, issueIDs)
|
||||||
|
|
||||||
|
// Populate labels for JSON output
|
||||||
|
for _, issue := range issues {
|
||||||
|
issue.Labels = labelsMap[issue.ID]
|
||||||
|
}
|
||||||
|
|
||||||
// Build response with counts
|
// Build response with counts
|
||||||
issuesWithCounts := make([]*types.IssueWithCounts, len(issues))
|
issuesWithCounts := make([]*types.IssueWithCounts, len(issues))
|
||||||
for i, issue := range issues {
|
for i, issue := range issues {
|
||||||
@@ -368,12 +369,18 @@ var listCmd = &cobra.Command{
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load labels in bulk for display
|
||||||
|
issueIDs := make([]string, len(issues))
|
||||||
|
for i, issue := range issues {
|
||||||
|
issueIDs[i] = issue.ID
|
||||||
|
}
|
||||||
|
labelsMap, _ := store.GetLabelsForIssues(ctx, issueIDs)
|
||||||
|
|
||||||
if longFormat {
|
if longFormat {
|
||||||
// Long format: multi-line with details
|
// Long format: multi-line with details
|
||||||
fmt.Printf("\nFound %d issues:\n\n", len(issues))
|
fmt.Printf("\nFound %d issues:\n\n", len(issues))
|
||||||
for _, issue := range issues {
|
for _, issue := range issues {
|
||||||
// Load labels for display
|
labels := labelsMap[issue.ID]
|
||||||
labels, _ := store.GetLabels(ctx, issue.ID)
|
|
||||||
|
|
||||||
fmt.Printf("%s [P%d] [%s] %s\n", issue.ID, issue.Priority, issue.IssueType, issue.Status)
|
fmt.Printf("%s [P%d] [%s] %s\n", issue.ID, issue.Priority, issue.IssueType, issue.Status)
|
||||||
fmt.Printf(" %s\n", issue.Title)
|
fmt.Printf(" %s\n", issue.Title)
|
||||||
@@ -388,8 +395,7 @@ var listCmd = &cobra.Command{
|
|||||||
} else {
|
} else {
|
||||||
// Compact format: one line per issue
|
// Compact format: one line per issue
|
||||||
for _, issue := range issues {
|
for _, issue := range issues {
|
||||||
// Load labels for display
|
labels := labelsMap[issue.ID]
|
||||||
labels, _ := store.GetLabels(ctx, issue.ID)
|
|
||||||
|
|
||||||
labelsStr := ""
|
labelsStr := ""
|
||||||
if len(labels) > 0 {
|
if len(labels) > 0 {
|
||||||
|
|||||||
@@ -126,6 +126,16 @@ Use --merge to merge the sync branch back to main branch.`,
|
|||||||
if dryRun {
|
if dryRun {
|
||||||
fmt.Println("→ [DRY RUN] Would export pending changes to JSONL")
|
fmt.Println("→ [DRY RUN] Would export pending changes to JSONL")
|
||||||
} else {
|
} else {
|
||||||
|
// Smart conflict resolution: if JSONL is newer, auto-import first
|
||||||
|
if isJSONLNewer(jsonlPath) {
|
||||||
|
fmt.Println("→ JSONL is newer than database, importing first...")
|
||||||
|
if err := importFromJSONL(ctx, jsonlPath, renameOnImport); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error auto-importing: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println("✓ Auto-import complete")
|
||||||
|
}
|
||||||
|
|
||||||
// Pre-export integrity checks
|
// Pre-export integrity checks
|
||||||
if err := ensureStoreActive(); err == nil && store != nil {
|
if err := ensureStoreActive(); err == nil && store != nil {
|
||||||
if err := validatePreExport(ctx, store, jsonlPath); err != nil {
|
if err := validatePreExport(ctx, store, jsonlPath); err != nil {
|
||||||
|
|||||||
@@ -791,6 +791,19 @@ func (m *MemoryStorage) GetLabels(ctx context.Context, issueID string) ([]string
|
|||||||
return m.labels[issueID], nil
|
return m.labels[issueID], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MemoryStorage) GetLabelsForIssues(ctx context.Context, issueIDs []string) (map[string][]string, error) {
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
|
||||||
|
result := make(map[string][]string)
|
||||||
|
for _, issueID := range issueIDs {
|
||||||
|
if labels, exists := m.labels[issueID]; exists {
|
||||||
|
result[issueID] = labels
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m *MemoryStorage) GetIssuesByLabel(ctx context.Context, label string) ([]*types.Issue, error) {
|
func (m *MemoryStorage) GetIssuesByLabel(ctx context.Context, label string) ([]*types.Issue, error) {
|
||||||
m.mu.RLock()
|
m.mu.RLock()
|
||||||
defer m.mu.RUnlock()
|
defer m.mu.RUnlock()
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ type Storage interface {
|
|||||||
AddLabel(ctx context.Context, issueID, label, actor string) error
|
AddLabel(ctx context.Context, issueID, label, actor string) error
|
||||||
RemoveLabel(ctx context.Context, issueID, label, actor string) error
|
RemoveLabel(ctx context.Context, issueID, label, actor string) error
|
||||||
GetLabels(ctx context.Context, issueID string) ([]string, error)
|
GetLabels(ctx context.Context, issueID string) ([]string, error)
|
||||||
|
GetLabelsForIssues(ctx context.Context, issueIDs []string) (map[string][]string, error)
|
||||||
GetIssuesByLabel(ctx context.Context, label string) ([]*types.Issue, error)
|
GetIssuesByLabel(ctx context.Context, label string) ([]*types.Issue, error)
|
||||||
|
|
||||||
// Ready Work & Blocking
|
// Ready Work & Blocking
|
||||||
|
|||||||
Reference in New Issue
Block a user