feat(doctor): add custom types check and centralize BeadsCustomTypes

- Add BeadsCustomTypes constant ("agent,role,rig,convoy,slot") to avoid
  hardcoded strings scattered across the codebase
- Add CustomTypesCheck to gt doctor that verifies Gas Town custom types
  are registered with beads, with --fix support
- Register custom types during gt init (best-effort, skips if no beads)
- Update install.go, rig_check.go, and rig/manager.go to use the constant

This ensures consistent type registration across all code paths and
catches misconfigured beads databases via gt doctor.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gastown/crew/george
2026-01-09 13:19:48 -08:00
committed by Steve Yegge
parent 86739556c2
commit 61b561a540
7 changed files with 179 additions and 8 deletions

View File

@@ -116,6 +116,7 @@ func runDoctor(cmd *cobra.Command, args []string) error {
d.Register(doctor.NewRepoFingerprintCheck())
d.Register(doctor.NewBootHealthCheck())
d.Register(doctor.NewBeadsDatabaseCheck())
d.Register(doctor.NewCustomTypesCheck())
d.Register(doctor.NewFormulaCheck())
d.Register(doctor.NewBdDaemonCheck())
d.Register(doctor.NewPrefixConflictCheck())

View File

@@ -3,10 +3,12 @@ package cmd
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/spf13/cobra"
"github.com/steveyegge/gastown/internal/constants"
"github.com/steveyegge/gastown/internal/git"
"github.com/steveyegge/gastown/internal/rig"
"github.com/steveyegge/gastown/internal/style"
@@ -80,6 +82,16 @@ func runInit(cmd *cobra.Command, args []string) error {
fmt.Printf(" ✓ Updated .git/info/exclude\n")
}
// Register custom beads types for Gas Town (agent, role, rig, convoy, slot).
// This is best-effort: if beads isn't installed or DB doesn't exist, we skip.
// The doctor check will catch missing types later.
if err := registerCustomTypes(cwd); err != nil {
fmt.Printf(" %s Could not register custom types: %v\n",
style.Dim.Render("⚠"), err)
} else {
fmt.Printf(" ✓ Registered custom beads types\n")
}
fmt.Printf("\n%s Rig initialized with %d directories.\n",
style.Bold.Render("✓"), created)
fmt.Println()
@@ -127,3 +139,34 @@ func updateGitExclude(repoPath string) error {
// Write back
return os.WriteFile(excludePath, append(content, []byte(additions)...), 0644)
}
// registerCustomTypes registers Gas Town custom issue types with beads.
// This is best-effort: returns nil if beads isn't available or DB doesn't exist.
// Handles gracefully: beads not installed, no .beads directory, or config errors.
func registerCustomTypes(workDir string) error {
// Check if bd command is available
if _, err := exec.LookPath("bd"); err != nil {
return nil // beads not installed, skip silently
}
// Check if .beads directory exists
beadsDir := filepath.Join(workDir, ".beads")
if _, err := os.Stat(beadsDir); os.IsNotExist(err) {
return nil // no beads DB yet, skip silently
}
// Try to set custom types
cmd := exec.Command("bd", "config", "set", "types.custom", constants.BeadsCustomTypes)
cmd.Dir = workDir
output, err := cmd.CombinedOutput()
if err != nil {
// Check for common expected errors
outStr := string(output)
if strings.Contains(outStr, "not initialized") ||
strings.Contains(outStr, "no such file") {
return nil // DB not initialized, skip silently
}
return fmt.Errorf("%s", strings.TrimSpace(outStr))
}
return nil
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/spf13/cobra"
"github.com/steveyegge/gastown/internal/beads"
"github.com/steveyegge/gastown/internal/constants"
"github.com/steveyegge/gastown/internal/claude"
"github.com/steveyegge/gastown/internal/config"
"github.com/steveyegge/gastown/internal/deps"
@@ -344,10 +345,9 @@ func initTownBeads(townPath string) error {
}
}
// Configure custom types for Gas Town (agent, role, rig, convoy).
// Configure custom types for Gas Town (agent, role, rig, convoy, slot).
// These were extracted from beads core in v0.46.0 and now require explicit config.
customTypes := "agent,role,rig,convoy,event"
configCmd := exec.Command("bd", "config", "set", "types.custom", customTypes)
configCmd := exec.Command("bd", "config", "set", "types.custom", constants.BeadsCustomTypes)
configCmd.Dir = townPath
if configOutput, configErr := configCmd.CombinedOutput(); configErr != nil {
// Non-fatal: older beads versions don't need this, newer ones do
@@ -390,7 +390,7 @@ func ensureRepoFingerprint(beadsPath string) error {
// Gas Town needs custom types: agent, role, rig, convoy, slot.
// This is idempotent - safe to call multiple times.
func ensureCustomTypes(beadsPath string) error {
cmd := exec.Command("bd", "config", "set", "types.custom", "agent,role,rig,convoy,slot")
cmd := exec.Command("bd", "config", "set", "types.custom", constants.BeadsCustomTypes)
cmd.Dir = beadsPath
output, err := cmd.CombinedOutput()
if err != nil {

View File

@@ -73,6 +73,19 @@ const (
FileAccountsJSON = "accounts.json"
)
// Beads configuration constants.
const (
// BeadsCustomTypes is the comma-separated list of custom issue types that
// Gas Town registers with beads. These types were extracted from beads core
// in v0.46.0 and now require explicit configuration.
BeadsCustomTypes = "agent,role,rig,convoy,slot"
)
// BeadsCustomTypesList returns the custom types as a slice.
func BeadsCustomTypesList() []string {
return []string{"agent", "role", "rig", "convoy", "slot"}
}
// Git branch names.
const (
// BranchMain is the default main branch name.

View File

@@ -4,6 +4,7 @@ import (
"bufio"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
@@ -533,3 +534,114 @@ func containsFlag(s, flag string) bool {
next := s[end]
return next == '"' || next == ' ' || next == '\'' || next == '\n' || next == '\t'
}
// CustomTypesCheck verifies Gas Town custom types are registered with beads.
type CustomTypesCheck struct {
FixableCheck
missingTypes []string // Cached during Run for use in Fix
townRoot string // Cached during Run for use in Fix
}
// NewCustomTypesCheck creates a new custom types check.
func NewCustomTypesCheck() *CustomTypesCheck {
return &CustomTypesCheck{
FixableCheck: FixableCheck{
BaseCheck: BaseCheck{
CheckName: "beads-custom-types",
CheckDescription: "Check that Gas Town custom types are registered with beads",
},
},
}
}
// Run checks if custom types are properly configured.
func (c *CustomTypesCheck) Run(ctx *CheckContext) *CheckResult {
// Check if bd command is available
if _, err := exec.LookPath("bd"); err != nil {
return &CheckResult{
Name: c.Name(),
Status: StatusOK,
Message: "beads not installed (skipped)",
}
}
// Check if .beads directory exists at town level
townBeadsDir := filepath.Join(ctx.TownRoot, ".beads")
if _, err := os.Stat(townBeadsDir); os.IsNotExist(err) {
return &CheckResult{
Name: c.Name(),
Status: StatusOK,
Message: "No beads database (skipped)",
}
}
// Get current custom types configuration
cmd := exec.Command("bd", "config", "get", "types.custom")
cmd.Dir = ctx.TownRoot
output, err := cmd.CombinedOutput()
if err != nil {
// If config key doesn't exist, types are not configured
c.townRoot = ctx.TownRoot
c.missingTypes = constants.BeadsCustomTypesList()
return &CheckResult{
Name: c.Name(),
Status: StatusWarning,
Message: "Custom types not configured",
Details: []string{
"Gas Town custom types (agent, role, rig, convoy, slot) are not registered",
"This may cause bead creation/validation errors",
},
FixHint: "Run 'gt doctor --fix' or 'bd config set types.custom \"" + constants.BeadsCustomTypes + "\"'",
}
}
// Parse configured types
configuredTypes := strings.TrimSpace(string(output))
configuredSet := make(map[string]bool)
for _, t := range strings.Split(configuredTypes, ",") {
configuredSet[strings.TrimSpace(t)] = true
}
// Check for missing required types
var missing []string
for _, required := range constants.BeadsCustomTypesList() {
if !configuredSet[required] {
missing = append(missing, required)
}
}
if len(missing) == 0 {
return &CheckResult{
Name: c.Name(),
Status: StatusOK,
Message: "All custom types registered",
}
}
// Cache for Fix
c.townRoot = ctx.TownRoot
c.missingTypes = missing
return &CheckResult{
Name: c.Name(),
Status: StatusWarning,
Message: fmt.Sprintf("%d custom type(s) missing", len(missing)),
Details: []string{
fmt.Sprintf("Missing types: %s", strings.Join(missing, ", ")),
fmt.Sprintf("Configured: %s", configuredTypes),
fmt.Sprintf("Required: %s", constants.BeadsCustomTypes),
},
FixHint: "Run 'gt doctor --fix' to register missing types",
}
}
// Fix registers the missing custom types.
func (c *CustomTypesCheck) Fix(ctx *CheckContext) error {
cmd := exec.Command("bd", "config", "set", "types.custom", constants.BeadsCustomTypes)
cmd.Dir = c.townRoot
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("bd config set types.custom: %s", strings.TrimSpace(string(output)))
}
return nil
}

View File

@@ -10,6 +10,7 @@ import (
"strings"
"github.com/steveyegge/gastown/internal/config"
"github.com/steveyegge/gastown/internal/constants"
)
// RigIsGitRepoCheck verifies the rig has a valid mayor/rig git clone.
@@ -1046,7 +1047,7 @@ func (c *BeadsRedirectCheck) Fix(ctx *CheckContext) error {
} else {
_ = output // bd init succeeded
// Configure custom types for Gas Town (beads v0.46.0+)
configCmd := exec.Command("bd", "config", "set", "types.custom", "agent,role,rig,convoy,event")
configCmd := exec.Command("bd", "config", "set", "types.custom", constants.BeadsCustomTypes)
configCmd.Dir = rigPath
_, _ = configCmd.CombinedOutput() // Ignore errors - older beads don't need this
}

View File

@@ -13,6 +13,7 @@ import (
"github.com/steveyegge/gastown/internal/beads"
"github.com/steveyegge/gastown/internal/claude"
"github.com/steveyegge/gastown/internal/constants"
"github.com/steveyegge/gastown/internal/config"
"github.com/steveyegge/gastown/internal/git"
"github.com/steveyegge/gastown/internal/templates"
@@ -365,7 +366,7 @@ func (m *Manager) AddRig(opts AddRigOptions) (*Rig, error) {
fmt.Printf(" Warning: Could not init bd database: %v (%s)\n", err, strings.TrimSpace(string(output)))
}
// Configure custom types for Gas Town (beads v0.46.0+)
configCmd := exec.Command("bd", "config", "set", "types.custom", "agent,role,rig,convoy,event")
configCmd := exec.Command("bd", "config", "set", "types.custom", constants.BeadsCustomTypes)
configCmd.Dir = mayorRigPath
_, _ = configCmd.CombinedOutput() // Ignore errors - older beads don't need this
}
@@ -600,7 +601,7 @@ func (m *Manager) initBeads(rigPath, prefix string) error {
// Configure custom types for Gas Town (agent, role, rig, convoy).
// These were extracted from beads core in v0.46.0 and now require explicit config.
configCmd := exec.Command("bd", "config", "set", "types.custom", "agent,role,rig,convoy,event")
configCmd := exec.Command("bd", "config", "set", "types.custom", constants.BeadsCustomTypes)
configCmd.Dir = rigPath
configCmd.Env = filteredEnv
// Ignore errors - older beads versions don't need this
@@ -624,7 +625,7 @@ func (m *Manager) initBeads(rigPath, prefix string) error {
fmt.Printf(" ⚠ Could not add route to town beads: %v\n", err)
}
typesCmd := exec.Command("bd", "config", "set", "types.custom", "agent,role,rig,convoy,slot")
typesCmd := exec.Command("bd", "config", "set", "types.custom", constants.BeadsCustomTypes)
typesCmd.Dir = rigPath
typesCmd.Env = filteredEnv
_, _ = typesCmd.CombinedOutput()