4 files changed,
+115,
-4
+2,
-1
1@@ -80,7 +80,8 @@ bug read --repo /path abc1234 # Display bug from specific repository
2 ```
3
4 Output includes:
5-- Title, Author, Creation date, Status, Labels
6+- Title, ID (7-char prefix), Author, Creation date, Status, Labels
7+- Parent (`Parent: <7-char> - <title>`, only if the issue has a parent)
8 - Description (first comment)
9 - All additional comments with their IDs
10
M
SKILL.md
+2,
-1
1@@ -95,7 +95,8 @@ bug agent read abc1234
2 ```
3
4 **Output includes:**
5-- Title, Author, Creation date, Status, Labels
6+- Title, ID (7-char prefix), Author, Creation date, Status, Labels
7+- Parent (`Parent: <7-char> - <title>`, only if the issue has a parent)
8 - Description (first comment)
9 - All additional comments with their IDs
10
M
main.go
+25,
-2
1@@ -1323,10 +1323,33 @@ func runRead(repoPath, bugIDStr string) error {
2
3 // Print metadata header
4 fmt.Printf("Title: %s\n", snap.Title)
5+ bugID := b.Id().String()
6+ if len(bugID) > 7 {
7+ bugID = bugID[:7]
8+ }
9+ fmt.Printf("ID: %s\n", bugID)
10 fmt.Printf("Author: %s\n", authorStr)
11 fmt.Printf("Created: %s\n", createdAt.Format(time.RFC3339))
12 fmt.Printf("Status: %s\n", snap.Status.String())
13 fmt.Printf("Labels: %s\n", labelsStr)
14+ // Parent last, only if present and resolvable
15+ if parentFullID := currentParentID(snap); parentFullID != "" {
16+ parentShort := parentFullID
17+ if len(parentShort) > 7 {
18+ parentShort = parentShort[:7]
19+ }
20+ parentTitle := ""
21+ if repo, err := repository.OpenGoGitRepo(repoPath, "", nil); err == nil {
22+ if parentBug, err := bug.Read(repo, entity.Id(parentFullID)); err == nil {
23+ parentTitle = parentBug.Compile().Title
24+ }
25+ repo.Close()
26+ }
27+ if parentTitle != "" {
28+ fmt.Printf("Parent: %s - %s\n", parentShort, parentTitle)
29+ }
30+ // Omit Parent line if parent cannot be resolved (e.g. deleted/orphan)
31+ }
32 fmt.Println()
33
34 // Print description (first comment)
35@@ -1859,7 +1882,7 @@ var readCmd = &cobra.Command{
36 Short: "Display a bug/issue with all comments",
37 Long: `Display a bug/issue and all its comments in a structured format.
38
39-The output includes metadata (title, author, creation date, status, labels)
40+The output includes metadata (title, ID, author, creation date, status, labels, parent if set)
41 followed by the description and all comments.
42
43 Examples:
44@@ -2033,7 +2056,7 @@ var agentReadCmd = &cobra.Command{
45 Long: `Display a bug/issue and all its comments in a structured format.
46
47 This is the same as 'bug read' but accessible under the agent subcommand.
48-The output includes metadata (title, author, creation date, status, labels)
49+The output includes metadata (title, ID, author, creation date, status, labels, parent if set)
50 followed by the description and all comments.
51
52 Examples:
+86,
-0
1@@ -3,6 +3,8 @@
2 package main
3
4 import (
5+ "io"
6+ "os"
7 "os/exec"
8 "strings"
9 "testing"
10@@ -64,3 +66,87 @@ func TestReadCommand_Integration(t *testing.T) {
11 t.Fatalf("runRead failed: %v", err)
12 }
13 }
14+
15+func captureRunRead(t *testing.T, repoPath, bugID string) string {
16+ t.Helper()
17+ old := os.Stdout
18+ r, w, _ := os.Pipe()
19+ os.Stdout = w
20+ defer func() { os.Stdout = old }()
21+ if err := runRead(repoPath, bugID); err != nil {
22+ t.Fatalf("runRead: %v", err)
23+ }
24+ w.Close()
25+ data, _ := io.ReadAll(r)
26+ return string(data)
27+}
28+
29+func TestReadCommand_ShowsIDLine(t *testing.T) {
30+ tmpDir := t.TempDir()
31+ initCmd := exec.Command("git", "init")
32+ initCmd.Dir = tmpDir
33+ if err := initCmd.Run(); err != nil {
34+ t.Fatalf("init: %v", err)
35+ }
36+ remoteCmd := exec.Command("git", "remote", "add", "origin", "[email protected]:test/repo.git")
37+ remoteCmd.Dir = tmpDir
38+ if err := remoteCmd.Run(); err != nil {
39+ t.Fatalf("remote: %v", err)
40+ }
41+ if err := runInitWithReader(tmpDir, strings.NewReader("Test User\[email protected]\n")); err != nil {
42+ t.Fatalf("init bug: %v", err)
43+ }
44+ if err := runNew(tmpDir, "ID Test", "desc", ""); err != nil {
45+ t.Fatalf("new: %v", err)
46+ }
47+ issues, _ := LoadBugs(tmpDir)
48+ bugID := issues[0].ShortID
49+ fullID := issues[0].FullID
50+ wantID := fullID
51+ if len(wantID) > 7 {
52+ wantID = wantID[:7]
53+ }
54+ out := captureRunRead(t, tmpDir, bugID)
55+ if !strings.Contains(out, "ID: "+wantID) {
56+ t.Errorf("read output missing %q\ngot:\n%s", "ID: "+wantID, out)
57+ }
58+ titleIdx := strings.Index(out, "Title:")
59+ idIdx := strings.Index(out, "ID: ")
60+ authorIdx := strings.Index(out, "Author:")
61+ if !(titleIdx >= 0 && idIdx > titleIdx && authorIdx > idIdx) {
62+ t.Errorf("want Title -> ID -> Author order\ngot:\n%s", out)
63+ }
64+}
65+
66+func TestReadCommand_ShowsParentLine(t *testing.T) {
67+ tmpDir := setupParentTestRepo(t)
68+ if err := runNew(tmpDir, "Parent Bug", "p desc", ""); err != nil {
69+ t.Fatalf("new parent: %v", err)
70+ }
71+ parent := findIssueByTitle(t, tmpDir, "Parent Bug")
72+ if err := runNew(tmpDir, "Child Bug", "c desc", parent.ShortID); err != nil {
73+ t.Fatalf("new child: %v", err)
74+ }
75+ child := findIssueByTitle(t, tmpDir, "Child Bug")
76+ out := captureRunRead(t, tmpDir, child.ShortID)
77+ wantParentShort := parent.FullID
78+ if len(wantParentShort) > 7 {
79+ wantParentShort = wantParentShort[:7]
80+ }
81+ want := "Parent: " + wantParentShort + " - Parent Bug"
82+ if !strings.Contains(out, want) {
83+ t.Errorf("want %q\ngot:\n%s", want, out)
84+ }
85+}
86+
87+func TestReadCommand_OmitsParentWhenNone(t *testing.T) {
88+ tmpDir := setupParentTestRepo(t)
89+ if err := runNew(tmpDir, "Lonely", "desc", ""); err != nil {
90+ t.Fatalf("new: %v", err)
91+ }
92+ lonely := findIssueByTitle(t, tmpDir, "Lonely")
93+ out := captureRunRead(t, tmpDir, lonely.ShortID)
94+ if strings.Contains(out, "Parent:") {
95+ t.Errorf("want no Parent line\ngot:\n%s", out)
96+ }
97+}