feat(completion): optimize ID prefix filtering and add completions to more commands
Improvements to shell completions from PR #935: 1. Add IDPrefix field to IssueFilter for efficient database-level filtering - Queries are now filtered at SQL level instead of fetching all issues - Updated sqlite, transaction, and memory stores to support IDPrefix 2. Add ValidArgsFunction to additional commands: - dep (add, remove, list, tree) - comments, comment (add) - delete - graph - label (add, remove, list) - duplicate, supersede - audit - move - relate, unrelate - refile - gate (show, resolve, add-waiter) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> Executed-By: beads/crew/dave Rig: beads Role: crew
This commit is contained in:
committed by
Steve Yegge
parent
025cdac962
commit
5dfb838d60
@@ -160,6 +160,9 @@ func init() {
|
|||||||
auditLabelCmd.Flags().StringVar(&auditLabelValue, "label", "", `Label value (e.g. "good" or "bad")`)
|
auditLabelCmd.Flags().StringVar(&auditLabelValue, "label", "", `Label value (e.g. "good" or "bad")`)
|
||||||
auditLabelCmd.Flags().StringVar(&auditLabelReason, "reason", "", "Reason for label")
|
auditLabelCmd.Flags().StringVar(&auditLabelReason, "reason", "", "Reason for label")
|
||||||
|
|
||||||
|
// Issue ID completions
|
||||||
|
auditCmd.ValidArgsFunction = issueIDCompletion
|
||||||
|
|
||||||
auditCmd.AddCommand(auditRecordCmd)
|
auditCmd.AddCommand(auditRecordCmd)
|
||||||
auditCmd.AddCommand(auditLabelCmd)
|
auditCmd.AddCommand(auditLabelCmd)
|
||||||
rootCmd.AddCommand(auditCmd)
|
rootCmd.AddCommand(auditCmd)
|
||||||
|
|||||||
@@ -223,6 +223,11 @@ func init() {
|
|||||||
commentCmd.Flags().StringP("file", "f", "", "Read comment text from file")
|
commentCmd.Flags().StringP("file", "f", "", "Read comment text from file")
|
||||||
commentCmd.Flags().StringP("author", "a", "", "Add author to comment")
|
commentCmd.Flags().StringP("author", "a", "", "Add author to comment")
|
||||||
|
|
||||||
|
// Issue ID completions
|
||||||
|
commentsCmd.ValidArgsFunction = issueIDCompletion
|
||||||
|
commentsAddCmd.ValidArgsFunction = issueIDCompletion
|
||||||
|
commentCmd.ValidArgsFunction = issueIDCompletion
|
||||||
|
|
||||||
rootCmd.AddCommand(commentsCmd)
|
rootCmd.AddCommand(commentsCmd)
|
||||||
rootCmd.AddCommand(commentCmd)
|
rootCmd.AddCommand(commentCmd)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -51,8 +50,10 @@ func issueIDCompletion(cmd *cobra.Command, args []string, toComplete string) ([]
|
|||||||
defer currentStore.Close()
|
defer currentStore.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use SearchIssues with empty query and default filter to get all issues
|
// Use SearchIssues with IDPrefix filter to efficiently query matching issues
|
||||||
filter := types.IssueFilter{}
|
filter := types.IssueFilter{
|
||||||
|
IDPrefix: toComplete, // Filter at database level for better performance
|
||||||
|
}
|
||||||
issues, err := currentStore.SearchIssues(ctx, "", filter)
|
issues, err := currentStore.SearchIssues(ctx, "", filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If we can't list issues, return empty completion
|
// If we can't list issues, return empty completion
|
||||||
@@ -62,11 +63,6 @@ func issueIDCompletion(cmd *cobra.Command, args []string, toComplete string) ([]
|
|||||||
// Build completion list
|
// Build completion list
|
||||||
completions := make([]string, 0, len(issues))
|
completions := make([]string, 0, len(issues))
|
||||||
for _, issue := range issues {
|
for _, issue := range issues {
|
||||||
// Filter based on what's already typed
|
|
||||||
if toComplete != "" && !strings.HasPrefix(issue.ID, toComplete) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format: ID\tTitle (shown during completion)
|
// Format: ID\tTitle (shown during completion)
|
||||||
completions = append(completions, fmt.Sprintf("%s\t%s", issue.ID, issue.Title))
|
completions = append(completions, fmt.Sprintf("%s\t%s", issue.ID, issue.Title))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -833,5 +833,6 @@ func init() {
|
|||||||
deleteCmd.Flags().Bool("cascade", false, "Recursively delete all dependent issues")
|
deleteCmd.Flags().Bool("cascade", false, "Recursively delete all dependent issues")
|
||||||
deleteCmd.Flags().Bool("hard", false, "Permanently delete (skip tombstone, cannot be recovered via sync)")
|
deleteCmd.Flags().Bool("hard", false, "Permanently delete (skip tombstone, cannot be recovered via sync)")
|
||||||
deleteCmd.Flags().String("reason", "", "Reason for deletion (stored in tombstone for audit trail)")
|
deleteCmd.Flags().String("reason", "", "Reason for deletion (stored in tombstone for audit trail)")
|
||||||
|
deleteCmd.ValidArgsFunction = issueIDCompletion
|
||||||
rootCmd.AddCommand(deleteCmd)
|
rootCmd.AddCommand(deleteCmd)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1167,6 +1167,12 @@ func init() {
|
|||||||
depListCmd.Flags().String("direction", "down", "Direction: 'down' (dependencies), 'up' (dependents)")
|
depListCmd.Flags().String("direction", "down", "Direction: 'down' (dependencies), 'up' (dependents)")
|
||||||
depListCmd.Flags().StringP("type", "t", "", "Filter by dependency type (e.g., tracks, blocks, parent-child)")
|
depListCmd.Flags().StringP("type", "t", "", "Filter by dependency type (e.g., tracks, blocks, parent-child)")
|
||||||
|
|
||||||
|
// Issue ID completions for dep subcommands
|
||||||
|
depAddCmd.ValidArgsFunction = issueIDCompletion
|
||||||
|
depRemoveCmd.ValidArgsFunction = issueIDCompletion
|
||||||
|
depListCmd.ValidArgsFunction = issueIDCompletion
|
||||||
|
depTreeCmd.ValidArgsFunction = issueIDCompletion
|
||||||
|
|
||||||
depCmd.AddCommand(depAddCmd)
|
depCmd.AddCommand(depAddCmd)
|
||||||
depCmd.AddCommand(depRemoveCmd)
|
depCmd.AddCommand(depRemoveCmd)
|
||||||
depCmd.AddCommand(depListCmd)
|
depCmd.AddCommand(depListCmd)
|
||||||
|
|||||||
@@ -50,10 +50,12 @@ var (
|
|||||||
func init() {
|
func init() {
|
||||||
duplicateCmd.Flags().StringVar(&duplicateOf, "of", "", "Canonical issue ID (required)")
|
duplicateCmd.Flags().StringVar(&duplicateOf, "of", "", "Canonical issue ID (required)")
|
||||||
_ = duplicateCmd.MarkFlagRequired("of") // Only fails if flag missing (caught in tests)
|
_ = duplicateCmd.MarkFlagRequired("of") // Only fails if flag missing (caught in tests)
|
||||||
|
duplicateCmd.ValidArgsFunction = issueIDCompletion
|
||||||
rootCmd.AddCommand(duplicateCmd)
|
rootCmd.AddCommand(duplicateCmd)
|
||||||
|
|
||||||
supersedeCmd.Flags().StringVar(&supersededWith, "with", "", "Replacement issue ID (required)")
|
supersedeCmd.Flags().StringVar(&supersededWith, "with", "", "Replacement issue ID (required)")
|
||||||
_ = supersedeCmd.MarkFlagRequired("with") // Only fails if flag missing (caught in tests)
|
_ = supersedeCmd.MarkFlagRequired("with") // Only fails if flag missing (caught in tests)
|
||||||
|
supersedeCmd.ValidArgsFunction = issueIDCompletion
|
||||||
rootCmd.AddCommand(supersedeCmd)
|
rootCmd.AddCommand(supersedeCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -871,6 +871,11 @@ func init() {
|
|||||||
gateCheckCmd.Flags().BoolP("escalate", "e", false, "Escalate failed/expired gates")
|
gateCheckCmd.Flags().BoolP("escalate", "e", false, "Escalate failed/expired gates")
|
||||||
gateCheckCmd.Flags().IntP("limit", "l", 100, "Limit results (default 100)")
|
gateCheckCmd.Flags().IntP("limit", "l", 100, "Limit results (default 100)")
|
||||||
|
|
||||||
|
// Issue ID completions
|
||||||
|
gateShowCmd.ValidArgsFunction = issueIDCompletion
|
||||||
|
gateResolveCmd.ValidArgsFunction = issueIDCompletion
|
||||||
|
gateAddWaiterCmd.ValidArgsFunction = issueIDCompletion
|
||||||
|
|
||||||
// Add subcommands
|
// Add subcommands
|
||||||
gateCmd.AddCommand(gateListCmd)
|
gateCmd.AddCommand(gateListCmd)
|
||||||
gateCmd.AddCommand(gateShowCmd)
|
gateCmd.AddCommand(gateShowCmd)
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ Colors indicate status:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
graphCmd.ValidArgsFunction = issueIDCompletion
|
||||||
rootCmd.AddCommand(graphCmd)
|
rootCmd.AddCommand(graphCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -314,6 +314,11 @@ var labelListAllCmd = &cobra.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
func init() {
|
func init() {
|
||||||
|
// Issue ID completions
|
||||||
|
labelAddCmd.ValidArgsFunction = issueIDCompletion
|
||||||
|
labelRemoveCmd.ValidArgsFunction = issueIDCompletion
|
||||||
|
labelListCmd.ValidArgsFunction = issueIDCompletion
|
||||||
|
|
||||||
labelCmd.AddCommand(labelAddCmd)
|
labelCmd.AddCommand(labelAddCmd)
|
||||||
labelCmd.AddCommand(labelRemoveCmd)
|
labelCmd.AddCommand(labelRemoveCmd)
|
||||||
labelCmd.AddCommand(labelListCmd)
|
labelCmd.AddCommand(labelListCmd)
|
||||||
|
|||||||
@@ -276,5 +276,6 @@ func init() {
|
|||||||
moveCmd.Flags().String("to", "", "Target rig or prefix (required)")
|
moveCmd.Flags().String("to", "", "Target rig or prefix (required)")
|
||||||
moveCmd.Flags().Bool("keep-open", false, "Keep the source issue open (don't close it)")
|
moveCmd.Flags().Bool("keep-open", false, "Keep the source issue open (don't close it)")
|
||||||
moveCmd.Flags().Bool("skip-deps", false, "Skip dependency remapping")
|
moveCmd.Flags().Bool("skip-deps", false, "Skip dependency remapping")
|
||||||
|
moveCmd.ValidArgsFunction = issueIDCompletion
|
||||||
rootCmd.AddCommand(moveCmd)
|
rootCmd.AddCommand(moveCmd)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,5 +160,6 @@ Examples:
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
refileCmd.Flags().Bool("keep-open", false, "Keep the source issue open (don't close it)")
|
refileCmd.Flags().Bool("keep-open", false, "Keep the source issue open (don't close it)")
|
||||||
|
refileCmd.ValidArgsFunction = issueIDCompletion
|
||||||
rootCmd.AddCommand(refileCmd)
|
rootCmd.AddCommand(refileCmd)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ Example:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
// Issue ID completions
|
||||||
|
relateCmd.ValidArgsFunction = issueIDCompletion
|
||||||
|
unrelateCmd.ValidArgsFunction = issueIDCompletion
|
||||||
|
|
||||||
// Add as subcommands of dep
|
// Add as subcommands of dep
|
||||||
depCmd.AddCommand(relateCmd)
|
depCmd.AddCommand(relateCmd)
|
||||||
depCmd.AddCommand(unrelateCmd)
|
depCmd.AddCommand(unrelateCmd)
|
||||||
|
|||||||
@@ -620,6 +620,11 @@ func (m *MemoryStorage) SearchIssues(ctx context.Context, query string, filter t
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ID prefix filtering (for shell completion)
|
||||||
|
if filter.IDPrefix != "" && !strings.HasPrefix(issue.ID, filter.IDPrefix) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Parent filtering (bd-yqhh): filter children by parent issue
|
// Parent filtering (bd-yqhh): filter children by parent issue
|
||||||
if filter.ParentID != nil {
|
if filter.ParentID != nil {
|
||||||
isChild := false
|
isChild := false
|
||||||
|
|||||||
@@ -1836,6 +1836,12 @@ func (s *SQLiteStorage) SearchIssues(ctx context.Context, query string, filter t
|
|||||||
whereClauses = append(whereClauses, fmt.Sprintf("id IN (%s)", strings.Join(placeholders, ", ")))
|
whereClauses = append(whereClauses, fmt.Sprintf("id IN (%s)", strings.Join(placeholders, ", ")))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ID prefix filtering (for shell completion)
|
||||||
|
if filter.IDPrefix != "" {
|
||||||
|
whereClauses = append(whereClauses, "id LIKE ?")
|
||||||
|
args = append(args, filter.IDPrefix+"%")
|
||||||
|
}
|
||||||
|
|
||||||
// Wisp filtering
|
// Wisp filtering
|
||||||
if filter.Ephemeral != nil {
|
if filter.Ephemeral != nil {
|
||||||
if *filter.Ephemeral {
|
if *filter.Ephemeral {
|
||||||
|
|||||||
@@ -1181,6 +1181,12 @@ func (t *sqliteTxStorage) SearchIssues(ctx context.Context, query string, filter
|
|||||||
whereClauses = append(whereClauses, fmt.Sprintf("id IN (%s)", strings.Join(placeholders, ", ")))
|
whereClauses = append(whereClauses, fmt.Sprintf("id IN (%s)", strings.Join(placeholders, ", ")))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ID prefix filtering (for shell completion)
|
||||||
|
if filter.IDPrefix != "" {
|
||||||
|
whereClauses = append(whereClauses, "id LIKE ?")
|
||||||
|
args = append(args, filter.IDPrefix+"%")
|
||||||
|
}
|
||||||
|
|
||||||
// Wisp filtering
|
// Wisp filtering
|
||||||
if filter.Ephemeral != nil {
|
if filter.Ephemeral != nil {
|
||||||
if *filter.Ephemeral {
|
if *filter.Ephemeral {
|
||||||
|
|||||||
@@ -758,7 +758,8 @@ type IssueFilter struct {
|
|||||||
Labels []string // AND semantics: issue must have ALL these labels
|
Labels []string // AND semantics: issue must have ALL these labels
|
||||||
LabelsAny []string // OR semantics: issue must have AT LEAST ONE of these labels
|
LabelsAny []string // OR semantics: issue must have AT LEAST ONE of these labels
|
||||||
TitleSearch string
|
TitleSearch string
|
||||||
IDs []string // Filter by specific issue IDs
|
IDs []string // Filter by specific issue IDs
|
||||||
|
IDPrefix string // Filter by ID prefix (for shell completion)
|
||||||
Limit int
|
Limit int
|
||||||
|
|
||||||
// Pattern matching
|
// Pattern matching
|
||||||
|
|||||||
Reference in New Issue
Block a user