diff --git a/cmd/bd/cli_coverage_show_test.go b/cmd/bd/cli_coverage_show_test.go index 6eb4d661..7d9a5308 100644 --- a/cmd/bd/cli_coverage_show_test.go +++ b/cmd/bd/cli_coverage_show_test.go @@ -391,9 +391,9 @@ func TestCoverage_ShowThread(t *testing.T) { } ctx := context.Background() - root := &types.Issue{Title: "Root message", IssueType: types.TypeMessage, Status: types.StatusOpen, Sender: "alice", Assignee: "bob"} - reply1 := &types.Issue{Title: "Re: Root", IssueType: types.TypeMessage, Status: types.StatusOpen, Sender: "bob", Assignee: "alice"} - reply2 := &types.Issue{Title: "Re: Re: Root", IssueType: types.TypeMessage, Status: types.StatusOpen, Sender: "alice", Assignee: "bob"} + root := &types.Issue{Title: "Root message", IssueType: "message", Status: types.StatusOpen, Sender: "alice", Assignee: "bob"} + reply1 := &types.Issue{Title: "Re: Root", IssueType: "message", Status: types.StatusOpen, Sender: "bob", Assignee: "alice"} + reply2 := &types.Issue{Title: "Re: Re: Root", IssueType: "message", Status: types.StatusOpen, Sender: "alice", Assignee: "bob"} if err := s.CreateIssue(ctx, root, "test-user"); err != nil { s.Close() t.Fatalf("CreateIssue root: %v", err) diff --git a/cmd/bd/cook.go b/cmd/bd/cook.go index efc2d0c5..42a18af1 100644 --- a/cmd/bd/cook.go +++ b/cmd/bd/cook.go @@ -521,7 +521,7 @@ func createGateIssue(step *formula.Step, parentID string) *types.Issue { Description: fmt.Sprintf("Async gate for step %s", step.ID), Status: types.StatusOpen, Priority: 2, - IssueType: types.TypeGate, + IssueType: "gate", AwaitType: step.Gate.Type, AwaitID: step.Gate.ID, Timeout: timeout, diff --git a/cmd/bd/export_obsidian.go b/cmd/bd/export_obsidian.go index bbde8778..3d8a21ba 100644 --- a/cmd/bd/export_obsidian.go +++ b/cmd/bd/export_obsidian.go @@ -32,20 +32,14 @@ var obsidianPriority = []string{ "⏬", // 4 = backlog/lowest } -// obsidianTypeTag maps bd issue type to Obsidian tag -// Note: Gas Town-specific types (agent, role, rig, convoy, slot) are now labels. -// The labels will be converted to tags automatically via the label->tag logic. +// obsidianTypeTag maps bd issue type to Obsidian tag (core types only) +// Gas Town types are custom types and will use their issue_type value as a tag. var obsidianTypeTag = map[types.IssueType]string{ - types.TypeBug: "#Bug", - types.TypeFeature: "#Feature", - types.TypeTask: "#Task", - types.TypeEpic: "#Epic", - types.TypeChore: "#Chore", - types.TypeMessage: "#Message", - types.TypeMergeRequest: "#MergeRequest", - types.TypeMolecule: "#Molecule", - types.TypeGate: "#Gate", - types.TypeEvent: "#Event", + types.TypeBug: "#Bug", + types.TypeFeature: "#Feature", + types.TypeTask: "#Task", + types.TypeEpic: "#Epic", + types.TypeChore: "#Chore", } // formatObsidianTask converts a single issue to Obsidian Tasks format diff --git a/cmd/bd/gate.go b/cmd/bd/gate.go index b08ac73e..d52f286f 100644 --- a/cmd/bd/gate.go +++ b/cmd/bd/gate.go @@ -61,7 +61,7 @@ By default, shows only open gates. Use --all to include closed gates.`, limit, _ := cmd.Flags().GetInt("limit") // Build filter for gate type issues - gateType := types.TypeGate + gateType := types.IssueType("gate") filter := types.IssueFilter{ IssueType: &gateType, Limit: limit, @@ -243,7 +243,7 @@ This is used by 'gt done --phase-complete' to register for gate wake notificatio } } - if issue.IssueType != types.TypeGate { + if issue.IssueType != "gate" { fmt.Fprintf(os.Stderr, "Error: %s is not a gate issue (type=%s)\n", gateID, issue.IssueType) os.Exit(1) } @@ -329,7 +329,7 @@ This is similar to 'bd show' but validates that the issue is a gate.`, } } - if issue.IssueType != types.TypeGate { + if issue.IssueType != "gate" { fmt.Fprintf(os.Stderr, "Error: %s is not a gate issue (type=%s)\n", gateID, issue.IssueType) os.Exit(1) } @@ -410,7 +410,7 @@ Use --reason to provide context for why the gate was resolved.`, } } - if issue.IssueType != types.TypeGate { + if issue.IssueType != "gate" { fmt.Fprintf(os.Stderr, "Error: %s is not a gate issue (type=%s)\n", gateID, issue.IssueType) os.Exit(1) } @@ -492,7 +492,7 @@ Examples: limit, _ := cmd.Flags().GetInt("limit") // Get open gates - gateType := types.TypeGate + gateType := types.IssueType("gate") filter := types.IssueFilter{ IssueType: &gateType, ExcludeStatus: []types.Status{types.StatusClosed}, diff --git a/cmd/bd/gate_discover.go b/cmd/bd/gate_discover.go index 571ba973..4bd63ab4 100644 --- a/cmd/bd/gate_discover.go +++ b/cmd/bd/gate_discover.go @@ -228,7 +228,7 @@ func findPendingGates() ([]*types.Issue, error) { } } else { // Direct mode - gateType := types.TypeGate + gateType := types.IssueType("gate") filter := types.IssueFilter{ IssueType: &gateType, ExcludeStatus: []types.Status{types.StatusClosed}, diff --git a/cmd/bd/list.go b/cmd/bd/list.go index 918ed666..1769c4e3 100644 --- a/cmd/bd/list.go +++ b/cmd/bd/list.go @@ -796,7 +796,7 @@ var listCmd = &cobra.Command{ // Gate filtering: exclude gate issues by default (bd-7zka.2) // Use --include-gates or --type gate to show gate issues if !includeGates && issueType != "gate" { - filter.ExcludeTypes = append(filter.ExcludeTypes, types.TypeGate) + filter.ExcludeTypes = append(filter.ExcludeTypes, "gate") } // Parent filtering: filter children by parent issue diff --git a/cmd/bd/mol_ready_gated.go b/cmd/bd/mol_ready_gated.go index deae7468..b4092ef1 100644 --- a/cmd/bd/mol_ready_gated.go +++ b/cmd/bd/mol_ready_gated.go @@ -113,7 +113,7 @@ func runMolReadyGated(cmd *cobra.Command, args []string) { // 5. Filter out molecules that are already hooked by someone func findGateReadyMolecules(ctx context.Context, s storage.Storage) ([]*GatedMolecule, error) { // Step 1: Find all closed gate beads - gateType := types.TypeGate + gateType := types.IssueType("gate") closedStatus := types.StatusClosed gateFilter := types.IssueFilter{ IssueType: &gateType, diff --git a/cmd/bd/mol_ready_gated_test.go b/cmd/bd/mol_ready_gated_test.go index 608f7dfc..7b6a8a27 100644 --- a/cmd/bd/mol_ready_gated_test.go +++ b/cmd/bd/mol_ready_gated_test.go @@ -127,7 +127,7 @@ func TestFindGateReadyMolecules_ClosedGate(t *testing.T) { gate := &types.Issue{ ID: "test-mol-002.gate-await-ci", Title: "Gate: gh:run ci-workflow", - IssueType: types.TypeGate, + IssueType: "gate", Status: types.StatusClosed, // Gate has closed AwaitType: "gh:run", AwaitID: "ci-workflow", @@ -222,7 +222,7 @@ func TestFindGateReadyMolecules_OpenGate(t *testing.T) { gate := &types.Issue{ ID: "test-mol-003.gate-await-ci", Title: "Gate: gh:run ci-workflow", - IssueType: types.TypeGate, + IssueType: "gate", Status: types.StatusOpen, // Gate is still open AwaitType: "gh:run", AwaitID: "ci-workflow", @@ -302,7 +302,7 @@ func TestFindGateReadyMolecules_HookedMolecule(t *testing.T) { gate := &types.Issue{ ID: "test-mol-004.gate-await-ci", Title: "Gate: gh:run ci-workflow", - IssueType: types.TypeGate, + IssueType: "gate", Status: types.StatusClosed, AwaitType: "gh:run", AwaitID: "ci-workflow", @@ -384,7 +384,7 @@ func TestFindGateReadyMolecules_MultipleGates(t *testing.T) { gate := &types.Issue{ ID: fmt.Sprintf("%s.gate", molID), Title: "Gate: gh:run", - IssueType: types.TypeGate, + IssueType: "gate", Status: types.StatusClosed, AwaitType: "gh:run", CreatedAt: time.Now(), diff --git a/cmd/bd/show_unit_helpers_test.go b/cmd/bd/show_unit_helpers_test.go index 916e8d62..913b4831 100644 --- a/cmd/bd/show_unit_helpers_test.go +++ b/cmd/bd/show_unit_helpers_test.go @@ -105,9 +105,9 @@ func TestFindRepliesToAndReplies_WorksWithMemoryStorage(t *testing.T) { 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"} - reply2 := &types.Issue{Title: "r2", Status: types.StatusOpen, Priority: 2, IssueType: types.TypeMessage, Sender: "a", Assignee: "b"} + root := &types.Issue{Title: "root", Status: types.StatusOpen, Priority: 2, IssueType: "message", Sender: "a", Assignee: "b"} + reply1 := &types.Issue{Title: "r1", Status: types.StatusOpen, Priority: 2, IssueType: "message", Sender: "b", Assignee: "a"} + reply2 := &types.Issue{Title: "r2", Status: types.StatusOpen, Priority: 2, IssueType: "message", Sender: "a", Assignee: "b"} if err := st.CreateIssue(ctx, root, "tester"); err != nil { t.Fatalf("CreateIssue(root): %v", err) } diff --git a/cmd/bd/swarm.go b/cmd/bd/swarm.go index 6ec3d5dd..d0be69a9 100644 --- a/cmd/bd/swarm.go +++ b/cmd/bd/swarm.go @@ -77,7 +77,7 @@ func findExistingSwarm(ctx context.Context, s SwarmStorage, epicID string) (*typ // Find a swarm molecule with relates-to dependency to this epic for _, dep := range dependents { // Only consider molecules (GetDependents doesn't populate mol_type, so we fetch full issue) - if dep.IssueType != types.TypeMolecule { + if dep.IssueType != "molecule" { continue } @@ -187,7 +187,7 @@ Examples: } // Verify it's an epic - if epic.IssueType != types.TypeEpic && epic.IssueType != types.TypeMolecule { + if epic.IssueType != types.TypeEpic && epic.IssueType != "molecule" { FatalErrorRespectJSON("'%s' is not an epic or molecule (type: %s)", epicID, epic.IssueType) } @@ -655,7 +655,7 @@ Examples: var epic *types.Issue // Check if it's a swarm molecule - if so, follow the link to the epic - if issue.IssueType == types.TypeMolecule && issue.MolType == types.MolTypeSwarm { + if issue.IssueType == "molecule" && issue.MolType == types.MolTypeSwarm { // Find linked epic via relates-to dependency deps, err := store.GetDependencyRecords(ctx, issue.ID) if err != nil { @@ -673,7 +673,7 @@ Examples: if epic == nil { FatalErrorRespectJSON("swarm molecule '%s' has no linked epic", issueID) } - } else if issue.IssueType == types.TypeEpic || issue.IssueType == types.TypeMolecule { + } else if issue.IssueType == types.TypeEpic || issue.IssueType == "molecule" { epic = issue } else { FatalErrorRespectJSON("'%s' is not an epic or swarm molecule (type: %s)", issueID, issue.IssueType) @@ -950,7 +950,7 @@ Examples: var epicTitle string // Check if it's an epic or single issue that needs wrapping - if issue.IssueType == types.TypeEpic || issue.IssueType == types.TypeMolecule { + if issue.IssueType == types.TypeEpic || issue.IssueType == "molecule" { epicID = issue.ID epicTitle = issue.Title } else { @@ -1042,7 +1042,7 @@ Examples: Description: fmt.Sprintf("Swarm molecule orchestrating epic %s.\n\nEpic: %s\nCoordinator: %s", epicID, epicID, coordinator), Status: types.StatusOpen, Priority: epic.Priority, - IssueType: types.TypeMolecule, + IssueType: "molecule", MolType: types.MolTypeSwarm, Assignee: coordinator, CreatedBy: actor, diff --git a/cmd/bd/thread_test.go b/cmd/bd/thread_test.go index 9b2881fc..b7f5b751 100644 --- a/cmd/bd/thread_test.go +++ b/cmd/bd/thread_test.go @@ -24,7 +24,7 @@ func TestThreadTraversal(t *testing.T) { Description: "This is the original message", Status: types.StatusOpen, Priority: 2, - IssueType: types.TypeMessage, + IssueType: "message", Assignee: "worker", Sender: "manager", Ephemeral: true, @@ -40,7 +40,7 @@ func TestThreadTraversal(t *testing.T) { Description: "This is reply 1", Status: types.StatusOpen, Priority: 2, - IssueType: types.TypeMessage, + IssueType: "message", Assignee: "manager", Sender: "worker", Ephemeral: true, @@ -56,7 +56,7 @@ func TestThreadTraversal(t *testing.T) { Description: "This is reply 2", Status: types.StatusOpen, Priority: 2, - IssueType: types.TypeMessage, + IssueType: "message", Assignee: "worker", Sender: "manager", Ephemeral: true, @@ -187,7 +187,7 @@ func TestThreadTraversalEmptyThread(t *testing.T) { Description: "This message has no thread", Status: types.StatusOpen, Priority: 2, - IssueType: types.TypeMessage, + IssueType: "message", Assignee: "user", Sender: "sender", Ephemeral: true, @@ -225,7 +225,7 @@ func TestThreadTraversalBranching(t *testing.T) { Description: "This message will have multiple replies", Status: types.StatusOpen, Priority: 2, - IssueType: types.TypeMessage, + IssueType: "message", Assignee: "user", Sender: "sender", Ephemeral: true, @@ -242,7 +242,7 @@ func TestThreadTraversalBranching(t *testing.T) { Description: "First branch reply", Status: types.StatusOpen, Priority: 2, - IssueType: types.TypeMessage, + IssueType: "message", Assignee: "sender", Sender: "user", Ephemeral: true, @@ -258,7 +258,7 @@ func TestThreadTraversalBranching(t *testing.T) { Description: "Second branch reply", Status: types.StatusOpen, Priority: 2, - IssueType: types.TypeMessage, + IssueType: "message", Assignee: "sender", Sender: "another-user", Ephemeral: true, @@ -361,7 +361,7 @@ func TestThreadTraversalOnlyRepliesTo(t *testing.T) { Description: "First message (target of blocks dep)", Status: types.StatusOpen, Priority: 2, - IssueType: types.TypeMessage, + IssueType: "message", Assignee: "user", Sender: "sender", Ephemeral: true, @@ -377,7 +377,7 @@ func TestThreadTraversalOnlyRepliesTo(t *testing.T) { Description: "Second message with blocks dependency to msg1", Status: types.StatusOpen, Priority: 2, - IssueType: types.TypeMessage, + IssueType: "message", Assignee: "user", Sender: "sender", Ephemeral: true, diff --git a/cmd/bd/types.go b/cmd/bd/types.go index 69bc98e6..171bd431 100644 --- a/cmd/bd/types.go +++ b/cmd/bd/types.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "os" - "strings" "github.com/spf13/cobra" "github.com/steveyegge/beads/internal/types" @@ -22,24 +21,6 @@ var coreWorkTypes = []struct { {types.TypeEpic, "Large body of work spanning multiple issues"}, } -// 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)"}, - {types.TypeAgent, "Agent identity bead"}, - {types.TypeRole, "Agent role definition"}, - {types.TypeRig, "Rig identity bead (multi-repo workspace)"}, - {types.TypeEvent, "Operational state change record"}, - {types.TypeMessage, "Ephemeral communication between workers"}, -} - var typesCmd = &cobra.Command{ Use: "types", GroupID: "views", @@ -96,34 +77,12 @@ Examples: 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) - } + fmt.Printf(" %s\n", t) } } else { fmt.Println("\nNo custom types configured.") fmt.Println("Configure with: bd config set types.custom \"type1,type2,...\"") } - - // 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, ", ")) - } }, } diff --git a/internal/beads/beads.go b/internal/beads/beads.go index 65e84e5e..82061586 100644 --- a/internal/beads/beads.go +++ b/internal/beads/beads.go @@ -342,14 +342,13 @@ const ( StatusClosed = types.StatusClosed ) -// IssueType constants +// IssueType constants (core types only - Gas Town types removed) const ( - TypeBug = types.TypeBug - TypeFeature = types.TypeFeature - TypeTask = types.TypeTask - TypeEpic = types.TypeEpic - TypeChore = types.TypeChore - TypeMolecule = types.TypeMolecule + TypeBug = types.TypeBug + TypeFeature = types.TypeFeature + TypeTask = types.TypeTask + TypeEpic = types.TypeEpic + TypeChore = types.TypeChore ) // DependencyType constants diff --git a/internal/molecules/molecules_test.go b/internal/molecules/molecules_test.go index 891ebca2..9e358235 100644 --- a/internal/molecules/molecules_test.go +++ b/internal/molecules/molecules_test.go @@ -164,7 +164,7 @@ func TestLoader_SkipExistingMolecules(t *testing.T) { existingMol := &types.Issue{ ID: "mol-existing", Title: "Existing Molecule", - IssueType: types.TypeMolecule, + IssueType: "molecule", Status: types.StatusOpen, IsTemplate: true, } diff --git a/internal/rpc/client_gate_shutdown_test.go b/internal/rpc/client_gate_shutdown_test.go index 66cacfe5..37d75a60 100644 --- a/internal/rpc/client_gate_shutdown_test.go +++ b/internal/rpc/client_gate_shutdown_test.go @@ -51,7 +51,7 @@ func TestClient_GateLifecycleAndShutdown(t *testing.T) { if err := json.Unmarshal(showResp.Data, &gate); err != nil { t.Fatalf("unmarshal GateShow: %v", err) } - if gate.ID != created.ID || gate.IssueType != types.TypeGate { + if gate.ID != created.ID || gate.IssueType != "gate" { t.Fatalf("unexpected gate: %+v", gate) } diff --git a/internal/rpc/server_issues_epics.go b/internal/rpc/server_issues_epics.go index 546d32bb..05fffd80 100644 --- a/internal/rpc/server_issues_epics.go +++ b/internal/rpc/server_issues_epics.go @@ -2053,7 +2053,7 @@ func (s *Server) handleGateCreate(req *Request) Response { // Create gate issue gate := &types.Issue{ Title: args.Title, - IssueType: types.TypeGate, + IssueType: "gate", Status: types.StatusOpen, Priority: 1, // Gates are typically high priority Assignee: "deacon/", @@ -2104,7 +2104,7 @@ func (s *Server) handleGateList(req *Request) Response { ctx := s.reqCtx(req) // Build filter for gates - gateType := types.TypeGate + gateType := types.IssueType("gate") filter := types.IssueFilter{ IssueType: &gateType, } @@ -2169,7 +2169,7 @@ func (s *Server) handleGateShow(req *Request) Response { Error: fmt.Sprintf("gate %s not found", gateID), } } - if gate.IssueType != types.TypeGate { + if gate.IssueType != "gate" { return Response{ Success: false, Error: fmt.Sprintf("%s is not a gate (type: %s)", gateID, gate.IssueType), @@ -2225,7 +2225,7 @@ func (s *Server) handleGateClose(req *Request) Response { Error: fmt.Sprintf("gate %s not found", gateID), } } - if gate.IssueType != types.TypeGate { + if gate.IssueType != "gate" { return Response{ Success: false, Error: fmt.Sprintf("%s is not a gate (type: %s)", gateID, gate.IssueType), @@ -2304,7 +2304,7 @@ func (s *Server) handleGateWait(req *Request) Response { Error: fmt.Sprintf("gate %s not found", gateID), } } - if gate.IssueType != types.TypeGate { + if gate.IssueType != "gate" { return Response{ Success: false, Error: fmt.Sprintf("%s is not a gate (type: %s)", gateID, gate.IssueType), diff --git a/internal/storage/memory/memory.go b/internal/storage/memory/memory.go index fe14fcbf..96905dfd 100644 --- a/internal/storage/memory/memory.go +++ b/internal/storage/memory/memory.go @@ -1085,8 +1085,9 @@ func (m *MemoryStorage) GetReadyWork(ctx context.Context, filter types.WorkFilte } else { // Exclude workflow types from ready work by default // These are internal workflow items, not work for polecats to claim + // (Gas Town types - not built into beads core) switch issue.IssueType { - case types.TypeMergeRequest, types.TypeGate, types.TypeMolecule, types.TypeMessage: + case "merge-request", "gate", "molecule", "message": continue } } diff --git a/internal/storage/memory/memory_more_coverage_test.go b/internal/storage/memory/memory_more_coverage_test.go index fd300b81..cb817c0d 100644 --- a/internal/storage/memory/memory_more_coverage_test.go +++ b/internal/storage/memory/memory_more_coverage_test.go @@ -594,7 +594,7 @@ func TestMemoryStorage_UpdateIssue_SearchIssues_ReadyWork_BlockedIssues(t *testi child := &types.Issue{ID: "bd-2", Title: "Child", Status: types.StatusOpen, Priority: 2, IssueType: types.TypeTask, Assignee: assignee} blocker := &types.Issue{ID: "bd-3", Title: "Blocker", Status: types.StatusOpen, Priority: 3, IssueType: types.TypeTask} pinned := &types.Issue{ID: "bd-4", Title: "Pinned", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, Pinned: true} - workflow := &types.Issue{ID: "bd-5", Title: "Workflow", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeMergeRequest} + workflow := &types.Issue{ID: "bd-5", Title: "Workflow", Status: types.StatusOpen, Priority: 1, IssueType: "merge-request"} for _, iss := range []*types.Issue{parent, child, blocker, pinned, workflow} { if err := store.CreateIssue(ctx, iss, "actor"); err != nil { t.Fatalf("CreateIssue %s: %v", iss.ID, err) @@ -720,7 +720,7 @@ func TestMemoryStorage_UpdateIssue_SearchIssues_ReadyWork_BlockedIssues(t *testi } // Filter by workflow type explicitly. - ready, err = store.GetReadyWork(ctx, types.WorkFilter{Type: string(types.TypeMergeRequest)}) + ready, err = store.GetReadyWork(ctx, types.WorkFilter{Type: "merge-request"}) if err != nil { t.Fatalf("GetReadyWork type: %v", err) } diff --git a/internal/storage/sqlite/gate_no_daemon_test.go b/internal/storage/sqlite/gate_no_daemon_test.go index 8c828931..86f19e97 100644 --- a/internal/storage/sqlite/gate_no_daemon_test.go +++ b/internal/storage/sqlite/gate_no_daemon_test.go @@ -39,7 +39,7 @@ func TestGateFieldsPreservedAcrossConnections(t *testing.T) { Title: "Test Gate", Status: types.StatusOpen, Priority: 1, - IssueType: types.TypeGate, + IssueType: "gate", Ephemeral: true, AwaitType: "timer", AwaitID: "5s", diff --git a/internal/storage/sqlite/graph_links_test.go b/internal/storage/sqlite/graph_links_test.go index d3c42dab..263007e6 100644 --- a/internal/storage/sqlite/graph_links_test.go +++ b/internal/storage/sqlite/graph_links_test.go @@ -292,7 +292,7 @@ func TestRepliesTo(t *testing.T) { Description: "Original content", Status: types.StatusOpen, Priority: 2, - IssueType: types.TypeMessage, + IssueType: "message", Sender: "alice", Assignee: "bob", Ephemeral: true, @@ -304,7 +304,7 @@ func TestRepliesTo(t *testing.T) { Description: "Reply content", Status: types.StatusOpen, Priority: 2, - IssueType: types.TypeMessage, + IssueType: "message", Sender: "bob", Assignee: "alice", Ephemeral: true, @@ -360,7 +360,7 @@ func TestRepliesTo_Chain(t *testing.T) { Title: "Message", Status: types.StatusOpen, Priority: 2, - IssueType: types.TypeMessage, + IssueType: "message", Sender: "user", Assignee: "inbox", Ephemeral: true, @@ -414,7 +414,7 @@ func TestWispField(t *testing.T) { Title: "Wisp Issue", Status: types.StatusOpen, Priority: 2, - IssueType: types.TypeMessage, + IssueType: "message", Ephemeral: true, CreatedAt: time.Now(), UpdatedAt: time.Now(), @@ -467,7 +467,7 @@ func TestWispFilter(t *testing.T) { Title: "Wisp", Status: types.StatusClosed, // Closed for cleanup test Priority: 2, - IssueType: types.TypeMessage, + IssueType: "message", Ephemeral: true, CreatedAt: time.Now(), UpdatedAt: time.Now(), @@ -534,7 +534,7 @@ func TestSenderField(t *testing.T) { Title: "Message", Status: types.StatusOpen, Priority: 2, - IssueType: types.TypeMessage, + IssueType: "message", Sender: "alice@example.com", Assignee: "bob@example.com", CreatedAt: time.Now(), @@ -565,7 +565,7 @@ func TestMessageType(t *testing.T) { Title: "Test Message", Status: types.StatusOpen, Priority: 2, - IssueType: types.TypeMessage, + IssueType: "message", CreatedAt: time.Now(), UpdatedAt: time.Now(), } @@ -579,12 +579,12 @@ func TestMessageType(t *testing.T) { if err != nil { t.Fatalf("GetIssue failed: %v", err) } - if saved.IssueType != types.TypeMessage { - t.Errorf("IssueType = %q, want %q", saved.IssueType, types.TypeMessage) + if saved.IssueType != "message" { + t.Errorf("IssueType = %q, want %q", saved.IssueType, "message") } // Filter by message type - messageType := types.TypeMessage + messageType := types.IssueType("message") filter := types.IssueFilter{ IssueType: &messageType, } diff --git a/internal/storage/sqlite/multirepo_test.go b/internal/storage/sqlite/multirepo_test.go index 95db2640..a532e089 100644 --- a/internal/storage/sqlite/multirepo_test.go +++ b/internal/storage/sqlite/multirepo_test.go @@ -1128,7 +1128,7 @@ func TestUpsertPreservesGateFields(t *testing.T) { Title: "Test Gate", Status: types.StatusOpen, Priority: 1, - IssueType: types.TypeGate, + IssueType: "gate", Ephemeral: true, AwaitType: "gh:run", AwaitID: "123456789", @@ -1170,7 +1170,7 @@ func TestUpsertPreservesGateFields(t *testing.T) { Title: "Test Gate Updated", // Different title to trigger update Status: types.StatusOpen, Priority: 1, - IssueType: types.TypeGate, + IssueType: "gate", AwaitType: "", // Empty - simulating JSONL without await fields AwaitID: "", // Empty Timeout: 0, diff --git a/internal/storage/sqlite/transaction_test.go b/internal/storage/sqlite/transaction_test.go index 1c2d1959..f18cfc0c 100644 --- a/internal/storage/sqlite/transaction_test.go +++ b/internal/storage/sqlite/transaction_test.go @@ -524,14 +524,14 @@ func TestTransactionAddDependency_RepliesTo(t *testing.T) { Title: "Original Message", Status: types.StatusOpen, Priority: 2, - IssueType: types.TypeMessage, + IssueType: "message", Sender: "alice", } reply := &types.Issue{ Title: "Re: Original Message", Status: types.StatusOpen, Priority: 2, - IssueType: types.TypeMessage, + IssueType: "message", Sender: "bob", } if err := store.CreateIssue(ctx, original, "test-actor"); err != nil { diff --git a/internal/types/types.go b/internal/types/types.go index 9c682bd7..ff7c42d0 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -486,21 +486,9 @@ const ( 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 ( - 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 - TypeGate IssueType = "gate" // Async coordination gate - TypeAgent IssueType = "agent" // Agent identity bead - TypeRole IssueType = "role" // Agent role definition - TypeRig IssueType = "rig" // Rig identity bead (multi-repo workspace) - TypeConvoy IssueType = "convoy" // Cross-project tracking with reactive completion - TypeEvent IssueType = "event" // Operational state change record - TypeSlot IssueType = "slot" // Exclusive access slot (merge-slot gate) -) +// Note: Gas Town types (molecule, gate, convoy, merge-request, slot, agent, role, rig, event, message) +// were removed from beads core. They are now purely custom types with no built-in constants. +// Use string literals like types.IssueType("molecule") if needed, and configure types.custom. // IsValid checks if the issue type is a core work type. // Only core work types (bug, feature, task, epic, chore) are built-in. @@ -538,16 +526,12 @@ func (t IssueType) IsValidWithCustom(customTypes []string) bool { } // Normalize maps issue type aliases to their canonical form. -// For example, "enhancement" -> "feature", "mr" -> "merge-request". +// For example, "enhancement" -> "feature". // Case-insensitive to match util.NormalizeIssueType behavior. func (t IssueType) Normalize() IssueType { switch strings.ToLower(string(t)) { case "enhancement", "feat": return TypeFeature - case "mr": - return TypeMergeRequest - case "mol": - return TypeMolecule default: return t } diff --git a/internal/types/types_test.go b/internal/types/types_test.go index ebfcb0b2..622e75d7 100644 --- a/internal/types/types_test.go +++ b/internal/types/types_test.go @@ -442,12 +442,12 @@ func TestValidateForImport(t *testing.T) { wantErr: false, // Should pass - federation trust model }, { - name: "built-in type agent passes", + name: "custom type passes (federation trust)", issue: Issue{ Title: "Test Issue", Status: StatusOpen, Priority: 1, - IssueType: TypeAgent, // Gas Town built-in type + IssueType: IssueType("agent"), // Custom type (no longer built-in) }, wantErr: false, }, @@ -545,17 +545,17 @@ func TestIssueTypeIsValid(t *testing.T) { {TypeTask, true}, {TypeEpic, true}, {TypeChore, 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}, + // Gas Town types are now custom types (not built-in) + {IssueType("message"), false}, + {IssueType("merge-request"), false}, + {IssueType("molecule"), false}, + {IssueType("gate"), false}, + {IssueType("agent"), false}, + {IssueType("role"), false}, + {IssueType("convoy"), false}, + {IssueType("event"), false}, + {IssueType("slot"), false}, + {IssueType("rig"), false}, // Invalid types {IssueType("invalid"), false}, {IssueType(""), false}, @@ -581,12 +581,12 @@ func TestIssueTypeRequiredSections(t *testing.T) { {TypeTask, 1, "## Acceptance Criteria"}, {TypeEpic, 1, "## Success Criteria"}, {TypeChore, 0, ""}, - {TypeMessage, 0, ""}, - {TypeMolecule, 0, ""}, - {TypeGate, 0, ""}, - {TypeEvent, 0, ""}, - {TypeMergeRequest, 0, ""}, - // Gas Town types (agent, role, rig, convoy, slot) have been removed + // Gas Town types are now custom and have no required sections + {IssueType("message"), 0, ""}, + {IssueType("molecule"), 0, ""}, + {IssueType("gate"), 0, ""}, + {IssueType("event"), 0, ""}, + {IssueType("merge-request"), 0, ""}, } for _, tt := range tests { diff --git a/internal/validation/template_test.go b/internal/validation/template_test.go index 73dbab5b..f91e4627 100644 --- a/internal/validation/template_test.go +++ b/internal/validation/template_test.go @@ -123,13 +123,13 @@ Widget displays correctly`, }, { name: "message has no requirements", - issueType: types.TypeMessage, + issueType: "message", description: "Hello", wantErr: false, }, { name: "molecule has no requirements", - issueType: types.TypeMolecule, + issueType: "molecule", description: "", wantErr: false, },