From 3dda4f2b365f56022fe7dba69694089d564875d6 Mon Sep 17 00:00:00 2001 From: goose Date: Fri, 2 Jan 2026 00:19:17 -0800 Subject: [PATCH] feat(convoy): Show status symbols and assignee for tracked issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Show ✓ for closed, ▶ for in_progress/hooked, ○ for other statuses - Display assignee name in brackets instead of issue type - Falls back to issue type when no assignee, or "unassigned" when neither Closes gt-cbstf 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- internal/cmd/convoy.go | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/internal/cmd/convoy.go b/internal/cmd/convoy.go index 3bcb0794..fbf90057 100644 --- a/internal/cmd/convoy.go +++ b/internal/cmd/convoy.go @@ -431,15 +431,25 @@ func runConvoyStatus(cmd *cobra.Command, args []string) error { if len(tracked) > 0 { fmt.Printf("\n %s\n", style.Bold.Render("Tracked Issues:")) for _, t := range tracked { + // Status symbol: ✓ closed, ▶ in_progress/hooked, ○ other status := "○" - if t.Status == "closed" { + switch t.Status { + case "closed": status = "✓" + case "in_progress", "hooked": + status = "▶" } - issueType := t.IssueType - if issueType == "" { - issueType = "task" + + // Show assignee in brackets (extract short name from path like gastown/polecats/goose -> goose) + bracketContent := t.IssueType + if t.Assignee != "" { + parts := strings.Split(t.Assignee, "/") + bracketContent = parts[len(parts)-1] // Last part of path + } else if bracketContent == "" { + bracketContent = "unassigned" } - line := fmt.Sprintf(" %s %s: %s [%s]", status, t.ID, t.Title, issueType) + + line := fmt.Sprintf(" %s %s: %s [%s]", status, t.ID, t.Title, bracketContent) if t.Worker != "" { workerDisplay := "@" + t.Worker if t.WorkerAge != "" { @@ -572,7 +582,8 @@ type trackedIssueInfo struct { Status string `json:"status"` Type string `json:"dependency_type"` IssueType string `json:"issue_type"` - Worker string `json:"worker,omitempty"` // Worker currently assigned (e.g., gastown/nux) + Assignee string `json:"assignee,omitempty"` // Assigned agent (e.g., gastown/polecats/goose) + Worker string `json:"worker,omitempty"` // Worker currently assigned (e.g., gastown/nux) WorkerAge string `json:"worker_age,omitempty"` // How long worker has been on this issue } @@ -644,6 +655,7 @@ func getTrackedIssues(townBeads, convoyID string) []trackedIssueInfo { info.Title = details.Title info.Status = details.Status info.IssueType = details.IssueType + info.Assignee = details.Assignee } else { info.Title = "(external)" info.Status = "unknown" @@ -667,6 +679,7 @@ type issueDetails struct { Title string Status string IssueType string + Assignee string } // getIssueDetailsBatch fetches details for multiple issues in a single bd show call. @@ -701,6 +714,7 @@ func getIssueDetailsBatch(issueIDs []string) map[string]*issueDetails { Title string `json:"title"` Status string `json:"status"` IssueType string `json:"issue_type"` + Assignee string `json:"assignee"` } if err := json.Unmarshal(stdout.Bytes(), &issues); err != nil { return result @@ -712,6 +726,7 @@ func getIssueDetailsBatch(issueIDs []string) map[string]*issueDetails { Title: issue.Title, Status: issue.Status, IssueType: issue.IssueType, + Assignee: issue.Assignee, } } @@ -735,6 +750,7 @@ func getIssueDetails(issueID string) *issueDetails { Title string `json:"title"` Status string `json:"status"` IssueType string `json:"issue_type"` + Assignee string `json:"assignee"` } if err := json.Unmarshal(stdout.Bytes(), &issues); err != nil || len(issues) == 0 { return nil @@ -745,6 +761,7 @@ func getIssueDetails(issueID string) *issueDetails { Title: issues[0].Title, Status: issues[0].Status, IssueType: issues[0].IssueType, + Assignee: issues[0].Assignee, } }