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:
committed by
Steve Yegge
parent
88a6438c80
commit
4f0f5744a6
@@ -81,6 +81,11 @@ func TestLoader_LoadAll(t *testing.T) {
|
||||
t.Fatalf("Failed to set prefix: %v", err)
|
||||
}
|
||||
|
||||
// Configure custom types for Gas Town types (bd-find4)
|
||||
if err := store.SetConfig(ctx, "types.custom", "molecule"); err != nil {
|
||||
t.Fatalf("Failed to set types.custom: %v", err)
|
||||
}
|
||||
|
||||
// Create a project-level molecules.jsonl
|
||||
moleculesPath := filepath.Join(beadsDir, "molecules.jsonl")
|
||||
content := `{"id":"mol-feature","title":"Feature Template","issue_type":"molecule","status":"open","description":"Standard feature workflow"}
|
||||
@@ -150,6 +155,11 @@ func TestLoader_SkipExistingMolecules(t *testing.T) {
|
||||
t.Fatalf("Failed to set prefix: %v", err)
|
||||
}
|
||||
|
||||
// Configure custom types for Gas Town types (bd-find4)
|
||||
if err := store.SetConfig(ctx, "types.custom", "molecule"); err != nil {
|
||||
t.Fatalf("Failed to set types.custom: %v", err)
|
||||
}
|
||||
|
||||
// Pre-create a molecule in the database (skip prefix validation for mol-* IDs)
|
||||
existingMol := &types.Issue{
|
||||
ID: "mol-existing",
|
||||
|
||||
@@ -51,6 +51,13 @@ func setupTestServer(t *testing.T) (*Server, *Client, 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)
|
||||
}
|
||||
|
||||
server := NewServer(socketPath, store, tmpDir, dbPath)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
@@ -190,8 +190,17 @@ func (m *MemoryStorage) CreateIssue(ctx context.Context, issue *types.Issue, act
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
// Validate
|
||||
if err := issue.Validate(); err != nil {
|
||||
// Get custom types and statuses for validation
|
||||
var customTypes, customStatuses []string
|
||||
if typeStr := m.config["types.custom"]; typeStr != "" {
|
||||
customTypes = parseCustomStatuses(typeStr)
|
||||
}
|
||||
if statusStr := m.config["status.custom"]; statusStr != "" {
|
||||
customStatuses = parseCustomStatuses(statusStr)
|
||||
}
|
||||
|
||||
// Validate with custom types
|
||||
if err := issue.ValidateWithCustom(customStatuses, customTypes); err != nil {
|
||||
return fmt.Errorf("validation failed: %w", err)
|
||||
}
|
||||
|
||||
@@ -243,9 +252,18 @@ func (m *MemoryStorage) CreateIssues(ctx context.Context, issues []*types.Issue,
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
// Get custom types and statuses for validation
|
||||
var customTypes, customStatuses []string
|
||||
if typeStr := m.config["types.custom"]; typeStr != "" {
|
||||
customTypes = parseCustomStatuses(typeStr)
|
||||
}
|
||||
if statusStr := m.config["status.custom"]; statusStr != "" {
|
||||
customStatuses = parseCustomStatuses(statusStr)
|
||||
}
|
||||
|
||||
// Validate all first
|
||||
for i, issue := range issues {
|
||||
if err := issue.Validate(); err != nil {
|
||||
if err := issue.ValidateWithCustom(customStatuses, customTypes); err != nil {
|
||||
return fmt.Errorf("validation failed for issue %d: %w", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,12 @@ func setupTestMemory(t *testing.T) *MemoryStorage {
|
||||
t.Fatalf("failed to set issue_prefix: %v", err)
|
||||
}
|
||||
|
||||
// Configure Gas Town custom types for test compatibility (bd-find4)
|
||||
// These types are no longer built-in but many tests use them
|
||||
if err := store.SetConfig(ctx, "types.custom", "message,merge-request,molecule,gate,agent,role,rig,convoy,event,slot"); err != nil {
|
||||
t.Fatalf("failed to set types.custom: %v", err)
|
||||
}
|
||||
|
||||
return store
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,11 @@ func TestGateFieldsPreservedAcrossConnections(t *testing.T) {
|
||||
t.Fatalf("failed to set issue_prefix: %v", err)
|
||||
}
|
||||
|
||||
// Configure custom types for Gas Town types (gate is not a core type)
|
||||
if err := store1.SetConfig(ctx, "types.custom", "gate"); err != nil {
|
||||
t.Fatalf("failed to set types.custom: %v", err)
|
||||
}
|
||||
|
||||
gate := &types.Issue{
|
||||
ID: "beads-test1",
|
||||
Title: "Test Gate",
|
||||
|
||||
@@ -39,6 +39,14 @@ func setupTestDB(t *testing.T) (*SQLiteStorage, func()) {
|
||||
t.Fatalf("failed to set issue_prefix: %v", err)
|
||||
}
|
||||
|
||||
// Configure Gas Town custom types for test compatibility (bd-find4)
|
||||
// These types are no longer built-in but many tests use them
|
||||
if err := store.SetConfig(ctx, "types.custom", "message,merge-request,molecule,gate,agent,role,rig,convoy,event,slot"); err != nil {
|
||||
store.Close()
|
||||
os.RemoveAll(tmpDir)
|
||||
t.Fatalf("failed to set types.custom: %v", err)
|
||||
}
|
||||
|
||||
cleanup := func() {
|
||||
store.Close()
|
||||
os.RemoveAll(tmpDir)
|
||||
|
||||
@@ -476,13 +476,20 @@ func (s Status) IsValidWithCustom(customStatuses []string) bool {
|
||||
// IssueType categorizes the kind of work
|
||||
type IssueType string
|
||||
|
||||
// Issue type constants
|
||||
// Core work type constants - these are the built-in types that beads validates.
|
||||
// All other types require configuration via types.custom in config.yaml.
|
||||
const (
|
||||
TypeBug IssueType = "bug"
|
||||
TypeFeature IssueType = "feature"
|
||||
TypeTask IssueType = "task"
|
||||
TypeEpic IssueType = "epic"
|
||||
TypeChore IssueType = "chore"
|
||||
)
|
||||
|
||||
// Well-known custom types - constants for code convenience.
|
||||
// These are NOT built-in types and require types.custom configuration for validation.
|
||||
// Used by Gas Town and other infrastructure that extends beads.
|
||||
const (
|
||||
TypeBug IssueType = "bug"
|
||||
TypeFeature IssueType = "feature"
|
||||
TypeTask IssueType = "task"
|
||||
TypeEpic IssueType = "epic"
|
||||
TypeChore IssueType = "chore"
|
||||
TypeMessage IssueType = "message" // Ephemeral communication between workers
|
||||
TypeMergeRequest IssueType = "merge-request" // Merge queue entry for refinery processing
|
||||
TypeMolecule IssueType = "molecule" // Template molecule for issue hierarchies
|
||||
@@ -495,10 +502,12 @@ const (
|
||||
TypeSlot IssueType = "slot" // Exclusive access slot (merge-slot gate)
|
||||
)
|
||||
|
||||
// IsValid checks if the issue type value is valid
|
||||
// IsValid checks if the issue type is a core work type.
|
||||
// Only core work types (bug, feature, task, epic, chore) are built-in.
|
||||
// Other types (molecule, gate, convoy, etc.) require types.custom configuration.
|
||||
func (t IssueType) IsValid() bool {
|
||||
switch t {
|
||||
case TypeBug, TypeFeature, TypeTask, TypeEpic, TypeChore, TypeMessage, TypeMergeRequest, TypeMolecule, TypeGate, TypeAgent, TypeRole, TypeRig, TypeConvoy, TypeEvent, TypeSlot:
|
||||
case TypeBug, TypeFeature, TypeTask, TypeEpic, TypeChore:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -569,8 +578,7 @@ func (t IssueType) RequiredSections() []RequiredSection {
|
||||
{Heading: "## Success Criteria", Hint: "Define high-level success criteria"},
|
||||
}
|
||||
default:
|
||||
// Chore, message, molecule, gate, agent, role, convoy, event, merge-request
|
||||
// have no required sections
|
||||
// Chore and custom types have no required sections
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -539,20 +539,24 @@ func TestIssueTypeIsValid(t *testing.T) {
|
||||
issueType IssueType
|
||||
valid bool
|
||||
}{
|
||||
// Core work types are always valid
|
||||
{TypeBug, true},
|
||||
{TypeFeature, true},
|
||||
{TypeTask, true},
|
||||
{TypeEpic, true},
|
||||
{TypeChore, true},
|
||||
{TypeMessage, true},
|
||||
{TypeMergeRequest, true},
|
||||
{TypeMolecule, true},
|
||||
{TypeGate, true},
|
||||
{TypeAgent, true},
|
||||
{TypeRole, true},
|
||||
{TypeConvoy, true},
|
||||
{TypeEvent, true},
|
||||
{TypeSlot, true},
|
||||
// Gas Town types require types.custom configuration
|
||||
{TypeMessage, false},
|
||||
{TypeMergeRequest, false},
|
||||
{TypeMolecule, false},
|
||||
{TypeGate, false},
|
||||
{TypeAgent, false},
|
||||
{TypeRole, false},
|
||||
{TypeConvoy, false},
|
||||
{TypeEvent, false},
|
||||
{TypeSlot, false},
|
||||
{TypeRig, false},
|
||||
// Invalid types
|
||||
{IssueType("invalid"), false},
|
||||
{IssueType(""), false},
|
||||
}
|
||||
|
||||
@@ -211,19 +211,18 @@ func TestParseIssueType(t *testing.T) {
|
||||
wantError bool
|
||||
errorContains string
|
||||
}{
|
||||
// Valid issue types
|
||||
// Core work types (always valid)
|
||||
{"bug type", "bug", types.TypeBug, false, ""},
|
||||
{"feature type", "feature", types.TypeFeature, false, ""},
|
||||
{"task type", "task", types.TypeTask, false, ""},
|
||||
{"epic type", "epic", types.TypeEpic, false, ""},
|
||||
{"chore type", "chore", types.TypeChore, false, ""},
|
||||
{"merge-request type", "merge-request", types.TypeMergeRequest, false, ""},
|
||||
{"molecule type", "molecule", types.TypeMolecule, false, ""},
|
||||
{"gate type", "gate", types.TypeGate, false, ""},
|
||||
{"event type", "event", types.TypeEvent, false, ""},
|
||||
{"message type", "message", types.TypeMessage, false, ""},
|
||||
// Gas Town types (agent, role, rig, convoy, slot) have been removed
|
||||
// They now require custom type configuration,
|
||||
// Gas Town types require types.custom configuration (invalid without config)
|
||||
{"merge-request type", "merge-request", types.TypeTask, true, "invalid issue type"},
|
||||
{"molecule type", "molecule", types.TypeTask, true, "invalid issue type"},
|
||||
{"gate type", "gate", types.TypeTask, true, "invalid issue type"},
|
||||
{"event type", "event", types.TypeTask, true, "invalid issue type"},
|
||||
{"message type", "message", types.TypeTask, true, "invalid issue type"},
|
||||
|
||||
// Case sensitivity (function is case-sensitive)
|
||||
{"uppercase bug", "BUG", types.TypeTask, true, "invalid issue type"},
|
||||
|
||||
Reference in New Issue
Block a user