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
@@ -209,10 +209,10 @@ func findUnknownTypesInHydratedIssues(repoPath string, multiRepo *config.MultiRe
|
|||||||
}
|
}
|
||||||
defer db.Close()
|
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{
|
knownTypes := map[string]bool{
|
||||||
"bug": true, "feature": true, "task": true, "epic": true, "chore": true,
|
"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
|
// Add parent's custom types
|
||||||
|
|||||||
@@ -35,6 +35,13 @@ func setupGatedTestDB(t *testing.T) (*sqlite.SQLiteStorage, func()) {
|
|||||||
t.Fatalf("Failed to set issue_prefix: %v", err)
|
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() {
|
cleanup := func() {
|
||||||
store.Close()
|
store.Close()
|
||||||
os.RemoveAll(tmpDir)
|
os.RemoveAll(tmpDir)
|
||||||
|
|||||||
@@ -100,6 +100,10 @@ func TestFindRepliesToAndReplies_WorksWithMemoryStorage(t *testing.T) {
|
|||||||
if err := st.SetConfig(ctx, "issue_prefix", "test"); err != nil {
|
if err := st.SetConfig(ctx, "issue_prefix", "test"); err != nil {
|
||||||
t.Fatalf("SetConfig: %v", err)
|
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"}
|
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"}
|
reply1 := &types.Issue{Title: "r1", Status: types.StatusOpen, Priority: 2, IssueType: types.TypeMessage, Sender: "b", Assignee: "a"}
|
||||||
|
|||||||
@@ -84,26 +84,32 @@ func failIfProductionDatabase(t *testing.T, dbPath string) {
|
|||||||
// This prevents "database not initialized" errors in tests
|
// This prevents "database not initialized" errors in tests
|
||||||
func newTestStore(t *testing.T, dbPath string) *sqlite.SQLiteStorage {
|
func newTestStore(t *testing.T, dbPath string) *sqlite.SQLiteStorage {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
// CRITICAL (bd-2c5a): Ensure we're not polluting production database
|
// CRITICAL (bd-2c5a): Ensure we're not polluting production database
|
||||||
failIfProductionDatabase(t, dbPath)
|
failIfProductionDatabase(t, dbPath)
|
||||||
|
|
||||||
if err := os.MkdirAll(filepath.Dir(dbPath), 0755); err != nil {
|
if err := os.MkdirAll(filepath.Dir(dbPath), 0755); err != nil {
|
||||||
t.Fatalf("Failed to create database directory: %v", err)
|
t.Fatalf("Failed to create database directory: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
store, err := sqlite.New(context.Background(), dbPath)
|
store, err := sqlite.New(context.Background(), dbPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create test database: %v", err)
|
t.Fatalf("Failed to create test database: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CRITICAL (bd-166): Set issue_prefix to prevent "database not initialized" errors
|
// CRITICAL (bd-166): Set issue_prefix to prevent "database not initialized" errors
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
if err := store.SetConfig(ctx, "issue_prefix", "test"); err != nil {
|
if err := store.SetConfig(ctx, "issue_prefix", "test"); err != nil {
|
||||||
store.Close()
|
store.Close()
|
||||||
t.Fatalf("Failed to set issue_prefix: %v", err)
|
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() })
|
t.Cleanup(func() { store.Close() })
|
||||||
return store
|
return store
|
||||||
}
|
}
|
||||||
@@ -130,7 +136,13 @@ func newTestStoreWithPrefix(t *testing.T, dbPath string, prefix string) *sqlite.
|
|||||||
store.Close()
|
store.Close()
|
||||||
t.Fatalf("Failed to set issue_prefix: %v", err)
|
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() })
|
t.Cleanup(func() { store.Close() })
|
||||||
return store
|
return store
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,37 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/steveyegge/beads/internal/types"
|
"github.com/steveyegge/beads/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AllIssueTypes returns all valid built-in issue types with descriptions.
|
// coreWorkTypes are the built-in types that beads validates without configuration.
|
||||||
// Ordered by typical usage frequency: work types first, then system types.
|
var coreWorkTypes = []struct {
|
||||||
var allIssueTypes = []struct {
|
|
||||||
Type types.IssueType
|
Type types.IssueType
|
||||||
Description string
|
Description string
|
||||||
}{
|
}{
|
||||||
// Work types (common user-facing types)
|
|
||||||
{types.TypeTask, "General work item (default)"},
|
{types.TypeTask, "General work item (default)"},
|
||||||
{types.TypeBug, "Bug report or defect"},
|
{types.TypeBug, "Bug report or defect"},
|
||||||
{types.TypeFeature, "New feature or enhancement"},
|
{types.TypeFeature, "New feature or enhancement"},
|
||||||
{types.TypeChore, "Maintenance or housekeeping"},
|
{types.TypeChore, "Maintenance or housekeeping"},
|
||||||
{types.TypeEpic, "Large body of work spanning multiple issues"},
|
{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.TypeMolecule, "Template for issue hierarchies"},
|
||||||
{types.TypeGate, "Async coordination gate"},
|
{types.TypeGate, "Async coordination gate"},
|
||||||
{types.TypeConvoy, "Cross-project tracking with reactive completion"},
|
{types.TypeConvoy, "Cross-project tracking with reactive completion"},
|
||||||
{types.TypeMergeRequest, "Merge queue entry for refinery processing"},
|
{types.TypeMergeRequest, "Merge queue entry for refinery processing"},
|
||||||
{types.TypeSlot, "Exclusive access slot (merge-slot gate)"},
|
{types.TypeSlot, "Exclusive access slot (merge-slot gate)"},
|
||||||
|
|
||||||
// Agent types (Gas Town infrastructure)
|
|
||||||
{types.TypeAgent, "Agent identity bead"},
|
{types.TypeAgent, "Agent identity bead"},
|
||||||
{types.TypeRole, "Agent role definition"},
|
{types.TypeRole, "Agent role definition"},
|
||||||
{types.TypeRig, "Rig identity bead (multi-repo workspace)"},
|
{types.TypeRig, "Rig identity bead (multi-repo workspace)"},
|
||||||
@@ -41,55 +45,85 @@ var typesCmd = &cobra.Command{
|
|||||||
Short: "List valid issue types",
|
Short: "List valid issue types",
|
||||||
Long: `List all valid issue types that can be used with bd create --type.
|
Long: `List all valid issue types that can be used with bd create --type.
|
||||||
|
|
||||||
Types are organized into categories:
|
Core work types (bug, task, feature, chore, epic) are always valid.
|
||||||
- Work types: Common types for tracking work (task, bug, feature, etc.)
|
Additional types require configuration via types.custom in .beads/config.yaml.
|
||||||
- System types: Used by beads tooling (molecule, gate, convoy, etc.)
|
|
||||||
- Agent types: Used by Gas Town agent infrastructure
|
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
bd types # List all types with descriptions
|
bd types # List all types with descriptions
|
||||||
bd types --json # Output as JSON
|
bd types --json # Output as JSON
|
||||||
`,
|
`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
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 {
|
if jsonOutput {
|
||||||
result := struct {
|
result := struct {
|
||||||
Types []struct {
|
CoreTypes []typeInfo `json:"core_types"`
|
||||||
Name string `json:"name"`
|
CustomTypes []string `json:"custom_types,omitempty"`
|
||||||
Description string `json:"description"`
|
|
||||||
} `json:"types"`
|
|
||||||
}{}
|
}{}
|
||||||
|
|
||||||
for _, t := range allIssueTypes {
|
for _, t := range coreWorkTypes {
|
||||||
result.Types = append(result.Types, struct {
|
result.CoreTypes = append(result.CoreTypes, typeInfo{
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
}{
|
|
||||||
Name: string(t.Type),
|
Name: string(t.Type),
|
||||||
Description: t.Description,
|
Description: t.Description,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
result.CustomTypes = customTypes
|
||||||
outputJSON(result)
|
outputJSON(result)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text output with categories
|
// Text output
|
||||||
fmt.Println("Work types:")
|
fmt.Println("Core work types (built-in):")
|
||||||
for _, t := range allIssueTypes[:5] {
|
for _, t := range coreWorkTypes {
|
||||||
fmt.Printf(" %-14s %s\n", t.Type, t.Description)
|
fmt.Printf(" %-14s %s\n", t.Type, t.Description)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("\nSystem types:")
|
if len(customTypes) > 0 {
|
||||||
for _, t := range allIssueTypes[5:10] {
|
fmt.Println("\nConfigured custom types:")
|
||||||
fmt.Printf(" %-14s %s\n", t.Type, t.Description)
|
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:")
|
// Show hint about well-known types if none are configured
|
||||||
for _, t := range allIssueTypes[10:] {
|
if len(customTypes) == 0 {
|
||||||
fmt.Printf(" %-14s %s\n", t.Type, t.Description)
|
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() {
|
func init() {
|
||||||
rootCmd.AddCommand(typesCmd)
|
rootCmd.AddCommand(typesCmd)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,6 +81,11 @@ func TestLoader_LoadAll(t *testing.T) {
|
|||||||
t.Fatalf("Failed to set prefix: %v", err)
|
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
|
// Create a project-level molecules.jsonl
|
||||||
moleculesPath := filepath.Join(beadsDir, "molecules.jsonl")
|
moleculesPath := filepath.Join(beadsDir, "molecules.jsonl")
|
||||||
content := `{"id":"mol-feature","title":"Feature Template","issue_type":"molecule","status":"open","description":"Standard feature workflow"}
|
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)
|
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)
|
// Pre-create a molecule in the database (skip prefix validation for mol-* IDs)
|
||||||
existingMol := &types.Issue{
|
existingMol := &types.Issue{
|
||||||
ID: "mol-existing",
|
ID: "mol-existing",
|
||||||
|
|||||||
@@ -51,6 +51,13 @@ func setupTestServer(t *testing.T) (*Server, *Client, func()) {
|
|||||||
t.Fatalf("Failed to set issue_prefix: %v", err)
|
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)
|
server := NewServer(socketPath, store, tmpDir, dbPath)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|||||||
@@ -190,8 +190,17 @@ func (m *MemoryStorage) CreateIssue(ctx context.Context, issue *types.Issue, act
|
|||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
// Validate
|
// Get custom types and statuses for validation
|
||||||
if err := issue.Validate(); err != nil {
|
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)
|
return fmt.Errorf("validation failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,9 +252,18 @@ func (m *MemoryStorage) CreateIssues(ctx context.Context, issues []*types.Issue,
|
|||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
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
|
// Validate all first
|
||||||
for i, issue := range issues {
|
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)
|
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)
|
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
|
return store
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,11 @@ func TestGateFieldsPreservedAcrossConnections(t *testing.T) {
|
|||||||
t.Fatalf("failed to set issue_prefix: %v", err)
|
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{
|
gate := &types.Issue{
|
||||||
ID: "beads-test1",
|
ID: "beads-test1",
|
||||||
Title: "Test Gate",
|
Title: "Test Gate",
|
||||||
|
|||||||
@@ -39,6 +39,14 @@ func setupTestDB(t *testing.T) (*SQLiteStorage, func()) {
|
|||||||
t.Fatalf("failed to set issue_prefix: %v", err)
|
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() {
|
cleanup := func() {
|
||||||
store.Close()
|
store.Close()
|
||||||
os.RemoveAll(tmpDir)
|
os.RemoveAll(tmpDir)
|
||||||
|
|||||||
@@ -476,13 +476,20 @@ func (s Status) IsValidWithCustom(customStatuses []string) bool {
|
|||||||
// IssueType categorizes the kind of work
|
// IssueType categorizes the kind of work
|
||||||
type IssueType string
|
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 (
|
const (
|
||||||
TypeBug IssueType = "bug"
|
|
||||||
TypeFeature IssueType = "feature"
|
|
||||||
TypeTask IssueType = "task"
|
|
||||||
TypeEpic IssueType = "epic"
|
|
||||||
TypeChore IssueType = "chore"
|
|
||||||
TypeMessage IssueType = "message" // Ephemeral communication between workers
|
TypeMessage IssueType = "message" // Ephemeral communication between workers
|
||||||
TypeMergeRequest IssueType = "merge-request" // Merge queue entry for refinery processing
|
TypeMergeRequest IssueType = "merge-request" // Merge queue entry for refinery processing
|
||||||
TypeMolecule IssueType = "molecule" // Template molecule for issue hierarchies
|
TypeMolecule IssueType = "molecule" // Template molecule for issue hierarchies
|
||||||
@@ -495,10 +502,12 @@ const (
|
|||||||
TypeSlot IssueType = "slot" // Exclusive access slot (merge-slot gate)
|
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 {
|
func (t IssueType) IsValid() bool {
|
||||||
switch t {
|
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 true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -569,8 +578,7 @@ func (t IssueType) RequiredSections() []RequiredSection {
|
|||||||
{Heading: "## Success Criteria", Hint: "Define high-level success criteria"},
|
{Heading: "## Success Criteria", Hint: "Define high-level success criteria"},
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
// Chore, message, molecule, gate, agent, role, convoy, event, merge-request
|
// Chore and custom types have no required sections
|
||||||
// have no required sections
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -539,20 +539,24 @@ func TestIssueTypeIsValid(t *testing.T) {
|
|||||||
issueType IssueType
|
issueType IssueType
|
||||||
valid bool
|
valid bool
|
||||||
}{
|
}{
|
||||||
|
// Core work types are always valid
|
||||||
{TypeBug, true},
|
{TypeBug, true},
|
||||||
{TypeFeature, true},
|
{TypeFeature, true},
|
||||||
{TypeTask, true},
|
{TypeTask, true},
|
||||||
{TypeEpic, true},
|
{TypeEpic, true},
|
||||||
{TypeChore, true},
|
{TypeChore, true},
|
||||||
{TypeMessage, true},
|
// Gas Town types require types.custom configuration
|
||||||
{TypeMergeRequest, true},
|
{TypeMessage, false},
|
||||||
{TypeMolecule, true},
|
{TypeMergeRequest, false},
|
||||||
{TypeGate, true},
|
{TypeMolecule, false},
|
||||||
{TypeAgent, true},
|
{TypeGate, false},
|
||||||
{TypeRole, true},
|
{TypeAgent, false},
|
||||||
{TypeConvoy, true},
|
{TypeRole, false},
|
||||||
{TypeEvent, true},
|
{TypeConvoy, false},
|
||||||
{TypeSlot, true},
|
{TypeEvent, false},
|
||||||
|
{TypeSlot, false},
|
||||||
|
{TypeRig, false},
|
||||||
|
// Invalid types
|
||||||
{IssueType("invalid"), false},
|
{IssueType("invalid"), false},
|
||||||
{IssueType(""), false},
|
{IssueType(""), false},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,19 +211,18 @@ func TestParseIssueType(t *testing.T) {
|
|||||||
wantError bool
|
wantError bool
|
||||||
errorContains string
|
errorContains string
|
||||||
}{
|
}{
|
||||||
// Valid issue types
|
// Core work types (always valid)
|
||||||
{"bug type", "bug", types.TypeBug, false, ""},
|
{"bug type", "bug", types.TypeBug, false, ""},
|
||||||
{"feature type", "feature", types.TypeFeature, false, ""},
|
{"feature type", "feature", types.TypeFeature, false, ""},
|
||||||
{"task type", "task", types.TypeTask, false, ""},
|
{"task type", "task", types.TypeTask, false, ""},
|
||||||
{"epic type", "epic", types.TypeEpic, false, ""},
|
{"epic type", "epic", types.TypeEpic, false, ""},
|
||||||
{"chore type", "chore", types.TypeChore, false, ""},
|
{"chore type", "chore", types.TypeChore, false, ""},
|
||||||
{"merge-request type", "merge-request", types.TypeMergeRequest, false, ""},
|
// Gas Town types require types.custom configuration (invalid without config)
|
||||||
{"molecule type", "molecule", types.TypeMolecule, false, ""},
|
{"merge-request type", "merge-request", types.TypeTask, true, "invalid issue type"},
|
||||||
{"gate type", "gate", types.TypeGate, false, ""},
|
{"molecule type", "molecule", types.TypeTask, true, "invalid issue type"},
|
||||||
{"event type", "event", types.TypeEvent, false, ""},
|
{"gate type", "gate", types.TypeTask, true, "invalid issue type"},
|
||||||
{"message type", "message", types.TypeMessage, false, ""},
|
{"event type", "event", types.TypeTask, true, "invalid issue type"},
|
||||||
// Gas Town types (agent, role, rig, convoy, slot) have been removed
|
{"message type", "message", types.TypeTask, true, "invalid issue type"},
|
||||||
// They now require custom type configuration,
|
|
||||||
|
|
||||||
// Case sensitivity (function is case-sensitive)
|
// Case sensitivity (function is case-sensitive)
|
||||||
{"uppercase bug", "BUG", types.TypeTask, true, "invalid issue type"},
|
{"uppercase bug", "BUG", types.TypeTask, true, "invalid issue type"},
|
||||||
|
|||||||
Reference in New Issue
Block a user