feat(types): remove Gas Town types from core built-in types

Core beads built-in types now only include work types:
- bug, feature, task, epic, chore

Gas Town types (molecule, gate, convoy, merge-request, slot, agent,
role, rig, event, message) are now "well-known custom types":
- Constants still exist for code convenience
- Require types.custom configuration for validation
- bd types command shows core types and configured custom types

Changes:
- types.go: Separate core work types from well-known custom types
- IsValid(): Only accepts core work types
- bd types: Updated to show core types and custom types from config
- memory.go: Use ValidateWithCustom for custom type support
- multirepo.go: Only check core types as built-in
- Updated all tests to configure custom types

This allows Gas Town (and other projects) to define their own types
via config while keeping beads core focused on work tracking.

Closes: bd-find4

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
beads/crew/emma
2026-01-17 05:07:11 -08:00
committed by Steve Yegge
parent 88a6438c80
commit 4f0f5744a6
14 changed files with 189 additions and 67 deletions

View File

@@ -209,10 +209,10 @@ func findUnknownTypesInHydratedIssues(repoPath string, multiRepo *config.MultiRe
}
defer db.Close()
// Collect all known types (built-in + parent custom + all child custom)
// Collect all known types (core work types + parent custom + all child custom)
// Only core work types are built-in; Gas Town types require types.custom config.
knownTypes := map[string]bool{
"bug": true, "feature": true, "task": true, "epic": true, "chore": true,
"message": true, "merge-request": true, "molecule": true, "gate": true, "event": true,
}
// Add parent's custom types

View File

