feat: Add date and priority filters to bd search (bd-au0.5)

Adds missing date range and priority filtering to bd search for feature
parity with bd list. Part of command standardization epic (bd-au0).

Changes:
- Add date range flags: --created-after/before, --updated-after/before,
  --closed-after/before
- Add priority range flags: --priority-min, --priority-max
- Support multiple date formats (RFC3339, YYYY-MM-DD, etc.)
- Apply filters in both direct mode and daemon RPC mode
- Add comprehensive tests for new filters

Examples:
  bd search "security" --priority-min 0 --priority-max 2
  bd search "bug" --created-after 2025-01-01
  bd search "refactor" --updated-after 2025-01-01 --priority-min 1

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-11-21 22:45:47 -05:00
parent 0040e8029b
commit 787fb4e56f
2 changed files with 278 additions and 1 deletions

View File

@@ -5,11 +5,13 @@ import (
"fmt"
"os"
"strings"
"time"
"github.com/spf13/cobra"
"github.com/steveyegge/beads/internal/rpc"
"github.com/steveyegge/beads/internal/types"
"github.com/steveyegge/beads/internal/util"
"github.com/steveyegge/beads/internal/validation"
)
var searchCmd = &cobra.Command{
@@ -22,7 +24,10 @@ Examples:
bd search "login" --status open
bd search "database" --label backend --limit 10
bd search --query "performance" --assignee alice
bd search "bd-5q" # Search by partial ID`,
bd search "bd-5q" # Search by partial ID
bd search "security" --priority-min 0 --priority-max 2
bd search "bug" --created-after 2025-01-01
bd search "refactor" --updated-after 2025-01-01 --priority-min 1`,
Run: func(cmd *cobra.Command, args []string) {
// Get query from args or --query flag
queryFlag, _ := cmd.Flags().GetString("query")
@@ -52,6 +57,18 @@ Examples:
labelsAny, _ := cmd.Flags().GetStringSlice("label-any")
longFormat, _ := cmd.Flags().GetBool("long")
// Date range flags
createdAfter, _ := cmd.Flags().GetString("created-after")
createdBefore, _ := cmd.Flags().GetString("created-before")
updatedAfter, _ := cmd.Flags().GetString("updated-after")
updatedBefore, _ := cmd.Flags().GetString("updated-before")
closedAfter, _ := cmd.Flags().GetString("closed-after")
closedBefore, _ := cmd.Flags().GetString("closed-before")
// Priority range flags
priorityMinStr, _ := cmd.Flags().GetString("priority-min")
priorityMaxStr, _ := cmd.Flags().GetString("priority-max")
// Normalize labels
labels = util.NormalizeLabels(labels)
labelsAny = util.NormalizeLabels(labelsAny)
@@ -83,6 +100,74 @@ Examples:
filter.LabelsAny = labelsAny
}
// Date ranges
if createdAfter != "" {
t, err := parseTimeFlag(createdAfter)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing --created-after: %v\n", err)
os.Exit(1)
}
filter.CreatedAfter = &t
}
if createdBefore != "" {
t, err := parseTimeFlag(createdBefore)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing --created-before: %v\n", err)
os.Exit(1)
}
filter.CreatedBefore = &t
}
if updatedAfter != "" {
t, err := parseTimeFlag(updatedAfter)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing --updated-after: %v\n", err)
os.Exit(1)
}
filter.UpdatedAfter = &t
}
if updatedBefore != "" {
t, err := parseTimeFlag(updatedBefore)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing --updated-before: %v\n", err)
os.Exit(1)
}
filter.UpdatedBefore = &t
}
if closedAfter != "" {
t, err := parseTimeFlag(closedAfter)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing --closed-after: %v\n", err)
os.Exit(1)
}
filter.ClosedAfter = &t
}
if closedBefore != "" {
t, err := parseTimeFlag(closedBefore)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing --closed-before: %v\n", err)
os.Exit(1)
}
filter.ClosedBefore = &t
}
// Priority ranges
if cmd.Flags().Changed("priority-min") {
priorityMin, err := validation.ValidatePriority(priorityMinStr)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing --priority-min: %v\n", err)
os.Exit(1)
}
filter.PriorityMin = &priorityMin
}
if cmd.Flags().Changed("priority-max") {
priorityMax, err := validation.ValidatePriority(priorityMaxStr)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing --priority-max: %v\n", err)
os.Exit(1)
}
filter.PriorityMax = &priorityMax
}
ctx := rootCtx
// Check database freshness before reading (skip when using daemon)
@@ -111,6 +196,30 @@ Examples:
listArgs.LabelsAny = labelsAny
}
// Date ranges
if filter.CreatedAfter != nil {
listArgs.CreatedAfter = filter.CreatedAfter.Format(time.RFC3339)
}
if filter.CreatedBefore != nil {
listArgs.CreatedBefore = filter.CreatedBefore.Format(time.RFC3339)
}
if filter.UpdatedAfter != nil {
listArgs.UpdatedAfter = filter.UpdatedAfter.Format(time.RFC3339)
}
if filter.UpdatedBefore != nil {
listArgs.UpdatedBefore = filter.UpdatedBefore.Format(time.RFC3339)
}
if filter.ClosedAfter != nil {
listArgs.ClosedAfter = filter.ClosedAfter.Format(time.RFC3339)
}
if filter.ClosedBefore != nil {
listArgs.ClosedBefore = filter.ClosedBefore.Format(time.RFC3339)
}
// Priority range
listArgs.PriorityMin = filter.PriorityMin
listArgs.PriorityMax = filter.PriorityMax
resp, err := daemonClient.List(listArgs)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
@@ -252,5 +361,17 @@ func init() {
searchCmd.Flags().IntP("limit", "n", 50, "Limit results (default: 50)")
searchCmd.Flags().Bool("long", false, "Show detailed multi-line output for each issue")
// Date range flags
searchCmd.Flags().String("created-after", "", "Filter issues created after date (YYYY-MM-DD or RFC3339)")
searchCmd.Flags().String("created-before", "", "Filter issues created before date (YYYY-MM-DD or RFC3339)")
searchCmd.Flags().String("updated-after", "", "Filter issues updated after date (YYYY-MM-DD or RFC3339)")
searchCmd.Flags().String("updated-before", "", "Filter issues updated before date (YYYY-MM-DD or RFC3339)")
searchCmd.Flags().String("closed-after", "", "Filter issues closed after date (YYYY-MM-DD or RFC3339)")
searchCmd.Flags().String("closed-before", "", "Filter issues closed before date (YYYY-MM-DD or RFC3339)")
// Priority range flags
searchCmd.Flags().String("priority-min", "", "Filter by minimum priority (inclusive, 0-4 or P0-P4)")
searchCmd.Flags().String("priority-max", "", "Filter by maximum priority (inclusive, 0-4 or P0-P4)")
rootCmd.AddCommand(searchCmd)
}