Files
gastown/internal/doctor/rig_beads_check.go
max 8d5611f14e feat(doctor): add rig identity beads check
Adds RigBeadsCheck to gt doctor to verify rig identity beads exist.
These beads track rig metadata (git URL, prefix, state) and are created
by gt rig add. The check scans routes.jsonl and verifies each rig
has an identity bead, with --fix to create missing ones.

Recovered from furiosa's uncommitted work after worker interruption.

Co-Authored-By: furiosa <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 12:59:49 -08:00

172 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",
},
},
}
}
// 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
}