Fix daemon/direct mode inconsistency in graph commands (bd-fu83)
Commands relate, unrelate, duplicate, and supersede now properly use RPC Update when daemonClient is available, instead of always calling store.UpdateIssue() directly and bypassing the daemon. Added RelatesTo, DuplicateOf, and SupersededBy fields to UpdateArgs in the RPC protocol, and updated server_issues_epics.go to handle them. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -112,13 +112,25 @@ func runDuplicate(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the duplicate issue with duplicate_of and close it
|
// Update the duplicate issue with duplicate_of and close it
|
||||||
updates := map[string]interface{}{
|
closedStatus := string(types.StatusClosed)
|
||||||
"duplicate_of": canonicalID,
|
if daemonClient != nil {
|
||||||
"status": string(types.StatusClosed),
|
// Use RPC for daemon mode (bd-fu83)
|
||||||
}
|
_, err := daemonClient.Update(&rpc.UpdateArgs{
|
||||||
|
ID: duplicateID,
|
||||||
if err := store.UpdateIssue(ctx, duplicateID, updates, actor); err != nil {
|
DuplicateOf: &canonicalID,
|
||||||
return fmt.Errorf("failed to mark as duplicate: %w", err)
|
Status: &closedStatus,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to mark as duplicate: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updates := map[string]interface{}{
|
||||||
|
"duplicate_of": canonicalID,
|
||||||
|
"status": closedStatus,
|
||||||
|
}
|
||||||
|
if err := store.UpdateIssue(ctx, duplicateID, updates, actor); err != nil {
|
||||||
|
return fmt.Errorf("failed to mark as duplicate: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger auto-flush
|
// Trigger auto-flush
|
||||||
@@ -199,13 +211,25 @@ func runSupersede(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the old issue with superseded_by and close it
|
// Update the old issue with superseded_by and close it
|
||||||
updates := map[string]interface{}{
|
closedStatus := string(types.StatusClosed)
|
||||||
"superseded_by": newID,
|
if daemonClient != nil {
|
||||||
"status": string(types.StatusClosed),
|
// Use RPC for daemon mode (bd-fu83)
|
||||||
}
|
_, err := daemonClient.Update(&rpc.UpdateArgs{
|
||||||
|
ID: oldID,
|
||||||
if err := store.UpdateIssue(ctx, oldID, updates, actor); err != nil {
|
SupersededBy: &newID,
|
||||||
return fmt.Errorf("failed to mark as superseded: %w", err)
|
Status: &closedStatus,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to mark as superseded: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updates := map[string]interface{}{
|
||||||
|
"superseded_by": newID,
|
||||||
|
"status": closedStatus,
|
||||||
|
}
|
||||||
|
if err := store.UpdateIssue(ctx, oldID, updates, actor); err != nil {
|
||||||
|
return fmt.Errorf("failed to mark as superseded: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger auto-flush
|
// Trigger auto-flush
|
||||||
|
|||||||
@@ -122,20 +122,44 @@ func runRelate(cmd *cobra.Command, args []string) error {
|
|||||||
// Add id2 to issue1's relates_to if not already present
|
// Add id2 to issue1's relates_to if not already present
|
||||||
if !contains(issue1.RelatesTo, id2) {
|
if !contains(issue1.RelatesTo, id2) {
|
||||||
newRelatesTo1 := append(issue1.RelatesTo, id2)
|
newRelatesTo1 := append(issue1.RelatesTo, id2)
|
||||||
if err := store.UpdateIssue(ctx, id1, map[string]interface{}{
|
formattedRelatesTo1 := formatRelatesTo(newRelatesTo1)
|
||||||
"relates_to": formatRelatesTo(newRelatesTo1),
|
if daemonClient != nil {
|
||||||
}, actor); err != nil {
|
// Use RPC for daemon mode (bd-fu83)
|
||||||
return fmt.Errorf("failed to update %s: %w", id1, err)
|
_, err := daemonClient.Update(&rpc.UpdateArgs{
|
||||||
|
ID: id1,
|
||||||
|
RelatesTo: &formattedRelatesTo1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update %s: %w", id1, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := store.UpdateIssue(ctx, id1, map[string]interface{}{
|
||||||
|
"relates_to": formattedRelatesTo1,
|
||||||
|
}, actor); err != nil {
|
||||||
|
return fmt.Errorf("failed to update %s: %w", id1, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add id1 to issue2's relates_to if not already present
|
// Add id1 to issue2's relates_to if not already present
|
||||||
if !contains(issue2.RelatesTo, id1) {
|
if !contains(issue2.RelatesTo, id1) {
|
||||||
newRelatesTo2 := append(issue2.RelatesTo, id1)
|
newRelatesTo2 := append(issue2.RelatesTo, id1)
|
||||||
if err := store.UpdateIssue(ctx, id2, map[string]interface{}{
|
formattedRelatesTo2 := formatRelatesTo(newRelatesTo2)
|
||||||
"relates_to": formatRelatesTo(newRelatesTo2),
|
if daemonClient != nil {
|
||||||
}, actor); err != nil {
|
// Use RPC for daemon mode (bd-fu83)
|
||||||
return fmt.Errorf("failed to update %s: %w", id2, err)
|
_, err := daemonClient.Update(&rpc.UpdateArgs{
|
||||||
|
ID: id2,
|
||||||
|
RelatesTo: &formattedRelatesTo2,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update %s: %w", id2, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := store.UpdateIssue(ctx, id2, map[string]interface{}{
|
||||||
|
"relates_to": formattedRelatesTo2,
|
||||||
|
}, actor); err != nil {
|
||||||
|
return fmt.Errorf("failed to update %s: %w", id2, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,18 +256,42 @@ func runUnrelate(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
// Remove id2 from issue1's relates_to
|
// Remove id2 from issue1's relates_to
|
||||||
newRelatesTo1 := remove(issue1.RelatesTo, id2)
|
newRelatesTo1 := remove(issue1.RelatesTo, id2)
|
||||||
if err := store.UpdateIssue(ctx, id1, map[string]interface{}{
|
formattedRelatesTo1 := formatRelatesTo(newRelatesTo1)
|
||||||
"relates_to": formatRelatesTo(newRelatesTo1),
|
if daemonClient != nil {
|
||||||
}, actor); err != nil {
|
// Use RPC for daemon mode (bd-fu83)
|
||||||
return fmt.Errorf("failed to update %s: %w", id1, err)
|
_, err := daemonClient.Update(&rpc.UpdateArgs{
|
||||||
|
ID: id1,
|
||||||
|
RelatesTo: &formattedRelatesTo1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update %s: %w", id1, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := store.UpdateIssue(ctx, id1, map[string]interface{}{
|
||||||
|
"relates_to": formattedRelatesTo1,
|
||||||
|
}, actor); err != nil {
|
||||||
|
return fmt.Errorf("failed to update %s: %w", id1, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove id1 from issue2's relates_to
|
// Remove id1 from issue2's relates_to
|
||||||
newRelatesTo2 := remove(issue2.RelatesTo, id1)
|
newRelatesTo2 := remove(issue2.RelatesTo, id1)
|
||||||
if err := store.UpdateIssue(ctx, id2, map[string]interface{}{
|
formattedRelatesTo2 := formatRelatesTo(newRelatesTo2)
|
||||||
"relates_to": formatRelatesTo(newRelatesTo2),
|
if daemonClient != nil {
|
||||||
}, actor); err != nil {
|
// Use RPC for daemon mode (bd-fu83)
|
||||||
return fmt.Errorf("failed to update %s: %w", id2, err)
|
_, err := daemonClient.Update(&rpc.UpdateArgs{
|
||||||
|
ID: id2,
|
||||||
|
RelatesTo: &formattedRelatesTo2,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update %s: %w", id2, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := store.UpdateIssue(ctx, id2, map[string]interface{}{
|
||||||
|
"relates_to": formattedRelatesTo2,
|
||||||
|
}, actor); err != nil {
|
||||||
|
return fmt.Errorf("failed to update %s: %w", id2, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger auto-flush
|
// Trigger auto-flush
|
||||||
|
|||||||
@@ -99,6 +99,10 @@ type UpdateArgs struct {
|
|||||||
Sender *string `json:"sender,omitempty"` // Who sent this (for messages)
|
Sender *string `json:"sender,omitempty"` // Who sent this (for messages)
|
||||||
Ephemeral *bool `json:"ephemeral,omitempty"` // Can be bulk-deleted when closed
|
Ephemeral *bool `json:"ephemeral,omitempty"` // Can be bulk-deleted when closed
|
||||||
RepliesTo *string `json:"replies_to,omitempty"` // Issue ID for conversation threading
|
RepliesTo *string `json:"replies_to,omitempty"` // Issue ID for conversation threading
|
||||||
|
// Graph link fields (bd-fu83)
|
||||||
|
RelatesTo *string `json:"relates_to,omitempty"` // JSON array of related issue IDs
|
||||||
|
DuplicateOf *string `json:"duplicate_of,omitempty"` // Canonical issue ID if duplicate
|
||||||
|
SupersededBy *string `json:"superseded_by,omitempty"` // Replacement issue ID if obsolete
|
||||||
}
|
}
|
||||||
|
|
||||||
// CloseArgs represents arguments for the close operation
|
// CloseArgs represents arguments for the close operation
|
||||||
|
|||||||
@@ -87,6 +87,16 @@ func updatesFromArgs(a UpdateArgs) map[string]interface{} {
|
|||||||
if a.RepliesTo != nil {
|
if a.RepliesTo != nil {
|
||||||
u["replies_to"] = *a.RepliesTo
|
u["replies_to"] = *a.RepliesTo
|
||||||
}
|
}
|
||||||
|
// Graph link fields (bd-fu83)
|
||||||
|
if a.RelatesTo != nil {
|
||||||
|
u["relates_to"] = *a.RelatesTo
|
||||||
|
}
|
||||||
|
if a.DuplicateOf != nil {
|
||||||
|
u["duplicate_of"] = *a.DuplicateOf
|
||||||
|
}
|
||||||
|
if a.SupersededBy != nil {
|
||||||
|
u["superseded_by"] = *a.SupersededBy
|
||||||
|
}
|
||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user