Add --json flag support to more bd commands

- stats: Added --json flag for programmatic output
- show, update: Added --json flag for issue details
- close, reopen: Added --json flag for bulk operations
- dep (add, remove, tree, cycles): Added --json flags
- label (add, remove, list, list-all): Added --json flags
- duplicates, merge: Added --json flags

Closes bd-4dcd2d09
This commit is contained in:
Steve Yegge
2025-10-31 14:36:20 -07:00
parent b2b0373d08
commit 118d7541dd
7 changed files with 43 additions and 5 deletions

View File

@@ -25,6 +25,7 @@ var depAddCmd = &cobra.Command{
Args: cobra.ExactArgs(2), Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
depType, _ := cmd.Flags().GetString("type") depType, _ := cmd.Flags().GetString("type")
jsonOutput, _ := cmd.Flags().GetBool("json")
ctx := context.Background() ctx := context.Background()
@@ -147,6 +148,7 @@ var depRemoveCmd = &cobra.Command{
Short: "Remove a dependency", Short: "Remove a dependency",
Args: cobra.ExactArgs(2), Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
jsonOutput, _ := cmd.Flags().GetBool("json")
ctx := context.Background() ctx := context.Background()
// Resolve partial IDs first // Resolve partial IDs first
@@ -238,6 +240,7 @@ var depTreeCmd = &cobra.Command{
Short: "Show dependency tree", Short: "Show dependency tree",
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
jsonOutput, _ := cmd.Flags().GetBool("json")
ctx := context.Background() ctx := context.Background()
// Resolve partial ID first // Resolve partial ID first
@@ -338,6 +341,8 @@ var depCyclesCmd = &cobra.Command{
Use: "cycles", Use: "cycles",
Short: "Detect dependency cycles", Short: "Detect dependency cycles",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
jsonOutput, _ := cmd.Flags().GetBool("json")
// If daemon is running but doesn't support this command, use direct storage // If daemon is running but doesn't support this command, use direct storage
if daemonClient != nil && store == nil { if daemonClient != nil && store == nil {
var err error var err error
@@ -385,9 +390,17 @@ var depCyclesCmd = &cobra.Command{
func init() { func init() {
depAddCmd.Flags().StringP("type", "t", "blocks", "Dependency type (blocks|related|parent-child|discovered-from)") depAddCmd.Flags().StringP("type", "t", "blocks", "Dependency type (blocks|related|parent-child|discovered-from)")
depAddCmd.Flags().Bool("json", false, "Output JSON format")
depRemoveCmd.Flags().Bool("json", false, "Output JSON format")
depTreeCmd.Flags().Bool("show-all-paths", false, "Show all paths to nodes (no deduplication for diamond dependencies)") depTreeCmd.Flags().Bool("show-all-paths", false, "Show all paths to nodes (no deduplication for diamond dependencies)")
depTreeCmd.Flags().IntP("max-depth", "d", 50, "Maximum tree depth to display (safety limit)") depTreeCmd.Flags().IntP("max-depth", "d", 50, "Maximum tree depth to display (safety limit)")
depTreeCmd.Flags().Bool("reverse", false, "Show dependent tree (what was discovered from this) instead of dependency tree (what blocks this)") depTreeCmd.Flags().Bool("reverse", false, "Show dependent tree (what was discovered from this) instead of dependency tree (what blocks this)")
depTreeCmd.Flags().Bool("json", false, "Output JSON format")
depCyclesCmd.Flags().Bool("json", false, "Output JSON format")
depCmd.AddCommand(depAddCmd) depCmd.AddCommand(depAddCmd)
depCmd.AddCommand(depRemoveCmd) depCmd.AddCommand(depRemoveCmd)
depCmd.AddCommand(depTreeCmd) depCmd.AddCommand(depTreeCmd)

View File

@@ -38,6 +38,7 @@ Example:
autoMerge, _ := cmd.Flags().GetBool("auto-merge") autoMerge, _ := cmd.Flags().GetBool("auto-merge")
dryRun, _ := cmd.Flags().GetBool("dry-run") dryRun, _ := cmd.Flags().GetBool("dry-run")
jsonOutput, _ := cmd.Flags().GetBool("json")
ctx := context.Background() ctx := context.Background()
@@ -174,6 +175,7 @@ Example:
func init() { func init() {
duplicatesCmd.Flags().Bool("auto-merge", false, "Automatically merge all duplicates") duplicatesCmd.Flags().Bool("auto-merge", false, "Automatically merge all duplicates")
duplicatesCmd.Flags().Bool("dry-run", false, "Show what would be merged without making changes") duplicatesCmd.Flags().Bool("dry-run", false, "Show what would be merged without making changes")
duplicatesCmd.Flags().Bool("json", false, "Output JSON format")
rootCmd.AddCommand(duplicatesCmd) rootCmd.AddCommand(duplicatesCmd)
} }

View File

@@ -22,7 +22,7 @@ var labelCmd = &cobra.Command{
} }
// Helper function to process label operations for multiple issues // Helper function to process label operations for multiple issues
func processBatchLabelOperation(issueIDs []string, label string, operation string, func processBatchLabelOperation(issueIDs []string, label string, operation string, jsonOut bool,
daemonFunc func(string, string) error, storeFunc func(context.Context, string, string, string) error) { daemonFunc func(string, string) error, storeFunc func(context.Context, string, string, string) error) {
ctx := context.Background() ctx := context.Background()
results := []map[string]interface{}{} results := []map[string]interface{}{}
@@ -40,7 +40,7 @@ func processBatchLabelOperation(issueIDs []string, label string, operation strin
continue continue
} }
if jsonOutput { if jsonOut {
results = append(results, map[string]interface{}{ results = append(results, map[string]interface{}{
"status": operation, "status": operation,
"issue_id": issueID, "issue_id": issueID,
@@ -62,7 +62,7 @@ func processBatchLabelOperation(issueIDs []string, label string, operation strin
markDirtyAndScheduleFlush() markDirtyAndScheduleFlush()
} }
if jsonOutput && len(results) > 0 { if jsonOut && len(results) > 0 {
outputJSON(results) outputJSON(results)
} }
} }
@@ -79,6 +79,7 @@ var labelAddCmd = &cobra.Command{
Short: "Add a label to one or more issues", Short: "Add a label to one or more issues",
Args: cobra.MinimumNArgs(2), Args: cobra.MinimumNArgs(2),
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
jsonOutput, _ := cmd.Flags().GetBool("json")
issueIDs, label := parseLabelArgs(args) issueIDs, label := parseLabelArgs(args)
// Resolve partial IDs // Resolve partial IDs
@@ -107,7 +108,7 @@ var labelAddCmd = &cobra.Command{
} }
issueIDs = resolvedIDs issueIDs = resolvedIDs
processBatchLabelOperation(issueIDs, label, "added", processBatchLabelOperation(issueIDs, label, "added", jsonOutput,
func(issueID, lbl string) error { func(issueID, lbl string) error {
_, err := daemonClient.AddLabel(&rpc.LabelAddArgs{ID: issueID, Label: lbl}) _, err := daemonClient.AddLabel(&rpc.LabelAddArgs{ID: issueID, Label: lbl})
return err return err
@@ -124,6 +125,7 @@ var labelRemoveCmd = &cobra.Command{
Short: "Remove a label from one or more issues", Short: "Remove a label from one or more issues",
Args: cobra.MinimumNArgs(2), Args: cobra.MinimumNArgs(2),
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
jsonOutput, _ := cmd.Flags().GetBool("json")
issueIDs, label := parseLabelArgs(args) issueIDs, label := parseLabelArgs(args)
// Resolve partial IDs // Resolve partial IDs
@@ -152,7 +154,7 @@ var labelRemoveCmd = &cobra.Command{
} }
issueIDs = resolvedIDs issueIDs = resolvedIDs
processBatchLabelOperation(issueIDs, label, "removed", processBatchLabelOperation(issueIDs, label, "removed", jsonOutput,
func(issueID, lbl string) error { func(issueID, lbl string) error {
_, err := daemonClient.RemoveLabel(&rpc.LabelRemoveArgs{ID: issueID, Label: lbl}) _, err := daemonClient.RemoveLabel(&rpc.LabelRemoveArgs{ID: issueID, Label: lbl})
return err return err
@@ -168,6 +170,7 @@ var labelListCmd = &cobra.Command{
Short: "List labels for an issue", Short: "List labels for an issue",
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
jsonOutput, _ := cmd.Flags().GetBool("json")
ctx := context.Background() ctx := context.Background()
// Resolve partial ID first // Resolve partial ID first
@@ -242,6 +245,7 @@ var labelListAllCmd = &cobra.Command{
Use: "list-all", Use: "list-all",
Short: "List all unique labels in the database", Short: "List all unique labels in the database",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
jsonOutput, _ := cmd.Flags().GetBool("json")
ctx := context.Background() ctx := context.Background()
var issues []*types.Issue var issues []*types.Issue
@@ -342,6 +346,11 @@ var labelListAllCmd = &cobra.Command{
} }
func init() { func init() {
labelAddCmd.Flags().Bool("json", false, "Output JSON format")
labelRemoveCmd.Flags().Bool("json", false, "Output JSON format")
labelListCmd.Flags().Bool("json", false, "Output JSON format")
labelListAllCmd.Flags().Bool("json", false, "Output JSON format")
labelCmd.AddCommand(labelAddCmd) labelCmd.AddCommand(labelAddCmd)
labelCmd.AddCommand(labelRemoveCmd) labelCmd.AddCommand(labelRemoveCmd)
labelCmd.AddCommand(labelListCmd) labelCmd.AddCommand(labelListCmd)

View File

@@ -43,6 +43,7 @@ Example:
sourceIDs := args sourceIDs := args
dryRun, _ := cmd.Flags().GetBool("dry-run") dryRun, _ := cmd.Flags().GetBool("dry-run")
jsonOutput, _ := cmd.Flags().GetBool("json")
// Validate merge operation // Validate merge operation
if err := validateMerge(targetID, sourceIDs); err != nil { if err := validateMerge(targetID, sourceIDs); err != nil {
@@ -96,6 +97,7 @@ Example:
func init() { func init() {
mergeCmd.Flags().String("into", "", "Target issue ID to merge into (required)") mergeCmd.Flags().String("into", "", "Target issue ID to merge into (required)")
mergeCmd.Flags().Bool("dry-run", false, "Validate without making changes") mergeCmd.Flags().Bool("dry-run", false, "Validate without making changes")
mergeCmd.Flags().Bool("json", false, "Output JSON format")
rootCmd.AddCommand(mergeCmd) rootCmd.AddCommand(mergeCmd)
} }

View File

@@ -206,6 +206,8 @@ var statsCmd = &cobra.Command{
Use: "stats", Use: "stats",
Short: "Show statistics", Short: "Show statistics",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
jsonOutput, _ := cmd.Flags().GetBool("json")
// If daemon is running, use RPC // If daemon is running, use RPC
if daemonClient != nil { if daemonClient != nil {
resp, err := daemonClient.Stats() resp, err := daemonClient.Stats()
@@ -296,6 +298,8 @@ func init() {
readyCmd.Flags().StringP("sort", "s", "hybrid", "Sort policy: hybrid (default), priority, oldest") readyCmd.Flags().StringP("sort", "s", "hybrid", "Sort policy: hybrid (default), priority, oldest")
readyCmd.Flags().Bool("json", false, "Output JSON format") readyCmd.Flags().Bool("json", false, "Output JSON format")
statsCmd.Flags().Bool("json", false, "Output JSON format")
rootCmd.AddCommand(readyCmd) rootCmd.AddCommand(readyCmd)
rootCmd.AddCommand(blockedCmd) rootCmd.AddCommand(blockedCmd)
rootCmd.AddCommand(statsCmd) rootCmd.AddCommand(statsCmd)

View File

@@ -22,6 +22,7 @@ This is more explicit than 'bd update --status open' and emits a Reopened event.
Args: cobra.MinimumNArgs(1), Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
reason, _ := cmd.Flags().GetString("reason") reason, _ := cmd.Flags().GetString("reason")
jsonOutput, _ := cmd.Flags().GetBool("json")
ctx := context.Background() ctx := context.Background()
@@ -146,5 +147,6 @@ This is more explicit than 'bd update --status open' and emits a Reopened event.
func init() { func init() {
reopenCmd.Flags().StringP("reason", "r", "", "Reason for reopening") reopenCmd.Flags().StringP("reason", "r", "", "Reason for reopening")
reopenCmd.Flags().Bool("json", false, "Output JSON format")
rootCmd.AddCommand(reopenCmd) rootCmd.AddCommand(reopenCmd)
} }

View File

@@ -20,6 +20,7 @@ var showCmd = &cobra.Command{
Short: "Show issue details", Short: "Show issue details",
Args: cobra.MinimumNArgs(1), Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
jsonOutput, _ := cmd.Flags().GetBool("json")
ctx := context.Background() ctx := context.Background()
// Resolve partial IDs first // Resolve partial IDs first
@@ -329,6 +330,7 @@ var updateCmd = &cobra.Command{
Short: "Update one or more issues", Short: "Update one or more issues",
Args: cobra.MinimumNArgs(1), Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
jsonOutput, _ := cmd.Flags().GetBool("json")
updates := make(map[string]interface{}) updates := make(map[string]interface{})
if cmd.Flags().Changed("status") { if cmd.Flags().Changed("status") {
@@ -691,6 +693,7 @@ var closeCmd = &cobra.Command{
if reason == "" { if reason == "" {
reason = "Closed" reason = "Closed"
} }
jsonOutput, _ := cmd.Flags().GetBool("json")
ctx := context.Background() ctx := context.Background()
@@ -776,6 +779,7 @@ var closeCmd = &cobra.Command{
} }
func init() { func init() {
showCmd.Flags().Bool("json", false, "Output JSON format")
rootCmd.AddCommand(showCmd) rootCmd.AddCommand(showCmd)
updateCmd.Flags().StringP("status", "s", "", "New status") updateCmd.Flags().StringP("status", "s", "", "New status")
@@ -789,6 +793,7 @@ func init() {
updateCmd.Flags().String("acceptance-criteria", "", "DEPRECATED: use --acceptance") updateCmd.Flags().String("acceptance-criteria", "", "DEPRECATED: use --acceptance")
_ = updateCmd.Flags().MarkHidden("acceptance-criteria") _ = updateCmd.Flags().MarkHidden("acceptance-criteria")
updateCmd.Flags().String("external-ref", "", "External reference (e.g., 'gh-9', 'jira-ABC')") updateCmd.Flags().String("external-ref", "", "External reference (e.g., 'gh-9', 'jira-ABC')")
updateCmd.Flags().Bool("json", false, "Output JSON format")
rootCmd.AddCommand(updateCmd) rootCmd.AddCommand(updateCmd)
editCmd.Flags().Bool("title", false, "Edit the title") editCmd.Flags().Bool("title", false, "Edit the title")
@@ -799,5 +804,6 @@ func init() {
rootCmd.AddCommand(editCmd) rootCmd.AddCommand(editCmd)
closeCmd.Flags().StringP("reason", "r", "", "Reason for closing") closeCmd.Flags().StringP("reason", "r", "", "Reason for closing")
closeCmd.Flags().Bool("json", false, "Output JSON format")
rootCmd.AddCommand(closeCmd) rootCmd.AddCommand(closeCmd)
} }