fix: Add cross-rig routing support to bd close
The close command now properly routes to different beads directories based on issue ID prefix, matching the behavior of bd show/update. Changes: - Check needsRouting() for each ID in both daemon and direct mode - Handle routed IDs via resolveAndGetIssueWithRouting() - Close issues in the correct remote store Fixes bd-3jrb 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Steve Yegge
parent
5ae088b594
commit
a9e70e3fe5
1787
.beads/issues.jsonl
1787
.beads/issues.jsonl
File diff suppressed because one or more lines are too long
113
cmd/bd/close.go
113
cmd/bd/close.go
@@ -64,10 +64,16 @@ create, update, show, or close operation).`,
|
|||||||
FatalErrorRespectJSON("--suggest-next only works when closing a single issue")
|
FatalErrorRespectJSON("--suggest-next only works when closing a single issue")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve partial IDs first
|
// Resolve partial IDs first, handling cross-rig routing
|
||||||
var resolvedIDs []string
|
var resolvedIDs []string
|
||||||
|
var routedArgs []string // IDs that need cross-repo routing (bypass daemon)
|
||||||
if daemonClient != nil {
|
if daemonClient != nil {
|
||||||
for _, id := range args {
|
for _, id := range args {
|
||||||
|
// Check if this ID needs routing to a different beads directory
|
||||||
|
if needsRouting(id) {
|
||||||
|
routedArgs = append(routedArgs, id)
|
||||||
|
continue
|
||||||
|
}
|
||||||
resolveArgs := &rpc.ResolveIDArgs{ID: id}
|
resolveArgs := &rpc.ResolveIDArgs{ID: id}
|
||||||
resp, err := daemonClient.ResolveID(resolveArgs)
|
resp, err := daemonClient.ResolveID(resolveArgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -80,10 +86,17 @@ create, update, show, or close operation).`,
|
|||||||
resolvedIDs = append(resolvedIDs, resolvedID)
|
resolvedIDs = append(resolvedIDs, resolvedID)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var err error
|
// Direct mode - check routing for each ID
|
||||||
resolvedIDs, err = utils.ResolvePartialIDs(ctx, store, args)
|
for _, id := range args {
|
||||||
if err != nil {
|
if needsRouting(id) {
|
||||||
FatalErrorRespectJSON("%v", err)
|
routedArgs = append(routedArgs, id)
|
||||||
|
} else {
|
||||||
|
resolved, err := utils.ResolvePartialID(ctx, store, id)
|
||||||
|
if err != nil {
|
||||||
|
FatalErrorRespectJSON("resolving ID %s: %v", id, err)
|
||||||
|
}
|
||||||
|
resolvedIDs = append(resolvedIDs, resolved)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,6 +170,49 @@ create, update, show, or close operation).`,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle routed IDs via direct mode (cross-rig)
|
||||||
|
for _, id := range routedArgs {
|
||||||
|
result, err := resolveAndGetIssueWithRouting(ctx, store, id)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error resolving %s: %v\n", id, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if result == nil || result.Issue == nil {
|
||||||
|
if result != nil {
|
||||||
|
result.Close()
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "Issue %s not found\n", id)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateIssueClosable(result.ResolvedID, result.Issue, force); err != nil {
|
||||||
|
result.Close()
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := result.Store.CloseIssue(ctx, result.ResolvedID, reason, actor); err != nil {
|
||||||
|
result.Close()
|
||||||
|
fmt.Fprintf(os.Stderr, "Error closing %s: %v\n", id, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get updated issue for hook
|
||||||
|
closedIssue, _ := result.Store.GetIssue(ctx, result.ResolvedID)
|
||||||
|
if closedIssue != nil && hookRunner != nil {
|
||||||
|
hookRunner.Run(hooks.EventClose, closedIssue)
|
||||||
|
}
|
||||||
|
|
||||||
|
if jsonOutput {
|
||||||
|
if closedIssue != nil {
|
||||||
|
closedIssues = append(closedIssues, closedIssue)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s Closed %s: %s\n", ui.RenderPass("✓"), result.ResolvedID, reason)
|
||||||
|
}
|
||||||
|
result.Close()
|
||||||
|
}
|
||||||
|
|
||||||
// Handle --continue flag in daemon mode
|
// Handle --continue flag in daemon mode
|
||||||
// Note: --continue requires direct database access to walk parent-child chain
|
// Note: --continue requires direct database access to walk parent-child chain
|
||||||
if continueFlag && len(closedIssues) > 0 {
|
if continueFlag && len(closedIssues) > 0 {
|
||||||
@@ -173,6 +229,8 @@ create, update, show, or close operation).`,
|
|||||||
// Direct mode
|
// Direct mode
|
||||||
closedIssues := []*types.Issue{}
|
closedIssues := []*types.Issue{}
|
||||||
closedCount := 0
|
closedCount := 0
|
||||||
|
|
||||||
|
// Handle local IDs
|
||||||
for _, id := range resolvedIDs {
|
for _, id := range resolvedIDs {
|
||||||
// Get issue for checks
|
// Get issue for checks
|
||||||
issue, _ := store.GetIssue(ctx, id)
|
issue, _ := store.GetIssue(ctx, id)
|
||||||
@@ -204,6 +262,51 @@ create, update, show, or close operation).`,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle routed IDs (cross-rig)
|
||||||
|
for _, id := range routedArgs {
|
||||||
|
result, err := resolveAndGetIssueWithRouting(ctx, store, id)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error resolving %s: %v\n", id, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if result == nil || result.Issue == nil {
|
||||||
|
if result != nil {
|
||||||
|
result.Close()
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "Issue %s not found\n", id)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateIssueClosable(result.ResolvedID, result.Issue, force); err != nil {
|
||||||
|
result.Close()
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := result.Store.CloseIssue(ctx, result.ResolvedID, reason, actor); err != nil {
|
||||||
|
result.Close()
|
||||||
|
fmt.Fprintf(os.Stderr, "Error closing %s: %v\n", id, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
closedCount++
|
||||||
|
|
||||||
|
// Get updated issue for hook
|
||||||
|
closedIssue, _ := result.Store.GetIssue(ctx, result.ResolvedID)
|
||||||
|
if closedIssue != nil && hookRunner != nil {
|
||||||
|
hookRunner.Run(hooks.EventClose, closedIssue)
|
||||||
|
}
|
||||||
|
|
||||||
|
if jsonOutput {
|
||||||
|
if closedIssue != nil {
|
||||||
|
closedIssues = append(closedIssues, closedIssue)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s Closed %s: %s\n", ui.RenderPass("✓"), result.ResolvedID, reason)
|
||||||
|
}
|
||||||
|
result.Close()
|
||||||
|
}
|
||||||
|
|
||||||
// Handle --suggest-next flag in direct mode
|
// Handle --suggest-next flag in direct mode
|
||||||
if suggestNext && len(resolvedIDs) == 1 && closedCount > 0 {
|
if suggestNext && len(resolvedIDs) == 1 && closedCount > 0 {
|
||||||
unblocked, err := store.GetNewlyUnblockedByClose(ctx, resolvedIDs[0])
|
unblocked, err := store.GetNewlyUnblockedByClose(ctx, resolvedIDs[0])
|
||||||
|
|||||||
Reference in New Issue
Block a user