Phase 4: Remove deprecated edge fields from Issue struct (Decision 004)
This is the final phase of the Edge Schema Consolidation. It removes the deprecated edge fields (RepliesTo, RelatesTo, DuplicateOf, SupersededBy) from the Issue struct and all related code. Changes: - Remove edge fields from types.Issue struct - Remove edge field scanning from queries.go and transaction.go - Update graph_links_test.go to use dependency API exclusively - Update relate.go to use AddDependency/RemoveDependency - Update show.go with helper functions for thread traversal via deps - Update mail_test.go to verify thread links via dependencies - Add migration 022 to drop columns from issues table - Fix cycle detection to allow bidirectional relates-to links - Fix migration 022 to disable foreign keys before table recreation All edge relationships now use the dependencies table exclusively. The old Issue fields are fully removed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -362,9 +362,7 @@ func runMailInbox(cmd *cobra.Command, args []string) error {
|
||||
|
||||
fmt.Printf(" %s: %s%s\n", msg.ID, msg.Title, priorityStr)
|
||||
fmt.Printf(" From: %s (%s)\n", msg.Sender, timeStr)
|
||||
if msg.RepliesTo != "" {
|
||||
fmt.Printf(" Re: %s\n", msg.RepliesTo)
|
||||
}
|
||||
// NOTE: Thread info now in dependencies (Decision 004)
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
@@ -418,9 +416,7 @@ func runMailRead(cmd *cobra.Command, args []string) error {
|
||||
if issue.Priority <= 1 {
|
||||
fmt.Printf("Priority: P%d\n", issue.Priority)
|
||||
}
|
||||
if issue.RepliesTo != "" {
|
||||
fmt.Printf("Re: %s\n", issue.RepliesTo)
|
||||
}
|
||||
// NOTE: Thread info (RepliesTo) now in dependencies (Decision 004)
|
||||
fmt.Printf("Status: %s\n", issue.Status)
|
||||
fmt.Println(strings.Repeat("─", 66))
|
||||
fmt.Println()
|
||||
@@ -593,10 +589,11 @@ func runMailReply(cmd *cobra.Command, args []string) error {
|
||||
Assignee: recipient,
|
||||
Sender: sender,
|
||||
Ephemeral: true,
|
||||
RepliesTo: messageID, // Thread link
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
// NOTE: RepliesTo now handled via dependency API (Decision 004)
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
_ = messageID // RepliesTo handled via CreateArgs.RepliesTo -> server creates dependency
|
||||
|
||||
if daemonClient != nil {
|
||||
// Daemon mode - create reply with all messaging fields
|
||||
|
||||
@@ -183,7 +183,7 @@ func TestMailReply(t *testing.T) {
|
||||
t.Fatalf("Failed to create original message: %v", err)
|
||||
}
|
||||
|
||||
// Create reply
|
||||
// Create reply (thread link now done via dependencies per Decision 004)
|
||||
reply := &types.Issue{
|
||||
Title: "Re: Original Subject",
|
||||
Description: "Reply body",
|
||||
@@ -193,7 +193,6 @@ func TestMailReply(t *testing.T) {
|
||||
Assignee: "manager", // Reply goes to original sender
|
||||
Sender: "worker",
|
||||
Ephemeral: true,
|
||||
RepliesTo: original.ID, // Thread link
|
||||
CreatedAt: now.Add(time.Minute),
|
||||
UpdatedAt: now.Add(time.Minute),
|
||||
}
|
||||
@@ -202,14 +201,31 @@ func TestMailReply(t *testing.T) {
|
||||
t.Fatalf("Failed to create reply: %v", err)
|
||||
}
|
||||
|
||||
// Verify reply has correct thread link
|
||||
savedReply, err := testStore.GetIssue(ctx, reply.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetIssue failed: %v", err)
|
||||
// Add replies-to dependency (thread link per Decision 004)
|
||||
dep := &types.Dependency{
|
||||
IssueID: reply.ID,
|
||||
DependsOnID: original.ID,
|
||||
Type: types.DepRepliesTo,
|
||||
}
|
||||
if err := testStore.AddDependency(ctx, dep, "test"); err != nil {
|
||||
t.Fatalf("Failed to add replies-to dependency: %v", err)
|
||||
}
|
||||
|
||||
if savedReply.RepliesTo != original.ID {
|
||||
t.Errorf("RepliesTo = %q, want %q", savedReply.RepliesTo, original.ID)
|
||||
// Verify reply has correct thread link via dependencies
|
||||
deps, err := testStore.GetDependenciesWithMetadata(ctx, reply.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetDependenciesWithMetadata failed: %v", err)
|
||||
}
|
||||
|
||||
var foundReplyLink bool
|
||||
for _, d := range deps {
|
||||
if d.DependencyType == types.DepRepliesTo && d.ID == original.ID {
|
||||
foundReplyLink = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundReplyLink {
|
||||
t.Errorf("Reply missing replies-to link to original message")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
156
cmd/bd/relate.go
156
cmd/bd/relate.go
@@ -119,47 +119,45 @@ func runRelate(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("issue not found: %s", id2)
|
||||
}
|
||||
|
||||
// Add id2 to issue1's relates_to if not already present
|
||||
if !contains(issue1.RelatesTo, id2) {
|
||||
newRelatesTo1 := append(issue1.RelatesTo, id2)
|
||||
formattedRelatesTo1 := formatRelatesTo(newRelatesTo1)
|
||||
if daemonClient != nil {
|
||||
// Use RPC for daemon mode (bd-fu83)
|
||||
_, 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 relates-to dependency: id1 -> id2 (bidirectional, so also id2 -> id1)
|
||||
// Per Decision 004, relates-to links are now stored in dependencies table
|
||||
if daemonClient != nil {
|
||||
// Add id1 -> id2
|
||||
_, err := daemonClient.AddDependency(&rpc.DepAddArgs{
|
||||
FromID: id1,
|
||||
ToID: id2,
|
||||
DepType: string(types.DepRelatesTo),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add relates-to %s -> %s: %w", id1, id2, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Add id1 to issue2's relates_to if not already present
|
||||
if !contains(issue2.RelatesTo, id1) {
|
||||
newRelatesTo2 := append(issue2.RelatesTo, id1)
|
||||
formattedRelatesTo2 := formatRelatesTo(newRelatesTo2)
|
||||
if daemonClient != nil {
|
||||
// Use RPC for daemon mode (bd-fu83)
|
||||
_, 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)
|
||||
}
|
||||
// Add id2 -> id1 (bidirectional)
|
||||
_, err = daemonClient.AddDependency(&rpc.DepAddArgs{
|
||||
FromID: id2,
|
||||
ToID: id1,
|
||||
DepType: string(types.DepRelatesTo),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add relates-to %s -> %s: %w", id2, id1, err)
|
||||
}
|
||||
} else {
|
||||
// Add id1 -> id2
|
||||
dep1 := &types.Dependency{
|
||||
IssueID: id1,
|
||||
DependsOnID: id2,
|
||||
Type: types.DepRelatesTo,
|
||||
}
|
||||
if err := store.AddDependency(ctx, dep1, actor); err != nil {
|
||||
return fmt.Errorf("failed to add relates-to %s -> %s: %w", id1, id2, err)
|
||||
}
|
||||
// Add id2 -> id1 (bidirectional)
|
||||
dep2 := &types.Dependency{
|
||||
IssueID: id2,
|
||||
DependsOnID: id1,
|
||||
Type: types.DepRelatesTo,
|
||||
}
|
||||
if err := store.AddDependency(ctx, dep2, actor); err != nil {
|
||||
return fmt.Errorf("failed to add relates-to %s -> %s: %w", id2, id1, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,43 +252,35 @@ func runUnrelate(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("issue not found: %s", id2)
|
||||
}
|
||||
|
||||
// Remove id2 from issue1's relates_to
|
||||
newRelatesTo1 := remove(issue1.RelatesTo, id2)
|
||||
formattedRelatesTo1 := formatRelatesTo(newRelatesTo1)
|
||||
// Remove relates-to dependency in both directions
|
||||
// Per Decision 004, relates-to links are now stored in dependencies table
|
||||
if daemonClient != nil {
|
||||
// Use RPC for daemon mode (bd-fu83)
|
||||
_, err := daemonClient.Update(&rpc.UpdateArgs{
|
||||
ID: id1,
|
||||
RelatesTo: &formattedRelatesTo1,
|
||||
// Remove id1 -> id2
|
||||
_, err := daemonClient.RemoveDependency(&rpc.DepRemoveArgs{
|
||||
FromID: id1,
|
||||
ToID: id2,
|
||||
DepType: string(types.DepRelatesTo),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update %s: %w", id1, err)
|
||||
return fmt.Errorf("failed to remove relates-to %s -> %s: %w", id1, id2, 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
|
||||
newRelatesTo2 := remove(issue2.RelatesTo, id1)
|
||||
formattedRelatesTo2 := formatRelatesTo(newRelatesTo2)
|
||||
if daemonClient != nil {
|
||||
// Use RPC for daemon mode (bd-fu83)
|
||||
_, err := daemonClient.Update(&rpc.UpdateArgs{
|
||||
ID: id2,
|
||||
RelatesTo: &formattedRelatesTo2,
|
||||
// Remove id2 -> id1 (bidirectional)
|
||||
_, err = daemonClient.RemoveDependency(&rpc.DepRemoveArgs{
|
||||
FromID: id2,
|
||||
ToID: id1,
|
||||
DepType: string(types.DepRelatesTo),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update %s: %w", id2, err)
|
||||
return fmt.Errorf("failed to remove relates-to %s -> %s: %w", id2, id1, 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)
|
||||
// Remove id1 -> id2
|
||||
if err := store.RemoveDependency(ctx, id1, id2, actor); err != nil {
|
||||
return fmt.Errorf("failed to remove relates-to %s -> %s: %w", id1, id2, err)
|
||||
}
|
||||
// Remove id2 -> id1 (bidirectional)
|
||||
if err := store.RemoveDependency(ctx, id2, id1, actor); err != nil {
|
||||
return fmt.Errorf("failed to remove relates-to %s -> %s: %w", id2, id1, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,29 +305,5 @@ func runUnrelate(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func contains(slice []string, item string) bool {
|
||||
for _, s := range slice {
|
||||
if s == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func remove(slice []string, item string) []string {
|
||||
result := make([]string, 0, len(slice))
|
||||
for _, s := range slice {
|
||||
if s != item {
|
||||
result = append(result, s)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func formatRelatesTo(ids []string) string {
|
||||
if len(ids) == 0 {
|
||||
return ""
|
||||
}
|
||||
data, _ := json.Marshal(ids)
|
||||
return string(data)
|
||||
}
|
||||
// Note: contains, remove, formatRelatesTo functions removed per Decision 004
|
||||
// relates-to links now use dependencies API instead of Issue.RelatesTo field
|
||||
|
||||
161
cmd/bd/show.go
161
cmd/bd/show.go
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/steveyegge/beads/internal/hooks"
|
||||
"github.com/steveyegge/beads/internal/rpc"
|
||||
"github.com/steveyegge/beads/internal/storage"
|
||||
"github.com/steveyegge/beads/internal/storage/sqlite"
|
||||
"github.com/steveyegge/beads/internal/types"
|
||||
"github.com/steveyegge/beads/internal/utils"
|
||||
@@ -1096,20 +1097,26 @@ func showMessageThread(ctx context.Context, messageID string, jsonOutput bool) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Find the root of the thread by following replies_to chain upward
|
||||
// Find the root of the thread by following replies-to dependencies upward
|
||||
// Per Decision 004, RepliesTo is now stored as a dependency, not an Issue field
|
||||
rootMsg := startMsg
|
||||
seen := make(map[string]bool)
|
||||
seen[rootMsg.ID] = true
|
||||
|
||||
for rootMsg.RepliesTo != "" {
|
||||
if seen[rootMsg.RepliesTo] {
|
||||
for {
|
||||
// Find parent via replies-to dependency
|
||||
parentID := findRepliesTo(ctx, rootMsg.ID, daemonClient, store)
|
||||
if parentID == "" {
|
||||
break // No parent, this is the root
|
||||
}
|
||||
if seen[parentID] {
|
||||
break // Avoid infinite loops
|
||||
}
|
||||
seen[rootMsg.RepliesTo] = true
|
||||
seen[parentID] = true
|
||||
|
||||
var parentMsg *types.Issue
|
||||
if daemonClient != nil {
|
||||
resp, err := daemonClient.Show(&rpc.ShowArgs{ID: rootMsg.RepliesTo})
|
||||
resp, err := daemonClient.Show(&rpc.ShowArgs{ID: parentID})
|
||||
if err != nil {
|
||||
break // Parent not found, use current as root
|
||||
}
|
||||
@@ -1117,7 +1124,7 @@ func showMessageThread(ctx context.Context, messageID string, jsonOutput bool) {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
parentMsg, _ = store.GetIssue(ctx, rootMsg.RepliesTo)
|
||||
parentMsg, _ = store.GetIssue(ctx, parentID)
|
||||
}
|
||||
if parentMsg == nil {
|
||||
break
|
||||
@@ -1127,8 +1134,10 @@ func showMessageThread(ctx context.Context, messageID string, jsonOutput bool) {
|
||||
|
||||
// Now collect all messages in the thread
|
||||
// Start from root and find all replies
|
||||
// Build a map of child ID -> parent ID for display purposes
|
||||
threadMessages := []*types.Issue{rootMsg}
|
||||
threadIDs := map[string]bool{rootMsg.ID: true}
|
||||
repliesTo := map[string]string{} // child ID -> parent ID
|
||||
queue := []string{rootMsg.ID}
|
||||
|
||||
// BFS to find all replies
|
||||
@@ -1136,39 +1145,17 @@ func showMessageThread(ctx context.Context, messageID string, jsonOutput bool) {
|
||||
currentID := queue[0]
|
||||
queue = queue[1:]
|
||||
|
||||
// Find all messages that reply to currentID
|
||||
var replies []*types.Issue
|
||||
if daemonClient != nil {
|
||||
// In daemon mode, search for messages with replies_to = currentID
|
||||
// Use list with a filter (simplified: we'll search all messages)
|
||||
// This is inefficient but works for now
|
||||
listArgs := &rpc.ListArgs{IssueType: "message"}
|
||||
resp, err := daemonClient.List(listArgs)
|
||||
if err == nil {
|
||||
var allMessages []*types.Issue
|
||||
if err := json.Unmarshal(resp.Data, &allMessages); err == nil {
|
||||
for _, msg := range allMessages {
|
||||
if msg.RepliesTo == currentID && !threadIDs[msg.ID] {
|
||||
replies = append(replies, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Direct mode - search for replies
|
||||
messageType := types.TypeMessage
|
||||
filter := types.IssueFilter{IssueType: &messageType}
|
||||
allMessages, _ := store.SearchIssues(ctx, "", filter)
|
||||
for _, msg := range allMessages {
|
||||
if msg.RepliesTo == currentID && !threadIDs[msg.ID] {
|
||||
replies = append(replies, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Find all messages that reply to currentID via replies-to dependency
|
||||
// Per Decision 004, replies are found via dependents with type replies-to
|
||||
replies := findReplies(ctx, currentID, daemonClient, store)
|
||||
|
||||
for _, reply := range replies {
|
||||
if threadIDs[reply.ID] {
|
||||
continue // Already seen
|
||||
}
|
||||
threadMessages = append(threadMessages, reply)
|
||||
threadIDs[reply.ID] = true
|
||||
repliesTo[reply.ID] = currentID // Track parent for display
|
||||
queue = append(queue, reply.ID)
|
||||
}
|
||||
}
|
||||
@@ -1193,18 +1180,12 @@ func showMessageThread(ctx context.Context, messageID string, jsonOutput bool) {
|
||||
fmt.Println(strings.Repeat("─", 66))
|
||||
|
||||
for _, msg := range threadMessages {
|
||||
// Show indent based on depth (count replies_to chain)
|
||||
// Show indent based on depth (count replies_to chain using our map)
|
||||
depth := 0
|
||||
parent := msg.RepliesTo
|
||||
parent := repliesTo[msg.ID]
|
||||
for parent != "" && depth < 5 {
|
||||
depth++
|
||||
// Find parent to get its replies_to
|
||||
for _, m := range threadMessages {
|
||||
if m.ID == parent {
|
||||
parent = m.RepliesTo
|
||||
break
|
||||
}
|
||||
}
|
||||
parent = repliesTo[parent]
|
||||
}
|
||||
indent := strings.Repeat(" ", depth)
|
||||
|
||||
@@ -1219,8 +1200,8 @@ func showMessageThread(ctx context.Context, messageID string, jsonOutput bool) {
|
||||
|
||||
fmt.Printf("%s%s %s %s\n", indent, statusIcon, cyan(msg.ID), dim(timeStr))
|
||||
fmt.Printf("%s From: %s To: %s\n", indent, msg.Sender, msg.Assignee)
|
||||
if msg.RepliesTo != "" {
|
||||
fmt.Printf("%s Re: %s\n", indent, msg.RepliesTo)
|
||||
if parentID := repliesTo[msg.ID]; parentID != "" {
|
||||
fmt.Printf("%s Re: %s\n", indent, parentID)
|
||||
}
|
||||
fmt.Printf("%s %s: %s\n", indent, dim("Subject"), msg.Title)
|
||||
if msg.Description != "" {
|
||||
@@ -1236,6 +1217,94 @@ func showMessageThread(ctx context.Context, messageID string, jsonOutput bool) {
|
||||
fmt.Printf("Total: %d messages in thread\n\n", len(threadMessages))
|
||||
}
|
||||
|
||||
// findRepliesTo finds the parent ID that this issue replies to via replies-to dependency.
|
||||
// Returns empty string if no parent found.
|
||||
func findRepliesTo(ctx context.Context, issueID string, daemonClient *rpc.Client, store storage.Storage) string {
|
||||
if daemonClient != nil {
|
||||
// In daemon mode, use Show to get dependencies with metadata
|
||||
resp, err := daemonClient.Show(&rpc.ShowArgs{ID: issueID})
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
// Parse the full show response to get dependencies
|
||||
type showResponse struct {
|
||||
Dependencies []struct {
|
||||
ID string `json:"id"`
|
||||
DependencyType string `json:"dependency_type"`
|
||||
} `json:"dependencies"`
|
||||
}
|
||||
var details showResponse
|
||||
if err := json.Unmarshal(resp.Data, &details); err != nil {
|
||||
return ""
|
||||
}
|
||||
for _, dep := range details.Dependencies {
|
||||
if dep.DependencyType == string(types.DepRepliesTo) {
|
||||
return dep.ID
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
// Direct mode - query storage
|
||||
if sqliteStore, ok := store.(*sqlite.SQLiteStorage); ok {
|
||||
deps, err := sqliteStore.GetDependenciesWithMetadata(ctx, issueID)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
for _, dep := range deps {
|
||||
if dep.DependencyType == types.DepRepliesTo {
|
||||
return dep.ID
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// findReplies finds all issues that reply to this issue via replies-to dependency.
|
||||
func findReplies(ctx context.Context, issueID string, daemonClient *rpc.Client, store storage.Storage) []*types.Issue {
|
||||
if daemonClient != nil {
|
||||
// In daemon mode, use Show to get dependents with metadata
|
||||
resp, err := daemonClient.Show(&rpc.ShowArgs{ID: issueID})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
// Parse the full show response to get dependents
|
||||
type showResponse struct {
|
||||
Dependents []struct {
|
||||
types.Issue
|
||||
DependencyType string `json:"dependency_type"`
|
||||
} `json:"dependents"`
|
||||
}
|
||||
var details showResponse
|
||||
if err := json.Unmarshal(resp.Data, &details); err != nil {
|
||||
return nil
|
||||
}
|
||||
var replies []*types.Issue
|
||||
for _, dep := range details.Dependents {
|
||||
if dep.DependencyType == string(types.DepRepliesTo) {
|
||||
issue := dep.Issue // Copy to avoid aliasing
|
||||
replies = append(replies, &issue)
|
||||
}
|
||||
}
|
||||
return replies
|
||||
}
|
||||
// Direct mode - query storage
|
||||
if sqliteStore, ok := store.(*sqlite.SQLiteStorage); ok {
|
||||
deps, err := sqliteStore.GetDependentsWithMetadata(ctx, issueID)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var replies []*types.Issue
|
||||
for _, dep := range deps {
|
||||
if dep.DependencyType == types.DepRepliesTo {
|
||||
issue := dep.Issue // Copy to avoid aliasing
|
||||
replies = append(replies, &issue)
|
||||
}
|
||||
}
|
||||
return replies
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
showCmd.Flags().Bool("json", false, "Output JSON format")
|
||||
showCmd.Flags().Bool("thread", false, "Show full conversation thread (for messages)")
|
||||
|
||||
Reference in New Issue
Block a user