diff --git a/internal/cmd/doctor.go b/internal/cmd/doctor.go index fc0c8d87..81e655c5 100644 --- a/internal/cmd/doctor.go +++ b/internal/cmd/doctor.go @@ -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()) diff --git a/internal/cmd/init.go b/internal/cmd/init.go index 2472c4b8..f6371185 100644 --- a/internal/cmd/init.go +++ b/internal/cmd/init.go @@ -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 +} diff --git a/internal/cmd/install.go b/internal/cmd/install.go index 7ddde64f..8da51075 100644 --- a/internal/cmd/install.go +++ b/internal/cmd/install.go @@ -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 { diff --git a/internal/constants/constants.go b/internal/constants/constants.go index 9bd0b15b..597584d7 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -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. diff --git a/internal/doctor/config_check.go b/internal/doctor/config_check.go index 6bfc2aa6..b268a454 100644 --- a/internal/doctor/config_check.go +++ b/internal/doctor/config_check.go @@ -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 +} diff --git a/internal/doctor/rig_check.go b/internal/doctor/rig_check.go index 36ee865d..ed5fd0eb 100644 --- a/internal/doctor/rig_check.go +++ b/internal/doctor/rig_check.go @@ -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 } diff --git a/internal/rig/manager.go b/internal/rig/manager.go index 82a4f674..31c7e378 100644 --- a/internal/rig/manager.go +++ b/internal/rig/manager.go @@ -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()