Merge branch 'main' of github.com:steveyegge/beads
# Conflicts: # .beads/beads.jsonl
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -273,6 +274,7 @@ var depTreeCmd = &cobra.Command{
|
||||
showAllPaths, _ := cmd.Flags().GetBool("show-all-paths")
|
||||
maxDepth, _ := cmd.Flags().GetInt("max-depth")
|
||||
reverse, _ := cmd.Flags().GetBool("reverse")
|
||||
formatStr, _ := cmd.Flags().GetString("format")
|
||||
|
||||
if maxDepth < 1 {
|
||||
fmt.Fprintf(os.Stderr, "Error: --max-depth must be >= 1\n")
|
||||
@@ -285,6 +287,12 @@ var depTreeCmd = &cobra.Command{
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Handle mermaid format
|
||||
if formatStr == "mermaid" {
|
||||
outputMermaidTree(tree, args[0])
|
||||
return
|
||||
}
|
||||
|
||||
if jsonOutput {
|
||||
// Always output array, even if empty
|
||||
if tree == nil {
|
||||
@@ -383,11 +391,71 @@ var depCyclesCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
// outputMermaidTree outputs a dependency tree in Mermaid.js flowchart format
|
||||
func outputMermaidTree(tree []*types.TreeNode, rootID string) {
|
||||
if len(tree) == 0 {
|
||||
fmt.Println("flowchart TD")
|
||||
fmt.Printf(" %s[\"No dependencies\"]\n", rootID)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("flowchart TD")
|
||||
|
||||
// Output nodes
|
||||
nodesSeen := make(map[string]bool)
|
||||
for _, node := range tree {
|
||||
if !nodesSeen[node.ID] {
|
||||
emoji := getStatusEmoji(node.Status)
|
||||
label := fmt.Sprintf("%s %s: %s", emoji, node.ID, node.Title)
|
||||
// Escape quotes and backslashes in label
|
||||
label = strings.ReplaceAll(label, "\\", "\\\\")
|
||||
label = strings.ReplaceAll(label, "\"", "\\\"")
|
||||
fmt.Printf(" %s[\"%s\"]\n", node.ID, label)
|
||||
|
||||
nodesSeen[node.ID] = true
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
|
||||
// Output edges - use explicit parent relationships from ParentID
|
||||
for _, node := range tree {
|
||||
if node.ParentID != "" && node.ParentID != node.ID {
|
||||
fmt.Printf(" %s --> %s\n", node.ParentID, node.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getStatusEmoji returns a symbol indicator for a given status
|
||||
func getStatusEmoji(status types.Status) string {
|
||||
switch status {
|
||||
case types.StatusOpen:
|
||||
return "☐" // U+2610 Ballot Box
|
||||
case types.StatusInProgress:
|
||||
return "◧" // U+25E7 Square Left Half Black
|
||||
case types.StatusBlocked:
|
||||
return "⚠" // U+26A0 Warning Sign
|
||||
case types.StatusClosed:
|
||||
return "☑" // U+2611 Ballot Box with Check
|
||||
default:
|
||||
return "?"
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
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().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().String("format", "", "Output format: 'mermaid' for Mermaid.js flowchart")
|
||||
depTreeCmd.Flags().Bool("json", false, "Output JSON format")
|
||||
|
||||
depCyclesCmd.Flags().Bool("json", false, "Output JSON format")
|
||||
|
||||
depCmd.AddCommand(depAddCmd)
|
||||
depCmd.AddCommand(depRemoveCmd)
|
||||
depCmd.AddCommand(depTreeCmd)
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -266,3 +270,217 @@ func TestDepRemove(t *testing.T) {
|
||||
t.Errorf("Expected 0 dependencies after removal, got %d", len(deps))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDepTreeFormatFlag(t *testing.T) {
|
||||
// Test that the --format flag exists on depTreeCmd
|
||||
flag := depTreeCmd.Flags().Lookup("format")
|
||||
if flag == nil {
|
||||
t.Fatal("depTreeCmd should have --format flag")
|
||||
}
|
||||
|
||||
// Test default value is empty string
|
||||
if flag.DefValue != "" {
|
||||
t.Errorf("Expected default format='', got %q", flag.DefValue)
|
||||
}
|
||||
|
||||
// Test usage text mentions mermaid
|
||||
if !strings.Contains(flag.Usage, "mermaid") {
|
||||
t.Errorf("Expected flag usage to mention 'mermaid', got %q", flag.Usage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetStatusEmoji(t *testing.T) {
|
||||
tests := []struct {
|
||||
status types.Status
|
||||
want string
|
||||
}{
|
||||
{types.StatusOpen, "☐"},
|
||||
{types.StatusInProgress, "◧"},
|
||||
{types.StatusBlocked, "⚠"},
|
||||
{types.StatusClosed, "☑"},
|
||||
{types.Status("unknown"), "?"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(string(tt.status), func(t *testing.T) {
|
||||
got := getStatusEmoji(tt.status)
|
||||
if got != tt.want {
|
||||
t.Errorf("getStatusEmoji(%q) = %q, want %q", tt.status, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutputMermaidTree(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tree []*types.TreeNode
|
||||
rootID string
|
||||
want []string // Lines that must appear in output
|
||||
}{
|
||||
{
|
||||
name: "empty tree",
|
||||
tree: []*types.TreeNode{},
|
||||
rootID: "test-1",
|
||||
want: []string{
|
||||
"flowchart TD",
|
||||
`test-1["No dependencies"]`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "single dependency",
|
||||
tree: []*types.TreeNode{
|
||||
{
|
||||
Issue: types.Issue{ID: "test-1", Title: "Task 1", Status: types.StatusInProgress},
|
||||
Depth: 0,
|
||||
ParentID: "",
|
||||
},
|
||||
{
|
||||
Issue: types.Issue{ID: "test-2", Title: "Task 2", Status: types.StatusClosed},
|
||||
Depth: 1,
|
||||
ParentID: "test-1",
|
||||
},
|
||||
},
|
||||
rootID: "test-1",
|
||||
want: []string{
|
||||
"flowchart TD",
|
||||
`test-1["◧ test-1: Task 1"]`,
|
||||
`test-2["☑ test-2: Task 2"]`,
|
||||
"test-1 --> test-2",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple dependencies",
|
||||
tree: []*types.TreeNode{
|
||||
{
|
||||
Issue: types.Issue{ID: "test-1", Title: "Main", Status: types.StatusOpen},
|
||||
Depth: 0,
|
||||
ParentID: "",
|
||||
},
|
||||
{
|
||||
Issue: types.Issue{ID: "test-2", Title: "Sub 1", Status: types.StatusClosed},
|
||||
Depth: 1,
|
||||
ParentID: "test-1",
|
||||
},
|
||||
{
|
||||
Issue: types.Issue{ID: "test-3", Title: "Sub 2", Status: types.StatusBlocked},
|
||||
Depth: 1,
|
||||
ParentID: "test-1",
|
||||
},
|
||||
},
|
||||
rootID: "test-1",
|
||||
want: []string{
|
||||
"flowchart TD",
|
||||
`test-1["☐ test-1: Main"]`,
|
||||
`test-2["☑ test-2: Sub 1"]`,
|
||||
`test-3["⚠ test-3: Sub 2"]`,
|
||||
"test-1 --> test-2",
|
||||
"test-1 --> test-3",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Capture stdout
|
||||
old := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
outputMermaidTree(tt.tree, tt.rootID)
|
||||
|
||||
w.Close()
|
||||
os.Stdout = old
|
||||
|
||||
var buf bytes.Buffer
|
||||
io.Copy(&buf, r)
|
||||
output := buf.String()
|
||||
|
||||
// Verify all expected lines appear
|
||||
for _, line := range tt.want {
|
||||
if !strings.Contains(output, line) {
|
||||
t.Errorf("expected output to contain %q, got:\n%s", line, output)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutputMermaidTree_Siblings(t *testing.T) {
|
||||
// Test case: Siblings with children (reproduces issue with wrong parent inference)
|
||||
// Structure:
|
||||
// BD-1 (root)
|
||||
// ├── BD-2 (sibling 1)
|
||||
// │ └── BD-4 (child of BD-2)
|
||||
// └── BD-3 (sibling 2)
|
||||
// └── BD-5 (child of BD-3)
|
||||
tree := []*types.TreeNode{
|
||||
{
|
||||
Issue: types.Issue{ID: "BD-1", Title: "Parent", Status: types.StatusOpen},
|
||||
Depth: 0,
|
||||
ParentID: "",
|
||||
},
|
||||
{
|
||||
Issue: types.Issue{ID: "BD-2", Title: "Sibling 1", Status: types.StatusOpen},
|
||||
Depth: 1,
|
||||
ParentID: "BD-1",
|
||||
},
|
||||
{
|
||||
Issue: types.Issue{ID: "BD-3", Title: "Sibling 2", Status: types.StatusOpen},
|
||||
Depth: 1,
|
||||
ParentID: "BD-1",
|
||||
},
|
||||
{
|
||||
Issue: types.Issue{ID: "BD-4", Title: "Child of Sibling 1", Status: types.StatusOpen},
|
||||
Depth: 2,
|
||||
ParentID: "BD-2",
|
||||
},
|
||||
{
|
||||
Issue: types.Issue{ID: "BD-5", Title: "Child of Sibling 2", Status: types.StatusOpen},
|
||||
Depth: 2,
|
||||
ParentID: "BD-3",
|
||||
},
|
||||
}
|
||||
|
||||
// Capture stdout
|
||||
old := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
outputMermaidTree(tree, "BD-1")
|
||||
|
||||
w.Close()
|
||||
os.Stdout = old
|
||||
|
||||
var buf bytes.Buffer
|
||||
io.Copy(&buf, r)
|
||||
output := buf.String()
|
||||
|
||||
// Verify correct edges exist
|
||||
correctEdges := []string{
|
||||
"BD-1 --> BD-2",
|
||||
"BD-1 --> BD-3",
|
||||
"BD-2 --> BD-4",
|
||||
"BD-3 --> BD-5",
|
||||
}
|
||||
|
||||
for _, edge := range correctEdges {
|
||||
if !strings.Contains(output, edge) {
|
||||
t.Errorf("expected edge %q to be present, got:\n%s", edge, output)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify incorrect edges do NOT exist (siblings shouldn't be connected)
|
||||
incorrectEdges := []string{
|
||||
"BD-2 --> BD-3", // Siblings shouldn't be connected
|
||||
"BD-3 --> BD-4", // BD-4's parent is BD-2, not BD-3
|
||||
"BD-4 --> BD-3", // Wrong direction
|
||||
"BD-4 --> BD-5", // These are cousins, not parent-child
|
||||
}
|
||||
|
||||
for _, edge := range incorrectEdges {
|
||||
if strings.Contains(output, edge) {
|
||||
t.Errorf("incorrect edge %q should NOT be present, got:\n%s", edge, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ Example:
|
||||
|
||||
autoMerge, _ := cmd.Flags().GetBool("auto-merge")
|
||||
dryRun, _ := cmd.Flags().GetBool("dry-run")
|
||||
jsonOutput, _ := cmd.Flags().GetBool("json")
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -174,6 +175,7 @@ Example:
|
||||
func init() {
|
||||
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("json", false, "Output JSON format")
|
||||
rootCmd.AddCommand(duplicatesCmd)
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ var labelCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
// 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) {
|
||||
ctx := context.Background()
|
||||
results := []map[string]interface{}{}
|
||||
@@ -40,7 +40,7 @@ func processBatchLabelOperation(issueIDs []string, label string, operation strin
|
||||
continue
|
||||
}
|
||||
|
||||
if jsonOutput {
|
||||
if jsonOut {
|
||||
results = append(results, map[string]interface{}{
|
||||
"status": operation,
|
||||
"issue_id": issueID,
|
||||
@@ -62,7 +62,7 @@ func processBatchLabelOperation(issueIDs []string, label string, operation strin
|
||||
markDirtyAndScheduleFlush()
|
||||
}
|
||||
|
||||
if jsonOutput && len(results) > 0 {
|
||||
if jsonOut && len(results) > 0 {
|
||||
outputJSON(results)
|
||||
}
|
||||
}
|
||||
@@ -79,6 +79,7 @@ var labelAddCmd = &cobra.Command{
|
||||
Short: "Add a label to one or more issues",
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
jsonOutput, _ := cmd.Flags().GetBool("json")
|
||||
issueIDs, label := parseLabelArgs(args)
|
||||
|
||||
// Resolve partial IDs
|
||||
@@ -107,7 +108,7 @@ var labelAddCmd = &cobra.Command{
|
||||
}
|
||||
issueIDs = resolvedIDs
|
||||
|
||||
processBatchLabelOperation(issueIDs, label, "added",
|
||||
processBatchLabelOperation(issueIDs, label, "added", jsonOutput,
|
||||
func(issueID, lbl string) error {
|
||||
_, err := daemonClient.AddLabel(&rpc.LabelAddArgs{ID: issueID, Label: lbl})
|
||||
return err
|
||||
@@ -124,6 +125,7 @@ var labelRemoveCmd = &cobra.Command{
|
||||
Short: "Remove a label from one or more issues",
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
jsonOutput, _ := cmd.Flags().GetBool("json")
|
||||
issueIDs, label := parseLabelArgs(args)
|
||||
|
||||
// Resolve partial IDs
|
||||
@@ -152,7 +154,7 @@ var labelRemoveCmd = &cobra.Command{
|
||||
}
|
||||
issueIDs = resolvedIDs
|
||||
|
||||
processBatchLabelOperation(issueIDs, label, "removed",
|
||||
processBatchLabelOperation(issueIDs, label, "removed", jsonOutput,
|
||||
func(issueID, lbl string) error {
|
||||
_, err := daemonClient.RemoveLabel(&rpc.LabelRemoveArgs{ID: issueID, Label: lbl})
|
||||
return err
|
||||
@@ -168,6 +170,7 @@ var labelListCmd = &cobra.Command{
|
||||
Short: "List labels for an issue",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
jsonOutput, _ := cmd.Flags().GetBool("json")
|
||||
ctx := context.Background()
|
||||
|
||||
// Resolve partial ID first
|
||||
@@ -242,6 +245,7 @@ var labelListAllCmd = &cobra.Command{
|
||||
Use: "list-all",
|
||||
Short: "List all unique labels in the database",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
jsonOutput, _ := cmd.Flags().GetBool("json")
|
||||
ctx := context.Background()
|
||||
|
||||
var issues []*types.Issue
|
||||
@@ -342,6 +346,11 @@ var labelListAllCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
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(labelRemoveCmd)
|
||||
labelCmd.AddCommand(labelListCmd)
|
||||
|
||||
@@ -836,9 +836,9 @@ func TestAutoImportDisabled(t *testing.T) {
|
||||
storeMutex.Unlock()
|
||||
}
|
||||
|
||||
// TestAutoImportWithCollision tests that auto-import detects collisions and preserves local changes
|
||||
func TestAutoImportWithCollision(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "bd-test-collision-*")
|
||||
// TestAutoImportWithUpdate tests that auto-import detects same-ID updates and applies them
|
||||
func TestAutoImportWithUpdate(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "bd-test-update-*")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
@@ -877,7 +877,7 @@ func TestAutoImportWithCollision(t *testing.T) {
|
||||
t.Fatalf("Failed to create issue: %v", err)
|
||||
}
|
||||
|
||||
// Create JSONL with same ID but status=open (conflict)
|
||||
// Create JSONL with same ID but status=open (update scenario)
|
||||
jsonlIssue := &types.Issue{
|
||||
ID: "test-col-1",
|
||||
Title: "Remote version",
|
||||
@@ -911,9 +911,9 @@ func TestAutoImportWithCollision(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestAutoImportNoCollision tests happy path with no conflicts
|
||||
func TestAutoImportNoCollision(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "bd-test-nocoll-*")
|
||||
// TestAutoImportNoUpdate tests happy path with no updates needed
|
||||
func TestAutoImportNoUpdate(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "bd-test-noupdate-*")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ Example:
|
||||
|
||||
sourceIDs := args
|
||||
dryRun, _ := cmd.Flags().GetBool("dry-run")
|
||||
jsonOutput, _ := cmd.Flags().GetBool("json")
|
||||
|
||||
// Validate merge operation
|
||||
if err := validateMerge(targetID, sourceIDs); err != nil {
|
||||
@@ -96,6 +97,7 @@ Example:
|
||||
func init() {
|
||||
mergeCmd.Flags().String("into", "", "Target issue ID to merge into (required)")
|
||||
mergeCmd.Flags().Bool("dry-run", false, "Validate without making changes")
|
||||
mergeCmd.Flags().Bool("json", false, "Output JSON format")
|
||||
rootCmd.AddCommand(mergeCmd)
|
||||
}
|
||||
|
||||
|
||||
@@ -206,6 +206,8 @@ var statsCmd = &cobra.Command{
|
||||
Use: "stats",
|
||||
Short: "Show statistics",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
jsonOutput, _ := cmd.Flags().GetBool("json")
|
||||
|
||||
// If daemon is running, use RPC
|
||||
if daemonClient != nil {
|
||||
resp, err := daemonClient.Stats()
|
||||
@@ -296,6 +298,8 @@ func init() {
|
||||
readyCmd.Flags().StringP("sort", "s", "hybrid", "Sort policy: hybrid (default), priority, oldest")
|
||||
readyCmd.Flags().Bool("json", false, "Output JSON format")
|
||||
|
||||
statsCmd.Flags().Bool("json", false, "Output JSON format")
|
||||
|
||||
rootCmd.AddCommand(readyCmd)
|
||||
rootCmd.AddCommand(blockedCmd)
|
||||
rootCmd.AddCommand(statsCmd)
|
||||
|
||||
@@ -22,6 +22,7 @@ This is more explicit than 'bd update --status open' and emits a Reopened event.
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
reason, _ := cmd.Flags().GetString("reason")
|
||||
jsonOutput, _ := cmd.Flags().GetBool("json")
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -146,5 +147,6 @@ This is more explicit than 'bd update --status open' and emits a Reopened event.
|
||||
|
||||
func init() {
|
||||
reopenCmd.Flags().StringP("reason", "r", "", "Reason for reopening")
|
||||
reopenCmd.Flags().Bool("json", false, "Output JSON format")
|
||||
rootCmd.AddCommand(reopenCmd)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ var showCmd = &cobra.Command{
|
||||
Short: "Show issue details",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
jsonOutput, _ := cmd.Flags().GetBool("json")
|
||||
ctx := context.Background()
|
||||
|
||||
// Resolve partial IDs first
|
||||
@@ -329,6 +330,7 @@ var updateCmd = &cobra.Command{
|
||||
Short: "Update one or more issues",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
jsonOutput, _ := cmd.Flags().GetBool("json")
|
||||
updates := make(map[string]interface{})
|
||||
|
||||
if cmd.Flags().Changed("status") {
|
||||
@@ -691,6 +693,7 @@ var closeCmd = &cobra.Command{
|
||||
if reason == "" {
|
||||
reason = "Closed"
|
||||
}
|
||||
jsonOutput, _ := cmd.Flags().GetBool("json")
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -776,6 +779,7 @@ var closeCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func init() {
|
||||
showCmd.Flags().Bool("json", false, "Output JSON format")
|
||||
rootCmd.AddCommand(showCmd)
|
||||
|
||||
updateCmd.Flags().StringP("status", "s", "", "New status")
|
||||
@@ -789,6 +793,7 @@ func init() {
|
||||
updateCmd.Flags().String("acceptance-criteria", "", "DEPRECATED: use --acceptance")
|
||||
_ = updateCmd.Flags().MarkHidden("acceptance-criteria")
|
||||
updateCmd.Flags().String("external-ref", "", "External reference (e.g., 'gh-9', 'jira-ABC')")
|
||||
updateCmd.Flags().Bool("json", false, "Output JSON format")
|
||||
rootCmd.AddCommand(updateCmd)
|
||||
|
||||
editCmd.Flags().Bool("title", false, "Edit the title")
|
||||
@@ -799,5 +804,6 @@ func init() {
|
||||
rootCmd.AddCommand(editCmd)
|
||||
|
||||
closeCmd.Flags().StringP("reason", "r", "", "Reason for closing")
|
||||
closeCmd.Flags().Bool("json", false, "Output JSON format")
|
||||
rootCmd.AddCommand(closeCmd)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user