fix: Use FatalErrorRespectJSON across all commands (bd-28sq)

Convert all fmt.Fprintf(os.Stderr, ...) + os.Exit(1) patterns to use
FatalErrorRespectJSON for consistent JSON error output:
- dep.go: dependency commands (add, remove, tree, cycles)
- label.go: label commands (add, remove, list, list-all)
- comments.go: comment commands (list, add)
- epic.go: epic commands (status, close-eligible)

🤖 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-25 13:56:19 -08:00
parent f56f3615e8
commit efdaa93789
4 changed files with 73 additions and 144 deletions

View File

@@ -42,17 +42,14 @@ Examples:
if err != nil { if err != nil {
if isUnknownOperationError(err) { if isUnknownOperationError(err) {
if err := fallbackToDirectMode("daemon does not support comment_list RPC"); err != nil { if err := fallbackToDirectMode("daemon does not support comment_list RPC"); err != nil {
fmt.Fprintf(os.Stderr, "Error getting comments: %v\n", err) FatalErrorRespectJSON("getting comments: %v", err)
os.Exit(1)
} }
} else { } else {
fmt.Fprintf(os.Stderr, "Error getting comments: %v\n", err) FatalErrorRespectJSON("getting comments: %v", err)
os.Exit(1)
} }
} else { } else {
if err := json.Unmarshal(resp.Data, &comments); err != nil { if err := json.Unmarshal(resp.Data, &comments); err != nil {
fmt.Fprintf(os.Stderr, "Error decoding comments: %v\n", err) FatalErrorRespectJSON("decoding comments: %v", err)
os.Exit(1)
} }
usedDaemon = true usedDaemon = true
} }
@@ -60,21 +57,18 @@ Examples:
if !usedDaemon { if !usedDaemon {
if err := ensureStoreActive(); err != nil { if err := ensureStoreActive(); err != nil {
fmt.Fprintf(os.Stderr, "Error getting comments: %v\n", err) FatalErrorRespectJSON("getting comments: %v", err)
os.Exit(1)
} }
ctx := rootCtx ctx := rootCtx
fullID, err := utils.ResolvePartialID(ctx, store, issueID) fullID, err := utils.ResolvePartialID(ctx, store, issueID)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error resolving %s: %v\n", issueID, err) FatalErrorRespectJSON("resolving %s: %v", issueID, err)
os.Exit(1)
} }
issueID = fullID issueID = fullID
result, err := store.GetIssueComments(ctx, issueID) result, err := store.GetIssueComments(ctx, issueID)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error getting comments: %v\n", err) FatalErrorRespectJSON("getting comments: %v", err)
os.Exit(1)
} }
comments = result comments = result
} }
@@ -87,8 +81,7 @@ Examples:
if jsonOutput { if jsonOutput {
data, err := json.MarshalIndent(comments, "", " ") data, err := json.MarshalIndent(comments, "", " ")
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error encoding JSON: %v\n", err) FatalErrorRespectJSON("encoding JSON: %v", err)
os.Exit(1)
} }
fmt.Println(string(data)) fmt.Println(string(data))
return return
@@ -130,13 +123,11 @@ Examples:
// Read from file // Read from file
data, err := os.ReadFile(commentText) // #nosec G304 - user-provided file path is intentional data, err := os.ReadFile(commentText) // #nosec G304 - user-provided file path is intentional
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error reading file: %v\n", err) FatalErrorRespectJSON("reading file: %v", err)
os.Exit(1)
} }
commentText = string(data) commentText = string(data)
} else if len(args) < 2 { } else if len(args) < 2 {
fmt.Fprintf(os.Stderr, "Error: comment text required (use -f to read from file)\n") FatalErrorRespectJSON("comment text required (use -f to read from file)")
os.Exit(1)
} else { } else {
commentText = args[1] commentText = args[1]
} }
@@ -167,18 +158,15 @@ Examples:
if err != nil { if err != nil {
if isUnknownOperationError(err) { if isUnknownOperationError(err) {
if err := fallbackToDirectMode("daemon does not support comment_add RPC"); err != nil { if err := fallbackToDirectMode("daemon does not support comment_add RPC"); err != nil {
fmt.Fprintf(os.Stderr, "Error adding comment: %v\n", err) FatalErrorRespectJSON("adding comment: %v", err)
os.Exit(1)
} }
} else { } else {
fmt.Fprintf(os.Stderr, "Error adding comment: %v\n", err) FatalErrorRespectJSON("adding comment: %v", err)
os.Exit(1)
} }
} else { } else {
var parsed types.Comment var parsed types.Comment
if err := json.Unmarshal(resp.Data, &parsed); err != nil { if err := json.Unmarshal(resp.Data, &parsed); err != nil {
fmt.Fprintf(os.Stderr, "Error decoding comment: %v\n", err) FatalErrorRespectJSON("decoding comment: %v", err)
os.Exit(1)
} }
comment = &parsed comment = &parsed
} }
@@ -186,30 +174,26 @@ Examples:
if comment == nil { if comment == nil {
if err := ensureStoreActive(); err != nil { if err := ensureStoreActive(); err != nil {
fmt.Fprintf(os.Stderr, "Error adding comment: %v\n", err) FatalErrorRespectJSON("adding comment: %v", err)
os.Exit(1)
} }
ctx := rootCtx ctx := rootCtx
fullID, err := utils.ResolvePartialID(ctx, store, issueID) fullID, err := utils.ResolvePartialID(ctx, store, issueID)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error resolving %s: %v\n", issueID, err) FatalErrorRespectJSON("resolving %s: %v", issueID, err)
os.Exit(1)
} }
issueID = fullID issueID = fullID
comment, err = store.AddIssueComment(ctx, issueID, author, commentText) comment, err = store.AddIssueComment(ctx, issueID, author, commentText)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error adding comment: %v\n", err) FatalErrorRespectJSON("adding comment: %v", err)
os.Exit(1)
} }
} }
if jsonOutput { if jsonOutput {
data, err := json.MarshalIndent(comment, "", " ") data, err := json.MarshalIndent(comment, "", " ")
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error encoding JSON: %v\n", err) FatalErrorRespectJSON("encoding JSON: %v", err)
os.Exit(1)
} }
fmt.Println(string(data)) fmt.Println(string(data))
return return

