feat: add --parent flag to bd update for reparenting issues (bd-cj2e)

Allows reparenting issues to a different epic/parent:
  bd update bd-xyz --parent=bd-epic

Implementation:
- Add Parent field to UpdateArgs in protocol.go
- Handle reparenting in RPC handler (server_issues_epics.go)
- Add --parent flag to updateCmd with both daemon and direct mode support
- Remove old parent-child dependency before adding new one
- Pass empty string to remove parent entirely

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-27 22:17:03 -08:00
parent 05d44503de
commit 35f8e197ee
3 changed files with 114 additions and 3 deletions

View File

@@ -124,6 +124,8 @@ type UpdateArgs struct {
SupersededBy *string `json:"superseded_by,omitempty"` // Replacement issue ID if obsolete
// Pinned field (bd-iea)
Pinned *bool `json:"pinned,omitempty"` // If true, issue is a persistent context marker
// Reparenting field (bd-cj2e)
Parent *string `json:"parent,omitempty"` // New parent issue ID (reparents the issue)
}
// CloseArgs represents arguments for the close operation

View File

@@ -466,8 +466,65 @@ func (s *Server) handleUpdate(req *Request) Response {
}
}
// Emit mutation event for event-driven daemon (only if any updates or label operations were performed)
if len(updates) > 0 || len(updateArgs.SetLabels) > 0 || len(updateArgs.AddLabels) > 0 || len(updateArgs.RemoveLabels) > 0 {
// Handle reparenting (bd-cj2e)
if updateArgs.Parent != nil {
newParentID := *updateArgs.Parent
// Validate new parent exists (unless empty string to remove parent)
if newParentID != "" {
newParent, err := store.GetIssue(ctx, newParentID)
if err != nil {
return Response{
Success: false,
Error: fmt.Sprintf("failed to get new parent: %v", err),
}
}
if newParent == nil {
return Response{
Success: false,
Error: fmt.Sprintf("parent issue %s not found", newParentID),
}
}
}
// Find and remove existing parent-child dependency
deps, err := store.GetDependencyRecords(ctx, updateArgs.ID)
if err != nil {
return Response{
Success: false,
Error: fmt.Sprintf("failed to get dependencies: %v", err),
}
}
for _, dep := range deps {
if dep.Type == types.DepParentChild {
if err := store.RemoveDependency(ctx, updateArgs.ID, dep.DependsOnID, actor); err != nil {
return Response{
Success: false,
Error: fmt.Sprintf("failed to remove old parent dependency: %v", err),
}
}
break // Only one parent-child dependency expected
}
}
// Add new parent-child dependency (if not removing parent)
if newParentID != "" {
newDep := &types.Dependency{
IssueID: updateArgs.ID,
DependsOnID: newParentID,
Type: types.DepParentChild,
}
if err := store.AddDependency(ctx, newDep, actor); err != nil {
return Response{
Success: false,
Error: fmt.Sprintf("failed to add parent dependency: %v", err),
}
}
}
}
// Emit mutation event for event-driven daemon (only if any updates or label/parent operations were performed)
if len(updates) > 0 || len(updateArgs.SetLabels) > 0 || len(updateArgs.AddLabels) > 0 || len(updateArgs.RemoveLabels) > 0 || updateArgs.Parent != nil {
// Check if this was a status change - emit rich MutationStatus event
if updateArgs.Status != nil && *updateArgs.Status != string(issue.Status) {
s.emitRichMutation(MutationEvent{