feat: add overseer experience commands (gt focus, gt attention)
Some checks failed
CI / Check for .beads changes (push) Has been skipped
CI / Check embedded formulas (push) Failing after 15s
CI / Test (push) Failing after 1m29s
CI / Lint (push) Failing after 19s
CI / Integration Tests (push) Successful in 1m14s
CI / Coverage Report (push) Has been skipped
Windows CI / Windows Build and Unit Tests (push) Has been cancelled

Implements the Overseer Experience epic (gt-k0kn):

- gt focus: Shows stalest high-priority goals, sorted by priority × staleness
- gt attention: Shows blocked items, PRs awaiting review, stuck workers
- gt status: Now includes GOALS and ATTENTION summary sections
- gt convoy list: Added --orphans, --epic, --by-epic flags

These commands reduce Mayor bottleneck by giving the overseer direct
visibility into system state without needing to ask Mayor.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kerosene
2026-01-22 18:27:41 -08:00
committed by John Ogle
parent 41760bf464
commit f65c4ecfc8
4 changed files with 986 additions and 9 deletions

View File

@@ -6,6 +6,7 @@ import (
"os"
"os/signal"
"path/filepath"
"sort"
"strings"
"sync"
"syscall"
@@ -439,6 +440,55 @@ func outputStatusText(status TownStatus) error {
fmt.Println()
}
// Goals summary (top 3 stalest high-priority)
goals, _ := collectFocusItems(status.Location)
// Sort by score (highest first)
sort.Slice(goals, func(i, j int) bool {
return goals[i].Score > goals[j].Score
})
if len(goals) > 0 {
fmt.Printf("%s (%d active)\n", style.Bold.Render("GOALS"), len(goals))
// Show top 3
showCount := 3
if len(goals) < showCount {
showCount = len(goals)
}
for i := 0; i < showCount; i++ {
g := goals[i]
var indicator string
switch g.Staleness {
case "stuck":
indicator = style.Error.Render("🔴")
case "stale":
indicator = style.Warning.Render("🟡")
default:
indicator = style.Success.Render("🟢")
}
fmt.Printf(" %s P%d %s: %s\n", indicator, g.Priority, g.ID, truncateWithEllipsis(g.Title, 40))
}
if len(goals) > showCount {
fmt.Printf(" %s\n", style.Dim.Render(fmt.Sprintf("... and %d more (gt focus)", len(goals)-showCount)))
}
fmt.Println()
}
// Attention summary (blocked items, reviews)
attention := collectAttentionSummary(status.Location)
if attention.Total > 0 {
fmt.Printf("%s (%d items)\n", style.Bold.Render("ATTENTION"), attention.Total)
if attention.Blocked > 0 {
fmt.Printf(" • %d blocked issue(s)\n", attention.Blocked)
}
if attention.Reviews > 0 {
fmt.Printf(" • %d PR(s) awaiting review\n", attention.Reviews)
}
if attention.Stuck > 0 {
fmt.Printf(" • %d stuck worker(s)\n", attention.Stuck)
}
fmt.Printf(" %s\n", style.Dim.Render("→ gt attention for details"))
fmt.Println()
}
// Role icons - uses centralized emojis from constants package
roleIcons := map[string]string{
constants.RoleMayor: constants.EmojiMayor,
@@ -1232,3 +1282,36 @@ func getAgentHook(b *beads.Beads, role, agentAddress, roleType string) AgentHook
return hook
}
// AttentionSummary holds counts of items needing attention for status display.
type AttentionSummary struct {
Blocked int
Reviews int
Stuck int
Decisions int
Total int
}
// collectAttentionSummary gathers counts of items needing attention.
func collectAttentionSummary(townRoot string) AttentionSummary {
summary := AttentionSummary{}
// Count blocked items (reuse logic from attention.go)
blocked := collectBlockedItems(townRoot)
summary.Blocked = len(blocked)
// Count reviews
reviews := collectReviewItems(townRoot)
summary.Reviews = len(reviews)
// Count stuck workers
stuck := collectStuckWorkers(townRoot)
summary.Stuck = len(stuck)
// Count decisions
decisions := collectDecisionItems(townRoot)
summary.Decisions = len(decisions)
summary.Total = summary.Blocked + summary.Reviews + summary.Stuck + summary.Decisions
return summary
}