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:
committed by
Steve Yegge
parent
86739556c2
commit
61b561a540
@@ -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())
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user