Add substring ID matching for all bd commands
- Enhanced ResolvePartialID to handle: - Bare hashes: 07b8c8 → bd-07b8c8 - Prefix without hyphen: bd07b8c8 → bd-07b8c8 - Full IDs: bd-07b8c8 (unchanged) - Substring matching: 07b → finds bd-07b8c8 - Added RPC support: - New OpResolveID operation - handleResolveID server handler - ResolveID client method - Updated all commands to resolve IDs: - show, update, close, reopen - dep (add, remove, tree) - label (add, remove, list) - Works in both daemon and direct modes Fixes bd-0591c3
This commit is contained in:
@@ -255,6 +255,11 @@ func (c *Client) Show(args *ShowArgs) (*Response, error) {
|
||||
return c.Execute(OpShow, args)
|
||||
}
|
||||
|
||||
// ResolveID resolves a partial issue ID to a full ID via the daemon
|
||||
func (c *Client) ResolveID(args *ResolveIDArgs) (*Response, error) {
|
||||
return c.Execute(OpResolveID, args)
|
||||
}
|
||||
|
||||
// Ready gets ready work via the daemon
|
||||
func (c *Client) Ready(args *ReadyArgs) (*Response, error) {
|
||||
return c.Execute(OpReady, args)
|
||||
|
||||
@@ -25,6 +25,7 @@ const (
|
||||
OpCommentList = "comment_list"
|
||||
OpCommentAdd = "comment_add"
|
||||
OpBatch = "batch"
|
||||
OpResolveID = "resolve_id"
|
||||
|
||||
OpCompact = "compact"
|
||||
OpCompactStats = "compact_stats"
|
||||
@@ -104,6 +105,11 @@ type ShowArgs struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
// ResolveIDArgs represents arguments for the resolve_id operation
|
||||
type ResolveIDArgs struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
// ReadyArgs represents arguments for the ready operation
|
||||
type ReadyArgs struct {
|
||||
Assignee string `json:"assignee,omitempty"`
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/steveyegge/beads/internal/types"
|
||||
"github.com/steveyegge/beads/internal/utils"
|
||||
)
|
||||
|
||||
// normalizeLabels trims whitespace, removes empty strings, and deduplicates labels
|
||||
@@ -319,6 +320,31 @@ func (s *Server) handleList(req *Request) Response {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleResolveID(req *Request) Response {
|
||||
var args ResolveIDArgs
|
||||
if err := json.Unmarshal(req.Args, &args); err != nil {
|
||||
return Response{
|
||||
Success: false,
|
||||
Error: fmt.Sprintf("invalid resolve_id args: %v", err),
|
||||
}
|
||||
}
|
||||
|
||||
ctx := s.reqCtx(req)
|
||||
resolvedID, err := utils.ResolvePartialID(ctx, s.storage, args.ID)
|
||||
if err != nil {
|
||||
return Response{
|
||||
Success: false,
|
||||
Error: fmt.Sprintf("failed to resolve ID: %v", err),
|
||||
}
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(resolvedID)
|
||||
return Response{
|
||||
Success: true,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleShow(req *Request) Response {
|
||||
var showArgs ShowArgs
|
||||
if err := json.Unmarshal(req.Args, &showArgs); err != nil {
|
||||
|
||||
@@ -168,6 +168,8 @@ func (s *Server) handleRequest(req *Request) Response {
|
||||
resp = s.handleList(req)
|
||||
case OpShow:
|
||||
resp = s.handleShow(req)
|
||||
case OpResolveID:
|
||||
resp = s.handleResolveID(req)
|
||||
case OpReady:
|
||||
resp = s.handleReady(req)
|
||||
case OpStats:
|
||||
|
||||
@@ -29,32 +29,47 @@ func ParseIssueID(input string, prefix string) string {
|
||||
// ResolvePartialID resolves a potentially partial issue ID to a full ID.
|
||||
// Supports:
|
||||
// - Full IDs: "bd-a3f8e9" or "a3f8e9" → "bd-a3f8e9"
|
||||
// - Partial IDs: "a3f8" → "bd-a3f8e9" (if unique match, requires hash IDs)
|
||||
// - Without hyphen: "bda3f8e9" or "wya3f8e9" → "bd-a3f8e9"
|
||||
// - Partial IDs: "a3f8" → "bd-a3f8e9" (if unique match)
|
||||
// - Hierarchical: "a3f8e9.1" → "bd-a3f8e9.1"
|
||||
//
|
||||
// Returns an error if:
|
||||
// - No issue found matching the ID
|
||||
// - Multiple issues match (ambiguous prefix)
|
||||
//
|
||||
// Note: Partial ID matching (shorter prefixes) requires hash-based IDs (bd-165).
|
||||
// For now, this primarily handles prefix-optional input (bd-a3f8e9 vs a3f8e9).
|
||||
func ResolvePartialID(ctx context.Context, store storage.Storage, input string) (string, error) {
|
||||
// Get the configured prefix
|
||||
prefix, err := store.GetConfig(ctx, "issue_prefix")
|
||||
if err != nil || prefix == "" {
|
||||
prefix = "bd-"
|
||||
prefix = "bd"
|
||||
}
|
||||
|
||||
// Ensure the input has the prefix
|
||||
parsedID := ParseIssueID(input, prefix)
|
||||
// Ensure prefix has hyphen for ID format
|
||||
prefixWithHyphen := prefix
|
||||
if !strings.HasSuffix(prefix, "-") {
|
||||
prefixWithHyphen = prefix + "-"
|
||||
}
|
||||
|
||||
// Normalize input:
|
||||
// 1. If it has the full prefix with hyphen (bd-a3f8e9), use as-is
|
||||
// 2. Otherwise, add prefix with hyphen (handles both bare hashes and prefix-without-hyphen cases)
|
||||
|
||||
var normalizedID string
|
||||
|
||||
if strings.HasPrefix(input, prefixWithHyphen) {
|
||||
// Already has prefix with hyphen: "bd-a3f8e9"
|
||||
normalizedID = input
|
||||
} else {
|
||||
// Bare hash or prefix without hyphen: "a3f8e9", "07b8c8", "bda3f8e9" → all get prefix with hyphen added
|
||||
normalizedID = prefixWithHyphen + input
|
||||
}
|
||||
|
||||
// First try exact match
|
||||
_, err = store.GetIssue(ctx, parsedID)
|
||||
if err == nil {
|
||||
return parsedID, nil
|
||||
issue, err := store.GetIssue(ctx, normalizedID)
|
||||
if err == nil && issue != nil {
|
||||
return normalizedID, nil
|
||||
}
|
||||
|
||||
// If exact match failed, try prefix search
|
||||
// If exact match failed, try substring search
|
||||
filter := types.IssueFilter{}
|
||||
|
||||
issues, err := store.SearchIssues(ctx, "", filter)
|
||||
@@ -62,9 +77,14 @@ func ResolvePartialID(ctx context.Context, store storage.Storage, input string)
|
||||
return "", fmt.Errorf("failed to search issues: %w", err)
|
||||
}
|
||||
|
||||
// Extract the hash part for substring matching
|
||||
hashPart := strings.TrimPrefix(normalizedID, prefix)
|
||||
|
||||
var matches []string
|
||||
for _, issue := range issues {
|
||||
if strings.HasPrefix(issue.ID, parsedID) {
|
||||
issueHash := strings.TrimPrefix(issue.ID, prefix)
|
||||
// Check if the issue hash contains the input hash as substring
|
||||
if strings.Contains(issueHash, hashPart) {
|
||||
matches = append(matches, issue.ID)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user