@@ -35,6 +35,13 @@ func setupGatedTestDB(t *testing.T) (*sqlite.SQLiteStorage, func()) {
t.Fatalf("Failed to set issue_prefix: %v", err)
}
// Configure Gas Town custom types for test compatibility (bd-find4)
if err := store.SetConfig(ctx, "types.custom", "molecule,gate,convoy,merge-request,slot,agent,role,rig,event,message"); err != nil {
store.Close()
os.RemoveAll(tmpDir)
t.Fatalf("Failed to set types.custom: %v", err)
}
cleanup := func() {
store.Close()
os.RemoveAll(tmpDir)

View File

@@ -100,6 +100,10 @@ func TestFindRepliesToAndReplies_WorksWithMemoryStorage(t *testing.T) {
if err := st.SetConfig(ctx, "issue_prefix", "test"); err != nil {
t.Fatalf("SetConfig: %v", err)
}
// Configure Gas Town custom types for test compatibility (bd-find4)
if err := st.SetConfig(ctx, "types.custom", "message"); err != nil {
t.Fatalf("SetConfig types.custom: %v", err)
}
root := &types.Issue{Title: "root", Status: types.StatusOpen, Priority: 2, IssueType: types.TypeMessage, Sender: "a", Assignee: "b"}
reply1 := &types.Issue{Title: "r1", Status: types.StatusOpen, Priority: 2, IssueType: types.TypeMessage, Sender: "b", Assignee: "a"}

View File

@@ -84,26 +84,32 @@ func failIfProductionDatabase(t *testing.T, dbPath string) {
// This prevents "database not initialized" errors in tests
func newTestStore(t *testing.T, dbPath string) *sqlite.SQLiteStorage {
t.Helper()
// CRITICAL (bd-2c5a): Ensure we're not polluting production database
failIfProductionDatabase(t, dbPath)
if err := os.MkdirAll(filepath.Dir(dbPath), 0755); err != nil {
t.Fatalf("Failed to create database directory: %v", err)
}
store, err := sqlite.New(context.Background(), dbPath)
if err != nil {
t.Fatalf("Failed to create test database: %v", err)
}
// CRITICAL (bd-166): Set issue_prefix to prevent "database not initialized" errors
ctx := context.Background()
if err := store.SetConfig(ctx, "issue_prefix", "test"); err != nil {
store.Close()
t.Fatalf("Failed to set issue_prefix: %v", err)
}
// Configure Gas Town custom types for test compatibility (bd-find4)
if err := store.SetConfig(ctx, "types.custom", "molecule,gate,convoy,merge-request,slot,agent,role,rig,event,message"); err != nil {
store.Close()
t.Fatalf("Failed to set types.custom: %v", err)
}
t.Cleanup(func() { store.Close() })
return store
}
@@ -130,7 +136,13 @@ func newTestStoreWithPrefix(t *testing.T, dbPath string, prefix string) *sqlite.
store.Close()
t.Fatalf("Failed to set issue_prefix: %v", err)
}
// Configure Gas Town custom types for test compatibility (bd-find4)
if err := store.SetConfig(ctx, "types.custom", "molecule,gate,convoy,merge-request,slot,agent,role,rig,event,message"); err != nil {
store.Close()
t.Fatalf("Failed to set types.custom: %v", err)
}
t.Cleanup(func() { store.Close() })
return store
}

View File

@@ -1,33 +1,37 @@
package main
import (
"context"
"fmt"
"strings"
"github.com/spf13/cobra"
"github.com/steveyegge/beads/internal/types"
)
// AllIssueTypes returns all valid built-in issue types with descriptions.
// Ordered by typical usage frequency: work types first, then system types.
var allIssueTypes = []struct {
// coreWorkTypes are the built-in types that beads validates without configuration.
var coreWorkTypes = []struct {
Type types.IssueType
Description string
}{
// Work types (common user-facing types)
{types.TypeTask, "General work item (default)"},
{types.TypeBug, "Bug report or defect"},
{types.TypeFeature, "New feature or enhancement"},
{types.TypeChore, "Maintenance or housekeeping"},
{types.TypeEpic, "Large body of work spanning multiple issues"},
}
// System types (used by tooling)
// wellKnownCustomTypes are commonly used types that require types.custom configuration.
// These are used by Gas Town and other infrastructure that extends beads.
var wellKnownCustomTypes = []struct {
Type types.IssueType
Description string
}{
{types.TypeMolecule, "Template for issue hierarchies"},
{types.TypeGate, "Async coordination gate"},
{types.TypeConvoy, "Cross-project tracking with reactive completion"},
{types.TypeMergeRequest, "Merge queue entry for refinery processing"},
{types.TypeSlot, "Exclusive access slot (merge-slot gate)"},
// Agent types (Gas Town infrastructure)
{types.TypeAgent, "Agent identity bead"},
{types.TypeRole, "Agent role definition"},
{types.TypeRig, "Rig identity bead (multi-repo workspace)"},
@@ -41,55 +45,85 @@ var typesCmd = &cobra.Command{
Short: "List valid issue types",
Long: `List all valid issue types that can be used with bd create --type.
Types are organized into categories:
- Work types: Common types for tracking work (task, bug, feature, etc.)
- System types: Used by beads tooling (molecule, gate, convoy, etc.)
- Agent types: Used by Gas Town agent infrastructure
Core work types (bug, task, feature, chore, epic) are always valid.
Additional types require configuration via types.custom in .beads/config.yaml.
Examples:
bd types # List all types with descriptions
bd types --json # Output as JSON
`,
Run: func(cmd *cobra.Command, args []string) {
// Get custom types from config
var customTypes []string
ctx := context.Background()
if store != nil {
if ct, err := store.GetCustomTypes(ctx); err == nil {
customTypes = ct
}
}
if jsonOutput {
result := struct {
Types []struct {
Name string `json:"name"`
Description string `json:"description"`
} `json:"types"`
CoreTypes []typeInfo `json:"core_types"`
CustomTypes []string `json:"custom_types,omitempty"`
}{}
for _, t := range allIssueTypes {
result.Types = append(result.Types, struct {
Name string `json:"name"`
Description string `json:"description"`
}{
for _, t := range coreWorkTypes {
result.CoreTypes = append(result.CoreTypes, typeInfo{
Name: string(t.Type),
Description: t.Description,
})
}
result.CustomTypes = customTypes
outputJSON(result)
return
}
// Text output with categories
fmt.Println("Work types:")
for _, t := range allIssueTypes[:5] {
// Text output
fmt.Println("Core work types (built-in):")
for _, t := range coreWorkTypes {
fmt.Printf(" %-14s %s\n", t.Type, t.Description)
}
fmt.Println("\nSystem types:")
for _, t := range allIssueTypes[5:10] {
fmt.Printf(" %-14s %s\n", t.Type, t.Description)
if len(customTypes) > 0 {
fmt.Println("\nConfigured custom types:")
for _, t := range customTypes {
// Check if it's a well-known type and show description
desc := ""
for _, wk := range wellKnownCustomTypes {
if string(wk.Type) == t {
desc = wk.Description
break
}
}
if desc != "" {
fmt.Printf(" %-14s %s\n", t, desc)
} else {
fmt.Printf(" %s\n", t)
}
}
} else {
fmt.Println("\nNo custom types configured.")
fmt.Println("Configure with: bd config set types.custom \"type1,type2,...\"")
}
fmt.Println("\nAgent types:")
for _, t := range allIssueTypes[10:] {
fmt.Printf(" %-14s %s\n", t.Type, t.Description)
// Show hint about well-known types if none are configured
if len(customTypes) == 0 {
fmt.Println("\nWell-known custom types (used by Gas Town):")
var typeNames []string
for _, t := range wellKnownCustomTypes {
typeNames = append(typeNames, string(t.Type))
}
fmt.Printf(" %s\n", strings.Join(typeNames, ", "))
}
},
}
type typeInfo struct {
Name string `json:"name"`
Description string `json:"description"`
}
func init() {
rootCmd.AddCommand(typesCmd)
}