View File

@@ -71,12 +71,10 @@ Examples:
resolveArgs := &rpc.ResolveIDArgs{ID: args[0]} resolveArgs := &rpc.ResolveIDArgs{ID: args[0]}
resp, err := daemonClient.ResolveID(resolveArgs) resp, err := daemonClient.ResolveID(resolveArgs)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error resolving issue ID %s: %v\n", args[0], err) FatalErrorRespectJSON("resolving issue ID %s: %v", args[0], err)
os.Exit(1)
} }
if err := json.Unmarshal(resp.Data, &fromID); err != nil { if err := json.Unmarshal(resp.Data, &fromID); err != nil {
fmt.Fprintf(os.Stderr, "Error unmarshaling resolved ID: %v\n", err) FatalErrorRespectJSON("unmarshaling resolved ID: %v", err)
os.Exit(1)
} }
if isExternalRef { if isExternalRef {
@@ -84,27 +82,23 @@ Examples:
toID = args[1] toID = args[1]
// Validate format: external:<project>:<capability> // Validate format: external:<project>:<capability>
if err := validateExternalRef(toID); err != nil { if err := validateExternalRef(toID); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err) FatalErrorRespectJSON("%v", err)
os.Exit(1)
} }
} else { } else {
resolveArgs = &rpc.ResolveIDArgs{ID: args[1]} resolveArgs = &rpc.ResolveIDArgs{ID: args[1]}
resp, err = daemonClient.ResolveID(resolveArgs) resp, err = daemonClient.ResolveID(resolveArgs)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error resolving dependency ID %s: %v\n", args[1], err) FatalErrorRespectJSON("resolving dependency ID %s: %v", args[1], err)
os.Exit(1)
} }
if err := json.Unmarshal(resp.Data, &toID); err != nil { if err := json.Unmarshal(resp.Data, &toID); err != nil {
fmt.Fprintf(os.Stderr, "Error unmarshaling resolved ID: %v\n", err) FatalErrorRespectJSON("unmarshaling resolved ID: %v", err)
os.Exit(1)
} }
} }
} else { } else {
var err error var err error
fromID, err = utils.ResolvePartialID(ctx, store, args[0]) fromID, err = utils.ResolvePartialID(ctx, store, args[0])
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error resolving issue ID %s: %v\n", args[0], err) FatalErrorRespectJSON("resolving issue ID %s: %v", args[0], err)
os.Exit(1)
} }
if isExternalRef { if isExternalRef {
@@ -112,14 +106,12 @@ Examples:
toID = args[1] toID = args[1]
// Validate format: external:<project>:<capability> // Validate format: external:<project>:<capability>
if err := validateExternalRef(toID); err != nil { if err := validateExternalRef(toID); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err) FatalErrorRespectJSON("%v", err)
os.Exit(1)
} }
} else { } else {
toID, err = utils.ResolvePartialID(ctx, store, args[1]) toID, err = utils.ResolvePartialID(ctx, store, args[1])
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error resolving dependency ID %s: %v\n", args[1], err) FatalErrorRespectJSON("resolving dependency ID %s: %v", args[1], err)
os.Exit(1)
} }
} }
} }
@@ -127,10 +119,7 @@ Examples:
// Check for child→parent dependency anti-pattern (bd-nim5) // Check for child→parent dependency anti-pattern (bd-nim5)
// This creates a deadlock: child can't start (parent open), parent can't close (children not done) // This creates a deadlock: child can't start (parent open), parent can't close (children not done)
if isChildOf(fromID, toID) { if isChildOf(fromID, toID) {
fmt.Fprintf(os.Stderr, "Error: Cannot add dependency: %s is already a child of %s.\n", fromID, toID) FatalErrorRespectJSON("cannot add dependency: %s is already a child of %s. Children inherit dependency on parent completion via hierarchy. Adding an explicit dependency would create a deadlock", fromID, toID)
fmt.Fprintf(os.Stderr, "Children inherit dependency on parent completion via hierarchy.\n")
fmt.Fprintf(os.Stderr, "Adding an explicit dependency would create a deadlock.\n")
os.Exit(1)
} }
// If daemon is running, use RPC // If daemon is running, use RPC
@@ -143,8 +132,7 @@ Examples:
resp, err := daemonClient.AddDependency(depArgs) resp, err := daemonClient.AddDependency(depArgs)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err) FatalErrorRespectJSON("%v", err)
os.Exit(1)
} }
if jsonOutput { if jsonOutput {
@@ -165,8 +153,7 @@ Examples:
} }
if err := store.AddDependency(ctx, dep, actor); err != nil { if err := store.AddDependency(ctx, dep, actor); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err) FatalErrorRespectJSON("%v", err)
os.Exit(1)
} }
// Schedule auto-flush // Schedule auto-flush
@@ -225,36 +212,30 @@ var depRemoveCmd = &cobra.Command{
resolveArgs := &rpc.ResolveIDArgs{ID: args[0]} resolveArgs := &rpc.ResolveIDArgs{ID: args[0]}
resp, err := daemonClient.ResolveID(resolveArgs) resp, err := daemonClient.ResolveID(resolveArgs)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error resolving issue ID %s: %v\n", args[0], err) FatalErrorRespectJSON("resolving issue ID %s: %v", args[0], err)
os.Exit(1)
} }
if err := json.Unmarshal(resp.Data, &fromID); err != nil { if err := json.Unmarshal(resp.Data, &fromID); err != nil {
fmt.Fprintf(os.Stderr, "Error unmarshaling resolved ID: %v\n", err) FatalErrorRespectJSON("unmarshaling resolved ID: %v", err)
os.Exit(1)
} }
resolveArgs = &rpc.ResolveIDArgs{ID: args[1]} resolveArgs = &rpc.ResolveIDArgs{ID: args[1]}
resp, err = daemonClient.ResolveID(resolveArgs) resp, err = daemonClient.ResolveID(resolveArgs)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error resolving dependency ID %s: %v\n", args[1], err) FatalErrorRespectJSON("resolving dependency ID %s: %v", args[1], err)
os.Exit(1)
} }
if err := json.Unmarshal(resp.Data, &toID); err != nil { if err := json.Unmarshal(resp.Data, &toID); err != nil {
fmt.Fprintf(os.Stderr, "Error unmarshaling resolved ID: %v\n", err) FatalErrorRespectJSON("unmarshaling resolved ID: %v", err)
os.Exit(1)
} }
} else { } else {
var err error var err error
fromID, err = utils.ResolvePartialID(ctx, store, args[0]) fromID, err = utils.ResolvePartialID(ctx, store, args[0])
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error resolving issue ID %s: %v\n", args[0], err) FatalErrorRespectJSON("resolving issue ID %s: %v", args[0], err)
os.Exit(1)
} }
toID, err = utils.ResolvePartialID(ctx, store, args[1]) toID, err = utils.ResolvePartialID(ctx, store, args[1])
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error resolving dependency ID %s: %v\n", args[1], err) FatalErrorRespectJSON("resolving dependency ID %s: %v", args[1], err)
os.Exit(1)
} }
} }
@@ -267,8 +248,7 @@ var depRemoveCmd = &cobra.Command{
resp, err := daemonClient.RemoveDependency(depArgs) resp, err := daemonClient.RemoveDependency(depArgs)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err) FatalErrorRespectJSON("%v", err)
os.Exit(1)
} }
if jsonOutput { if jsonOutput {
@@ -286,8 +266,7 @@ var depRemoveCmd = &cobra.Command{
fullToID := toID fullToID := toID
if err := store.RemoveDependency(ctx, fullFromID, fullToID, actor); err != nil { if err := store.RemoveDependency(ctx, fullFromID, fullToID, actor); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err) FatalErrorRespectJSON("%v", err)
os.Exit(1)
} }
// Schedule auto-flush // Schedule auto-flush
@@ -332,19 +311,16 @@ Examples:
resolveArgs := &rpc.ResolveIDArgs{ID: args[0]} resolveArgs := &rpc.ResolveIDArgs{ID: args[0]}
resp, err := daemonClient.ResolveID(resolveArgs) resp, err := daemonClient.ResolveID(resolveArgs)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error resolving issue ID %s: %v\n", args[0], err) FatalErrorRespectJSON("resolving issue ID %s: %v", args[0], err)
os.Exit(1)
} }
if err := json.Unmarshal(resp.Data, &fullID); err != nil { if err := json.Unmarshal(resp.Data, &fullID); err != nil {
fmt.Fprintf(os.Stderr, "Error unmarshaling resolved ID: %v\n", err) FatalErrorRespectJSON("unmarshaling resolved ID: %v", err)
os.Exit(1)
} }
} else { } else {
var err error var err error
fullID, err = utils.ResolvePartialID(ctx, store, args[0]) fullID, err = utils.ResolvePartialID(ctx, store, args[0])
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error resolving %s: %v\n", args[0], err) FatalErrorRespectJSON("resolving %s: %v", args[0], err)
os.Exit(1)
} }
} }
@@ -353,8 +329,7 @@ Examples:
var err error var err error
store, err = sqlite.New(rootCtx, dbPath) store, err = sqlite.New(rootCtx, dbPath)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error: failed to open database: %v\n", err) FatalErrorRespectJSON("failed to open database: %v", err)
os.Exit(1)
} }
defer func() { _ = store.Close() }() defer func() { _ = store.Close() }()
} }
@@ -375,13 +350,11 @@ Examples:
// Validate direction // Validate direction
if direction != "down" && direction != "up" && direction != "both" { if direction != "down" && direction != "up" && direction != "both" {
fmt.Fprintf(os.Stderr, "Error: --direction must be 'down', 'up', or 'both'\n") FatalErrorRespectJSON("--direction must be 'down', 'up', or 'both'")
os.Exit(1)
} }
if maxDepth < 1 { if maxDepth < 1 {
fmt.Fprintf(os.Stderr, "Error: --max-depth must be >= 1\n") FatalErrorRespectJSON("--max-depth must be >= 1")
os.Exit(1)
} }
// For "both" direction, we need to fetch both trees and merge them // For "both" direction, we need to fetch both trees and merge them
@@ -392,15 +365,13 @@ Examples:
// Get dependencies (down) - what blocks this issue // Get dependencies (down) - what blocks this issue
downTree, err := store.GetDependencyTree(ctx, fullID, maxDepth, showAllPaths, false) downTree, err := store.GetDependencyTree(ctx, fullID, maxDepth, showAllPaths, false)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err) FatalErrorRespectJSON("%v", err)
os.Exit(1)
} }
// Get dependents (up) - what this issue blocks // Get dependents (up) - what this issue blocks
upTree, err := store.GetDependencyTree(ctx, fullID, maxDepth, showAllPaths, true) upTree, err := store.GetDependencyTree(ctx, fullID, maxDepth, showAllPaths, true)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err) FatalErrorRespectJSON("%v", err)
os.Exit(1)
} }
// Merge: root appears once, dependencies below, dependents above // Merge: root appears once, dependencies below, dependents above
@@ -410,8 +381,7 @@ Examples:
} else { } else {
tree, err = store.GetDependencyTree(ctx, fullID, maxDepth, showAllPaths, direction == "up") tree, err = store.GetDependencyTree(ctx, fullID, maxDepth, showAllPaths, direction == "up")
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err) FatalErrorRespectJSON("%v", err)
os.Exit(1)
} }
} }
@@ -471,8 +441,7 @@ var depCyclesCmd = &cobra.Command{
var err error var err error
store, err = sqlite.New(rootCtx, dbPath) store, err = sqlite.New(rootCtx, dbPath)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error: failed to open database: %v\n", err) FatalErrorRespectJSON("failed to open database: %v", err)
os.Exit(1)
} }
defer func() { _ = store.Close() }() defer func() { _ = store.Close() }()
} }
@@ -480,8 +449,7 @@ var depCyclesCmd = &cobra.Command{
ctx := rootCtx ctx := rootCtx
cycles, err := store.DetectCycles(ctx) cycles, err := store.DetectCycles(ctx)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err) FatalErrorRespectJSON("%v", err)
os.Exit(1)
} }
if jsonOutput { if jsonOutput {

View File

@@ -26,23 +26,19 @@ var epicStatusCmd = &cobra.Command{
EligibleOnly: eligibleOnly, EligibleOnly: eligibleOnly,
}) })
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error communicating with daemon: %v\n", err) FatalErrorRespectJSON("communicating with daemon: %v", err)
os.Exit(1)
} }
if !resp.Success { if !resp.Success {
fmt.Fprintf(os.Stderr, "Error getting epic status: %s\n", resp.Error) FatalErrorRespectJSON("getting epic status: %s", resp.Error)
os.Exit(1)
} }
if err := json.Unmarshal(resp.Data, &epics); err != nil { if err := json.Unmarshal(resp.Data, &epics); err != nil {
fmt.Fprintf(os.Stderr, "Error parsing response: %v\n", err) FatalErrorRespectJSON("parsing response: %v", err)
os.Exit(1)
} }
} else { } else {
ctx := rootCtx ctx := rootCtx
epics, err = store.GetEpicsEligibleForClosure(ctx) epics, err = store.GetEpicsEligibleForClosure(ctx)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error getting epic status: %v\n", err) FatalErrorRespectJSON("getting epic status: %v", err)
os.Exit(1)
} }
if eligibleOnly { if eligibleOnly {
filtered := []*types.EpicStatus{} filtered := []*types.EpicStatus{}
@@ -58,8 +54,7 @@ var epicStatusCmd = &cobra.Command{
enc := json.NewEncoder(os.Stdout) enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ") enc.SetIndent("", " ")
if err := enc.Encode(epics); err != nil { if err := enc.Encode(epics); err != nil {
fmt.Fprintf(os.Stderr, "Error encoding JSON: %v\n", err) FatalErrorRespectJSON("encoding JSON: %v", err)
os.Exit(1)
} }
return return
} }
@@ -108,23 +103,19 @@ var closeEligibleEpicsCmd = &cobra.Command{
EligibleOnly: true, EligibleOnly: true,
}) })
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error communicating with daemon: %v\n", err) FatalErrorRespectJSON("communicating with daemon: %v", err)
os.Exit(1)
} }
if !resp.Success { if !resp.Success {
fmt.Fprintf(os.Stderr, "Error getting eligible epics: %s\n", resp.Error) FatalErrorRespectJSON("getting eligible epics: %s", resp.Error)
os.Exit(1)
} }
if err := json.Unmarshal(resp.Data, &eligibleEpics); err != nil { if err := json.Unmarshal(resp.Data, &eligibleEpics); err != nil {
fmt.Fprintf(os.Stderr, "Error parsing response: %v\n", err) FatalErrorRespectJSON("parsing response: %v", err)
os.Exit(1)
} }
} else { } else {
ctx := rootCtx ctx := rootCtx
epics, err := store.GetEpicsEligibleForClosure(ctx) epics, err := store.GetEpicsEligibleForClosure(ctx)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error getting eligible epics: %v\n", err) FatalErrorRespectJSON("getting eligible epics: %v", err)
os.Exit(1)
} }
for _, epic := range epics { for _, epic := range epics {
if epic.EligibleForClose { if epic.EligibleForClose {
@@ -145,8 +136,7 @@ var closeEligibleEpicsCmd = &cobra.Command{
enc := json.NewEncoder(os.Stdout) enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ") enc.SetIndent("", " ")
if err := enc.Encode(eligibleEpics); err != nil { if err := enc.Encode(eligibleEpics); err != nil {
fmt.Fprintf(os.Stderr, "Error encoding JSON: %v\n", err) FatalErrorRespectJSON("encoding JSON: %v", err)
os.Exit(1)
} }
} else { } else {
fmt.Printf("Would close %d epic(s):\n", len(eligibleEpics)) fmt.Printf("Would close %d epic(s):\n", len(eligibleEpics))
@@ -191,8 +181,7 @@ var closeEligibleEpicsCmd = &cobra.Command{
"closed": closedIDs, "closed": closedIDs,
"count": len(closedIDs), "count": len(closedIDs),
}); err != nil { }); err != nil {
fmt.Fprintf(os.Stderr, "Error encoding JSON: %v\n", err) FatalErrorRespectJSON("encoding JSON: %v", err)
os.Exit(1)
} }
} else { } else {
fmt.Printf("✓ Closed %d epic(s)\n", len(closedIDs)) fmt.Printf("✓ Closed %d epic(s)\n", len(closedIDs))

View File

@@ -102,9 +102,7 @@ var labelAddCmd = &cobra.Command{
// Protect reserved label namespaces (bd-eijl) // Protect reserved label namespaces (bd-eijl)
// provides:* labels can only be added via 'bd ship' command // provides:* labels can only be added via 'bd ship' command
if strings.HasPrefix(label, "provides:") { if strings.HasPrefix(label, "provides:") {
fmt.Fprintf(os.Stderr, "Error: 'provides:' labels are reserved for cross-project capabilities\n") FatalErrorRespectJSON("'provides:' labels are reserved for cross-project capabilities. Hint: use 'bd ship %s' instead", strings.TrimPrefix(label, "provides:"))
fmt.Fprintf(os.Stderr, "Hint: use 'bd ship %s' instead\n", strings.TrimPrefix(label, "provides:"))
os.Exit(1)
} }
processBatchLabelOperation(issueIDs, label, "added", jsonOutput, processBatchLabelOperation(issueIDs, label, "added", jsonOutput,
@@ -176,19 +174,16 @@ var labelListCmd = &cobra.Command{
resolveArgs := &rpc.ResolveIDArgs{ID: args[0]} resolveArgs := &rpc.ResolveIDArgs{ID: args[0]}
resp, err := daemonClient.ResolveID(resolveArgs) resp, err := daemonClient.ResolveID(resolveArgs)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error resolving issue ID %s: %v\n", args[0], err) FatalErrorRespectJSON("resolving issue ID %s: %v", args[0], err)
os.Exit(1)
} }
if err := json.Unmarshal(resp.Data, &issueID); err != nil { if err := json.Unmarshal(resp.Data, &issueID); err != nil {
fmt.Fprintf(os.Stderr, "Error unmarshaling resolved ID: %v\n", err) FatalErrorRespectJSON("unmarshaling resolved ID: %v", err)
os.Exit(1)
} }
} else { } else {
var err error var err error
issueID, err = utils.ResolvePartialID(ctx, store, args[0]) issueID, err = utils.ResolvePartialID(ctx, store, args[0])
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error resolving %s: %v\n", args[0], err) FatalErrorRespectJSON("resolving %s: %v", args[0], err)
os.Exit(1)
} }
} }
var labels []string var labels []string
@@ -196,13 +191,11 @@ var labelListCmd = &cobra.Command{
if daemonClient != nil { if daemonClient != nil {
resp, err := daemonClient.Show(&rpc.ShowArgs{ID: issueID}) resp, err := daemonClient.Show(&rpc.ShowArgs{ID: issueID})
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err) FatalErrorRespectJSON("%v", err)
os.Exit(1)
} }
var issue types.Issue var issue types.Issue
if err := json.Unmarshal(resp.Data, &issue); err != nil { if err := json.Unmarshal(resp.Data, &issue); err != nil {
fmt.Fprintf(os.Stderr, "Error parsing response: %v\n", err) FatalErrorRespectJSON("parsing response: %v", err)
os.Exit(1)
} }
labels = issue.Labels labels = issue.Labels
} else { } else {
@@ -210,8 +203,7 @@ var labelListCmd = &cobra.Command{
var err error var err error
labels, err = store.GetLabels(ctx, issueID) labels, err = store.GetLabels(ctx, issueID)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err) FatalErrorRespectJSON("%v", err)
os.Exit(1)
} }
} }
if jsonOutput { if jsonOutput {
@@ -245,19 +237,16 @@ var labelListAllCmd = &cobra.Command{
if daemonClient != nil { if daemonClient != nil {
resp, err := daemonClient.List(&rpc.ListArgs{}) resp, err := daemonClient.List(&rpc.ListArgs{})
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err) FatalErrorRespectJSON("%v", err)
os.Exit(1)
} }
if err := json.Unmarshal(resp.Data, &issues); err != nil { if err := json.Unmarshal(resp.Data, &issues); err != nil {
fmt.Fprintf(os.Stderr, "Error parsing response: %v\n", err) FatalErrorRespectJSON("parsing response: %v", err)
os.Exit(1)
} }
} else { } else {
// Direct mode // Direct mode
issues, err = store.SearchIssues(ctx, "", types.IssueFilter{}) issues, err = store.SearchIssues(ctx, "", types.IssueFilter{})
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err) FatalErrorRespectJSON("%v", err)
os.Exit(1)
} }
} }
// Collect unique labels with counts // Collect unique labels with counts
@@ -272,8 +261,7 @@ var labelListAllCmd = &cobra.Command{
// Direct mode - need to fetch labels // Direct mode - need to fetch labels
labels, err := store.GetLabels(ctx, issue.ID) labels, err := store.GetLabels(ctx, issue.ID)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error getting labels for %s: %v\n", issue.ID, err) FatalErrorRespectJSON("getting labels for %s: %v", issue.ID, err)
os.Exit(1)
} }
for _, label := range labels { for _, label := range labels {
labelCounts[label]++ labelCounts[label]++