refactor(types): remove Gas Town type constants from beads core (bd-w2zz4)
Remove Gas Town-specific type constants (TypeMolecule, TypeGate, TypeConvoy, TypeMergeRequest, TypeSlot, TypeAgent, TypeRole, TypeRig, TypeEvent, TypeMessage) from internal/types/types.go. Beads now only has core work types built-in: - bug, feature, task, epic, chore All Gas Town types are now purely custom types with no special handling in beads. Use string literals like "gate" or "molecule" when needed, and configure types.custom in config.yaml for validation. Changes: - Remove Gas Town type constants from types.go - Remove mr/mol aliases from Normalize() - Update bd types command to only show core types - Replace all constant usages with string literals throughout codebase - Update tests to use string literals This decouples beads from Gas Town, making it a generic issue tracker. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
+5
-5
@@ -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},
|
||||
|
||||
@@ -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},
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+6
-6
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
+1
-42
@@ -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, ", "))
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user