From fd1afc13407a0d1cafc1812f337932bd744fcff4 Mon Sep 17 00:00:00 2001 From: gastown/polecats/toast Date: Tue, 30 Dec 2025 22:30:17 -0800 Subject: [PATCH] Add merge queue observability to gt status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add State (idle/processing/blocked) and Health (healthy/stale/empty) fields to MQSummary - Display state indicator (● for processing, ○ for idle/blocked) - Show [stale] warning when queue has >10 pending items with no processing - Include new fields in JSON output for automation (gt-hpcyt) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- internal/cmd/status.go | 47 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/internal/cmd/status.go b/internal/cmd/status.go index eb017f32..6e403af4 100644 --- a/internal/cmd/status.go +++ b/internal/cmd/status.go @@ -88,9 +88,11 @@ type RigStatus struct { // MQSummary represents the merge queue status for a rig. type MQSummary struct { - Pending int `json:"pending"` // Open MRs ready to merge (no blockers) - InFlight int `json:"in_flight"` // MRs currently being processed - Blocked int `json:"blocked"` // MRs waiting on dependencies + Pending int `json:"pending"` // Open MRs ready to merge (no blockers) + InFlight int `json:"in_flight"` // MRs currently being processed + Blocked int `json:"blocked"` // MRs waiting on dependencies + State string `json:"state"` // idle, processing, or blocked + Health string `json:"health"` // healthy, stale, or empty } // AgentHookInfo represents an agent's hook (pinned work) status. @@ -336,7 +338,20 @@ func outputStatusText(status TownStatus) error { mqParts = append(mqParts, style.Dim.Render(fmt.Sprintf("%d blocked", r.MQ.Blocked))) } if len(mqParts) > 0 { - fmt.Printf(" MQ: %s\n", strings.Join(mqParts, ", ")) + // Add state indicator + stateIcon := "○" // idle + switch r.MQ.State { + case "processing": + stateIcon = style.Success.Render("●") + case "blocked": + stateIcon = style.Error.Render("○") + } + // Add health warning if stale + healthSuffix := "" + if r.MQ.Health == "stale" { + healthSuffix = style.Error.Render(" [stale]") + } + fmt.Printf(" MQ: %s %s%s\n", stateIcon, strings.Join(mqParts, ", "), healthSuffix) } } fmt.Println() @@ -741,6 +756,28 @@ func getMQSummary(r *rig.Rig) *MQSummary { } } + // Determine queue state + state := "idle" + if len(inProgressMRs) > 0 { + state = "processing" + } else if pending > 0 { + state = "idle" // Has work but not processing yet + } else if blocked > 0 { + state = "blocked" // Only blocked items, nothing processable + } + + // Determine queue health + health := "empty" + total := pending + len(inProgressMRs) + blocked + if total > 0 { + health = "healthy" + // Check for potential issues + if pending > 10 && len(inProgressMRs) == 0 { + // Large queue but nothing processing - may be stuck + health = "stale" + } + } + // Only return summary if there's something to show if pending == 0 && len(inProgressMRs) == 0 && blocked == 0 { return nil @@ -750,6 +787,8 @@ func getMQSummary(r *rig.Rig) *MQSummary { Pending: pending, InFlight: len(inProgressMRs), Blocked: blocked, + State: state, + Health: health, } }