diff --git a/cmd/bd/list.go b/cmd/bd/list.go index ac166587..9f36321d 100644 --- a/cmd/bd/list.go +++ b/cmd/bd/list.go @@ -148,6 +148,9 @@ var listCmd = &cobra.Command{ // Template filtering (beads-1ra) includeTemplates, _ := cmd.Flags().GetBool("include-templates") + // Parent filtering (bd-yqhh) + parentID, _ := cmd.Flags().GetString("parent") + // Use global jsonOutput set by PersistentPreRun // Normalize labels: trim, dedupe, remove empty @@ -311,6 +314,11 @@ var listCmd = &cobra.Command{ filter.IsTemplate = &isTemplate } + // Parent filtering (bd-yqhh): filter children by parent issue + if parentID != "" { + filter.ParentID = &parentID + } + // Check database freshness before reading (bd-2q6d, bd-c4rq) // Skip check when using daemon (daemon auto-imports on staleness) ctx := rootCtx @@ -392,6 +400,9 @@ var listCmd = &cobra.Command{ // Template filtering (beads-1ra) listArgs.IncludeTemplates = includeTemplates + // Parent filtering (bd-yqhh) + listArgs.ParentID = parentID + resp, err := daemonClient.List(listArgs) if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) @@ -666,6 +677,9 @@ func init() { // Template filtering (beads-1ra): exclude templates by default listCmd.Flags().Bool("include-templates", false, "Include template molecules in output") + // Parent filtering (bd-yqhh): filter children by parent issue + listCmd.Flags().String("parent", "", "Filter by parent issue ID (shows children of specified issue)") + // Note: --json flag is defined as a persistent flag in main.go, not here rootCmd.AddCommand(listCmd) } diff --git a/internal/rpc/protocol.go b/internal/rpc/protocol.go index 251c6f7d..41eafc26 100644 --- a/internal/rpc/protocol.go +++ b/internal/rpc/protocol.go @@ -162,6 +162,9 @@ type ListArgs struct { // Template filtering (beads-1ra) IncludeTemplates bool `json:"include_templates,omitempty"` + + // Parent filtering (bd-yqhh) + ParentID string `json:"parent_id,omitempty"` } // CountArgs represents arguments for the count operation diff --git a/internal/rpc/server_issues_epics.go b/internal/rpc/server_issues_epics.go index 523bf2b7..1c788dd7 100644 --- a/internal/rpc/server_issues_epics.go +++ b/internal/rpc/server_issues_epics.go @@ -751,6 +751,11 @@ func (s *Server) handleList(req *Request) Response { filter.IsTemplate = &isTemplate } + // Parent filtering (bd-yqhh) + if listArgs.ParentID != "" { + filter.ParentID = &listArgs.ParentID + } + // Guard against excessive ID lists to avoid SQLite parameter limits const maxIDs = 1000 if len(filter.IDs) > maxIDs { diff --git a/internal/storage/memory/memory.go b/internal/storage/memory/memory.go index f1b055fa..c44882d0 100644 --- a/internal/storage/memory/memory.go +++ b/internal/storage/memory/memory.go @@ -571,6 +571,20 @@ func (m *MemoryStorage) SearchIssues(ctx context.Context, query string, filter t } } + // Parent filtering (bd-yqhh): filter children by parent issue + if filter.ParentID != nil { + isChild := false + for _, dep := range m.dependencies[issue.ID] { + if dep.Type == types.DepParentChild && dep.DependsOnID == *filter.ParentID { + isChild = true + break + } + } + if !isChild { + continue + } + } + // Copy issue and attach metadata issueCopy := *issue if deps, ok := m.dependencies[issue.ID]; ok { diff --git a/internal/storage/sqlite/queries.go b/internal/storage/sqlite/queries.go index 6d324d3c..62e94309 100644 --- a/internal/storage/sqlite/queries.go +++ b/internal/storage/sqlite/queries.go @@ -1623,6 +1623,12 @@ func (s *SQLiteStorage) SearchIssues(ctx context.Context, query string, filter t } } + // Parent filtering (bd-yqhh): filter children by parent issue + if filter.ParentID != nil { + whereClauses = append(whereClauses, "id IN (SELECT issue_id FROM dependencies WHERE type = 'parent-child' AND depends_on_id = ?)") + args = append(args, *filter.ParentID) + } + whereSQL := "" if len(whereClauses) > 0 { whereSQL = "WHERE " + strings.Join(whereClauses, " AND ") diff --git a/internal/storage/sqlite/transaction.go b/internal/storage/sqlite/transaction.go index 9698041a..86d44646 100644 --- a/internal/storage/sqlite/transaction.go +++ b/internal/storage/sqlite/transaction.go @@ -1098,6 +1098,12 @@ func (t *sqliteTxStorage) SearchIssues(ctx context.Context, query string, filter } } + // Parent filtering (bd-yqhh): filter children by parent issue + if filter.ParentID != nil { + whereClauses = append(whereClauses, "id IN (SELECT issue_id FROM dependencies WHERE type = 'parent-child' AND depends_on_id = ?)") + args = append(args, *filter.ParentID) + } + whereSQL := "" if len(whereClauses) > 0 { whereSQL = "WHERE " + strings.Join(whereClauses, " AND ") diff --git a/internal/types/types.go b/internal/types/types.go index 47e3b49c..fdfad2ef 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -562,6 +562,9 @@ type IssueFilter struct { // Template filtering (beads-1ra) IsTemplate *bool // Filter by template flag (nil = any, true = only templates, false = exclude templates) + + // Parent filtering (bd-yqhh): filter children by parent issue ID + ParentID *string // Filter by parent issue (via parent-child dependency) } // SortPolicy determines how ready work is ordered