show ID and parent in bug read output
4 files changed,  +115, -4
M README.md
+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:
M read_integration_test.go
+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+}