Files
gastown/internal/doctor/rig_beads_check.go
Ryan Snodgrass e1f2bb8b4b feat(ui): import comprehensive UX system from beads
Import beads' UX design system into gastown:

- Add internal/ui/ package with Ayu theme colors and semantic styling
  - styles.go: AdaptiveColor definitions for light/dark mode
  - terminal.go: TTY detection, NO_COLOR/CLICOLOR support
  - markdown.go: Glamour rendering with agent mode bypass
  - pager.go: Smart paging with GT_PAGER support

- Add colorized help output (internal/cmd/help.go)
  - Group headers in accent color
  - Command names styled for scannability
  - Flag types and defaults muted

- Add gt thanks command (internal/cmd/thanks.go)
  - Contributor display with same logic as bd thanks
  - Styled with Ayu theme colors

- Update gt doctor to match bd doctor UX
  - Category grouping (Core, Infrastructure, Rig, Patrol, etc.)
  - Semantic icons (✓ ⚠ ✖) with Ayu colors
  - Tree connectors for detail lines
  - Summary line with pass/warn/fail counts
  - Warnings section at end with numbered issues

- Migrate existing styles to use ui package
  - internal/style/style.go uses ui.ColorPass etc.
  - internal/tui/feed/styles.go uses ui package colors

Co-Authored-By: SageOx <ox@sageox.ai>
2026-01-09 22:46:06 -08:00

173 lines
4.2 KiB
Go

package doctor
import (
"fmt"
"path/filepath"
"strings"
"github.com/steveyegge/gastown/internal/beads"
"github.com/steveyegge/gastown/internal/rig"
)
// RigBeadsCheck verifies that rig identity beads exist for all rigs.
// Rig identity beads track rig metadata like git URL, prefix, and operational state.
// They are created by gt rig add (see gt-zmznh) but may be missing for legacy rigs.
type RigBeadsCheck struct {
FixableCheck
}
// NewRigBeadsCheck creates a new rig identity beads check.
func NewRigBeadsCheck() *RigBeadsCheck {
return &RigBeadsCheck{
FixableCheck: FixableCheck{
BaseCheck: BaseCheck{
CheckName: "rig-beads-exist",
CheckDescription: "Verify rig identity beads exist for all rigs",
CheckCategory: CategoryRig,
},
},
}
}
// Run checks if rig identity beads exist for all rigs.
func (c *RigBeadsCheck) Run(ctx *CheckContext) *CheckResult {
// Load routes to get rig info
townBeadsDir := filepath.Join(ctx.TownRoot, ".beads")
routes, err := beads.LoadRoutes(townBeadsDir)
if err != nil {
return &CheckResult{
Name: c.Name(),
Status: StatusWarning,
Message: "Could not load routes.jsonl",
}
}
// Build unique rig list from routes
// Routes have format: prefix "gt-" -> path "gastown/mayor/rig"
rigSet := make(map[string]struct {
prefix string
beadsPath string
})
for _, r := range routes {
// Extract rig name from path (first component)
parts := strings.Split(r.Path, "/")
if len(parts) >= 1 && parts[0] != "." {
rigName := parts[0]
prefix := strings.TrimSuffix(r.Prefix, "-")
if _, exists := rigSet[rigName]; !exists {
rigSet[rigName] = struct {
prefix string
beadsPath string
}{
prefix: prefix,
beadsPath: r.Path,
}
}
}
}
if len(rigSet) == 0 {
return &CheckResult{
Name: c.Name(),
Status: StatusOK,
Message: "No rigs to check",
}
}
var missing []string
var checked int
// Check each rig for its identity bead
for rigName, info := range rigSet {
rigBeadsPath := filepath.Join(ctx.TownRoot, info.beadsPath)
bd := beads.New(rigBeadsPath)
rigBeadID := beads.RigBeadIDWithPrefix(info.prefix, rigName)
if _, err := bd.Show(rigBeadID); err != nil {
missing = append(missing, rigBeadID)
}
checked++
}
if len(missing) == 0 {
return &CheckResult{
Name: c.Name(),
Status: StatusOK,
Message: fmt.Sprintf("All %d rig identity beads exist", checked),
}
}
return &CheckResult{
Name: c.Name(),
Status: StatusError,
Message: fmt.Sprintf("%d rig identity bead(s) missing", len(missing)),
Details: missing,
FixHint: "Run 'gt doctor --fix' to create missing rig identity beads",
}
}
// Fix creates missing rig identity beads.
func (c *RigBeadsCheck) Fix(ctx *CheckContext) error {
// Load routes to get rig info
townBeadsDir := filepath.Join(ctx.TownRoot, ".beads")
routes, err := beads.LoadRoutes(townBeadsDir)
if err != nil {
return fmt.Errorf("loading routes.jsonl: %w", err)
}
// Build unique rig list from routes
rigSet := make(map[string]struct {
prefix string
beadsPath string
})
for _, r := range routes {
parts := strings.Split(r.Path, "/")
if len(parts) >= 1 && parts[0] != "." {
rigName := parts[0]
prefix := strings.TrimSuffix(r.Prefix, "-")
if _, exists := rigSet[rigName]; !exists {
rigSet[rigName] = struct {
prefix string
beadsPath string
}{
prefix: prefix,
beadsPath: r.Path,
}
}
}
}
if len(rigSet) == 0 {
return nil // No rigs to process
}
// Create missing rig identity beads
for rigName, info := range rigSet {
rigBeadsPath := filepath.Join(ctx.TownRoot, info.beadsPath)
bd := beads.New(rigBeadsPath)
rigBeadID := beads.RigBeadIDWithPrefix(info.prefix, rigName)
if _, err := bd.Show(rigBeadID); err != nil {
// Bead doesn't exist - create it
// Try to get git URL from rig config
rigPath := filepath.Join(ctx.TownRoot, rigName)
gitURL := ""
if cfg, err := rig.LoadRigConfig(rigPath); err == nil {
gitURL = cfg.GitURL
}
fields := &beads.RigFields{
Repo: gitURL,
Prefix: info.prefix,
State: "active",
}
if _, err := bd.CreateRigBead(rigBeadID, rigName, fields); err != nil {
return fmt.Errorf("creating %s: %w", rigBeadID, err)
}
}
}
return nil
}