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()
|
ctx := context.Background()
|
||||||
|
|
||||||
root := &types.Issue{Title: "Root message", 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: types.TypeMessage, Status: types.StatusOpen, Sender: "bob", Assignee: "alice"}
|
reply1 := &types.Issue{Title: "Re: Root", IssueType: "message", Status: types.StatusOpen, Sender: "bob", Assignee: "alice"}
|
||||||
reply2 := &types.Issue{Title: "Re: Re: Root", IssueType: types.TypeMessage, Status: types.StatusOpen, Sender: "alice", Assignee: "bob"}
|
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 {
|
if err := s.CreateIssue(ctx, root, "test-user"); err != nil {
|
||||||
s.Close()
|
s.Close()
|
||||||
t.Fatalf("CreateIssue root: %v", err)
|
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),
|
Description: fmt.Sprintf("Async gate for step %s", step.ID),
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeGate,
|
IssueType: "gate",
|
||||||
AwaitType: step.Gate.Type,
|
AwaitType: step.Gate.Type,
|
||||||
AwaitID: step.Gate.ID,
|
AwaitID: step.Gate.ID,
|
||||||
Timeout: timeout,
|
Timeout: timeout,
|
||||||
|
|||||||
@@ -32,20 +32,14 @@ var obsidianPriority = []string{
|
|||||||
"⏬", // 4 = backlog/lowest
|
"⏬", // 4 = backlog/lowest
|
||||||
}
|
}
|
||||||
|
|
||||||
// obsidianTypeTag maps bd issue type to Obsidian tag
|
// obsidianTypeTag maps bd issue type to Obsidian tag (core types only)
|
||||||
// Note: Gas Town-specific types (agent, role, rig, convoy, slot) are now labels.
|
// Gas Town types are custom types and will use their issue_type value as a tag.
|
||||||
// The labels will be converted to tags automatically via the label->tag logic.
|
|
||||||
var obsidianTypeTag = map[types.IssueType]string{
|
var obsidianTypeTag = map[types.IssueType]string{
|
||||||
types.TypeBug: "#Bug",
|
types.TypeBug: "#Bug",
|
||||||
types.TypeFeature: "#Feature",
|
types.TypeFeature: "#Feature",
|
||||||
types.TypeTask: "#Task",
|
types.TypeTask: "#Task",
|
||||||
types.TypeEpic: "#Epic",
|
types.TypeEpic: "#Epic",
|
||||||
types.TypeChore: "#Chore",
|
types.TypeChore: "#Chore",
|
||||||
types.TypeMessage: "#Message",
|
|
||||||
types.TypeMergeRequest: "#MergeRequest",
|
|
||||||
types.TypeMolecule: "#Molecule",
|
|
||||||
types.TypeGate: "#Gate",
|
|
||||||
types.TypeEvent: "#Event",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// formatObsidianTask converts a single issue to Obsidian Tasks format
|
// 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")
|
limit, _ := cmd.Flags().GetInt("limit")
|
||||||
|
|
||||||
// Build filter for gate type issues
|
// Build filter for gate type issues
|
||||||
gateType := types.TypeGate
|
gateType := types.IssueType("gate")
|
||||||
filter := types.IssueFilter{
|
filter := types.IssueFilter{
|
||||||
IssueType: &gateType,
|
IssueType: &gateType,
|
||||||
Limit: limit,
|
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)
|
fmt.Fprintf(os.Stderr, "Error: %s is not a gate issue (type=%s)\n", gateID, issue.IssueType)
|
||||||
os.Exit(1)
|
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)
|
fmt.Fprintf(os.Stderr, "Error: %s is not a gate issue (type=%s)\n", gateID, issue.IssueType)
|
||||||
os.Exit(1)
|
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)
|
fmt.Fprintf(os.Stderr, "Error: %s is not a gate issue (type=%s)\n", gateID, issue.IssueType)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
@@ -492,7 +492,7 @@ Examples:
|
|||||||
limit, _ := cmd.Flags().GetInt("limit")
|
limit, _ := cmd.Flags().GetInt("limit")
|
||||||
|
|
||||||
// Get open gates
|
// Get open gates
|
||||||
gateType := types.TypeGate
|
gateType := types.IssueType("gate")
|
||||||
filter := types.IssueFilter{
|
filter := types.IssueFilter{
|
||||||
IssueType: &gateType,
|
IssueType: &gateType,
|
||||||
ExcludeStatus: []types.Status{types.StatusClosed},
|
ExcludeStatus: []types.Status{types.StatusClosed},
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ func findPendingGates() ([]*types.Issue, error) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Direct mode
|
// Direct mode
|
||||||
gateType := types.TypeGate
|
gateType := types.IssueType("gate")
|
||||||
filter := types.IssueFilter{
|
filter := types.IssueFilter{
|
||||||
IssueType: &gateType,
|
IssueType: &gateType,
|
||||||
ExcludeStatus: []types.Status{types.StatusClosed},
|
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)
|
// Gate filtering: exclude gate issues by default (bd-7zka.2)
|
||||||
// Use --include-gates or --type gate to show gate issues
|
// Use --include-gates or --type gate to show gate issues
|
||||||
if !includeGates && issueType != "gate" {
|
if !includeGates && issueType != "gate" {
|
||||||
filter.ExcludeTypes = append(filter.ExcludeTypes, types.TypeGate)
|
filter.ExcludeTypes = append(filter.ExcludeTypes, "gate")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parent filtering: filter children by parent issue
|
// 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
|
// 5. Filter out molecules that are already hooked by someone
|
||||||
func findGateReadyMolecules(ctx context.Context, s storage.Storage) ([]*GatedMolecule, error) {
|
func findGateReadyMolecules(ctx context.Context, s storage.Storage) ([]*GatedMolecule, error) {
|
||||||
// Step 1: Find all closed gate beads
|
// Step 1: Find all closed gate beads
|
||||||
gateType := types.TypeGate
|
gateType := types.IssueType("gate")
|
||||||
closedStatus := types.StatusClosed
|
closedStatus := types.StatusClosed
|
||||||
gateFilter := types.IssueFilter{
|
gateFilter := types.IssueFilter{
|
||||||
IssueType: &gateType,
|
IssueType: &gateType,
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ func TestFindGateReadyMolecules_ClosedGate(t *testing.T) {
|
|||||||
gate := &types.Issue{
|
gate := &types.Issue{
|
||||||
ID: "test-mol-002.gate-await-ci",
|
ID: "test-mol-002.gate-await-ci",
|
||||||
Title: "Gate: gh:run ci-workflow",
|
Title: "Gate: gh:run ci-workflow",
|
||||||
IssueType: types.TypeGate,
|
IssueType: "gate",
|
||||||
Status: types.StatusClosed, // Gate has closed
|
Status: types.StatusClosed, // Gate has closed
|
||||||
AwaitType: "gh:run",
|
AwaitType: "gh:run",
|
||||||
AwaitID: "ci-workflow",
|
AwaitID: "ci-workflow",
|
||||||
@@ -222,7 +222,7 @@ func TestFindGateReadyMolecules_OpenGate(t *testing.T) {
|
|||||||
gate := &types.Issue{
|
gate := &types.Issue{
|
||||||
ID: "test-mol-003.gate-await-ci",
|
ID: "test-mol-003.gate-await-ci",
|
||||||
Title: "Gate: gh:run ci-workflow",
|
Title: "Gate: gh:run ci-workflow",
|
||||||
IssueType: types.TypeGate,
|
IssueType: "gate",
|
||||||
Status: types.StatusOpen, // Gate is still open
|
Status: types.StatusOpen, // Gate is still open
|
||||||
AwaitType: "gh:run",
|
AwaitType: "gh:run",
|
||||||
AwaitID: "ci-workflow",
|
AwaitID: "ci-workflow",
|
||||||
@@ -302,7 +302,7 @@ func TestFindGateReadyMolecules_HookedMolecule(t *testing.T) {
|
|||||||
gate := &types.Issue{
|
gate := &types.Issue{
|
||||||
ID: "test-mol-004.gate-await-ci",
|
ID: "test-mol-004.gate-await-ci",
|
||||||
Title: "Gate: gh:run ci-workflow",
|
Title: "Gate: gh:run ci-workflow",
|
||||||
IssueType: types.TypeGate,
|
IssueType: "gate",
|
||||||
Status: types.StatusClosed,
|
Status: types.StatusClosed,
|
||||||
AwaitType: "gh:run",
|
AwaitType: "gh:run",
|
||||||
AwaitID: "ci-workflow",
|
AwaitID: "ci-workflow",
|
||||||
@@ -384,7 +384,7 @@ func TestFindGateReadyMolecules_MultipleGates(t *testing.T) {
|
|||||||
gate := &types.Issue{
|
gate := &types.Issue{
|
||||||
ID: fmt.Sprintf("%s.gate", molID),
|
ID: fmt.Sprintf("%s.gate", molID),
|
||||||
Title: "Gate: gh:run",
|
Title: "Gate: gh:run",
|
||||||
IssueType: types.TypeGate,
|
IssueType: "gate",
|
||||||
Status: types.StatusClosed,
|
Status: types.StatusClosed,
|
||||||
AwaitType: "gh:run",
|
AwaitType: "gh:run",
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
|
|||||||
@@ -105,9 +105,9 @@ func TestFindRepliesToAndReplies_WorksWithMemoryStorage(t *testing.T) {
|
|||||||
t.Fatalf("SetConfig types.custom: %v", err)
|
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: "message", 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: "message", Sender: "b", Assignee: "a"}
|
||||||
reply2 := &types.Issue{Title: "r2", Status: types.StatusOpen, Priority: 2, IssueType: types.TypeMessage, Sender: "a", Assignee: "b"}
|
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 {
|
if err := st.CreateIssue(ctx, root, "tester"); err != nil {
|
||||||
t.Fatalf("CreateIssue(root): %v", err)
|
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
|
// Find a swarm molecule with relates-to dependency to this epic
|
||||||
for _, dep := range dependents {
|
for _, dep := range dependents {
|
||||||
// Only consider molecules (GetDependents doesn't populate mol_type, so we fetch full issue)
|
// Only consider molecules (GetDependents doesn't populate mol_type, so we fetch full issue)
|
||||||
if dep.IssueType != types.TypeMolecule {
|
if dep.IssueType != "molecule" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +187,7 @@ Examples:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify it's an epic
|
// 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)
|
FatalErrorRespectJSON("'%s' is not an epic or molecule (type: %s)", epicID, epic.IssueType)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -655,7 +655,7 @@ Examples:
|
|||||||
var epic *types.Issue
|
var epic *types.Issue
|
||||||
|
|
||||||
// Check if it's a swarm molecule - if so, follow the link to the epic
|
// 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
|
// Find linked epic via relates-to dependency
|
||||||
deps, err := store.GetDependencyRecords(ctx, issue.ID)
|
deps, err := store.GetDependencyRecords(ctx, issue.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -673,7 +673,7 @@ Examples:
|
|||||||
if epic == nil {
|
if epic == nil {
|
||||||
FatalErrorRespectJSON("swarm molecule '%s' has no linked epic", issueID)
|
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
|
epic = issue
|
||||||
} else {
|
} else {
|
||||||
FatalErrorRespectJSON("'%s' is not an epic or swarm molecule (type: %s)", issueID, issue.IssueType)
|
FatalErrorRespectJSON("'%s' is not an epic or swarm molecule (type: %s)", issueID, issue.IssueType)
|
||||||
@@ -950,7 +950,7 @@ Examples:
|
|||||||
var epicTitle string
|
var epicTitle string
|
||||||
|
|
||||||
// Check if it's an epic or single issue that needs wrapping
|
// 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
|
epicID = issue.ID
|
||||||
epicTitle = issue.Title
|
epicTitle = issue.Title
|
||||||
} else {
|
} else {
|
||||||
@@ -1042,7 +1042,7 @@ Examples:
|
|||||||
Description: fmt.Sprintf("Swarm molecule orchestrating epic %s.\n\nEpic: %s\nCoordinator: %s", epicID, epicID, coordinator),
|
Description: fmt.Sprintf("Swarm molecule orchestrating epic %s.\n\nEpic: %s\nCoordinator: %s", epicID, epicID, coordinator),
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: epic.Priority,
|
Priority: epic.Priority,
|
||||||
IssueType: types.TypeMolecule,
|
IssueType: "molecule",
|
||||||
MolType: types.MolTypeSwarm,
|
MolType: types.MolTypeSwarm,
|
||||||
Assignee: coordinator,
|
Assignee: coordinator,
|
||||||
CreatedBy: actor,
|
CreatedBy: actor,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ func TestThreadTraversal(t *testing.T) {
|
|||||||
Description: "This is the original message",
|
Description: "This is the original message",
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeMessage,
|
IssueType: "message",
|
||||||
Assignee: "worker",
|
Assignee: "worker",
|
||||||
Sender: "manager",
|
Sender: "manager",
|
||||||
Ephemeral: true,
|
Ephemeral: true,
|
||||||
@@ -40,7 +40,7 @@ func TestThreadTraversal(t *testing.T) {
|
|||||||
Description: "This is reply 1",
|
Description: "This is reply 1",
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeMessage,
|
IssueType: "message",
|
||||||
Assignee: "manager",
|
Assignee: "manager",
|
||||||
Sender: "worker",
|
Sender: "worker",
|
||||||
Ephemeral: true,
|
Ephemeral: true,
|
||||||
@@ -56,7 +56,7 @@ func TestThreadTraversal(t *testing.T) {
|
|||||||
Description: "This is reply 2",
|
Description: "This is reply 2",
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeMessage,
|
IssueType: "message",
|
||||||
Assignee: "worker",
|
Assignee: "worker",
|
||||||
Sender: "manager",
|
Sender: "manager",
|
||||||
Ephemeral: true,
|
Ephemeral: true,
|
||||||
@@ -187,7 +187,7 @@ func TestThreadTraversalEmptyThread(t *testing.T) {
|
|||||||
Description: "This message has no thread",
|
Description: "This message has no thread",
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeMessage,
|
IssueType: "message",
|
||||||
Assignee: "user",
|
Assignee: "user",
|
||||||
Sender: "sender",
|
Sender: "sender",
|
||||||
Ephemeral: true,
|
Ephemeral: true,
|
||||||
@@ -225,7 +225,7 @@ func TestThreadTraversalBranching(t *testing.T) {
|
|||||||
Description: "This message will have multiple replies",
|
Description: "This message will have multiple replies",
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeMessage,
|
IssueType: "message",
|
||||||
Assignee: "user",
|
Assignee: "user",
|
||||||
Sender: "sender",
|
Sender: "sender",
|
||||||
Ephemeral: true,
|
Ephemeral: true,
|
||||||
@@ -242,7 +242,7 @@ func TestThreadTraversalBranching(t *testing.T) {
|
|||||||
Description: "First branch reply",
|
Description: "First branch reply",
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeMessage,
|
IssueType: "message",
|
||||||
Assignee: "sender",
|
Assignee: "sender",
|
||||||
Sender: "user",
|
Sender: "user",
|
||||||
Ephemeral: true,
|
Ephemeral: true,
|
||||||
@@ -258,7 +258,7 @@ func TestThreadTraversalBranching(t *testing.T) {
|
|||||||
Description: "Second branch reply",
|
Description: "Second branch reply",
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeMessage,
|
IssueType: "message",
|
||||||
Assignee: "sender",
|
Assignee: "sender",
|
||||||
Sender: "another-user",
|
Sender: "another-user",
|
||||||
Ephemeral: true,
|
Ephemeral: true,
|
||||||
@@ -361,7 +361,7 @@ func TestThreadTraversalOnlyRepliesTo(t *testing.T) {
|
|||||||
Description: "First message (target of blocks dep)",
|
Description: "First message (target of blocks dep)",
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeMessage,
|
IssueType: "message",
|
||||||
Assignee: "user",
|
Assignee: "user",
|
||||||
Sender: "sender",
|
Sender: "sender",
|
||||||
Ephemeral: true,
|
Ephemeral: true,
|
||||||
@@ -377,7 +377,7 @@ func TestThreadTraversalOnlyRepliesTo(t *testing.T) {
|
|||||||
Description: "Second message with blocks dependency to msg1",
|
Description: "Second message with blocks dependency to msg1",
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeMessage,
|
IssueType: "message",
|
||||||
Assignee: "user",
|
Assignee: "user",
|
||||||
Sender: "sender",
|
Sender: "sender",
|
||||||
Ephemeral: true,
|
Ephemeral: true,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/steveyegge/beads/internal/types"
|
"github.com/steveyegge/beads/internal/types"
|
||||||
@@ -22,24 +21,6 @@ var coreWorkTypes = []struct {
|
|||||||
{types.TypeEpic, "Large body of work spanning multiple issues"},
|
{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{
|
var typesCmd = &cobra.Command{
|
||||||
Use: "types",
|
Use: "types",
|
||||||
GroupID: "views",
|
GroupID: "views",
|
||||||
@@ -96,34 +77,12 @@ Examples:
|
|||||||
if len(customTypes) > 0 {
|
if len(customTypes) > 0 {
|
||||||
fmt.Println("\nConfigured custom types:")
|
fmt.Println("\nConfigured custom types:")
|
||||||
for _, t := range customTypes {
|
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 {
|
} else {
|
||||||
fmt.Println("\nNo custom types configured.")
|
fmt.Println("\nNo custom types configured.")
|
||||||
fmt.Println("Configure with: bd config set types.custom \"type1,type2,...\"")
|
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, ", "))
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -342,14 +342,13 @@ const (
|
|||||||
StatusClosed = types.StatusClosed
|
StatusClosed = types.StatusClosed
|
||||||
)
|
)
|
||||||
|
|
||||||
// IssueType constants
|
// IssueType constants (core types only - Gas Town types removed)
|
||||||
const (
|
const (
|
||||||
TypeBug = types.TypeBug
|
TypeBug = types.TypeBug
|
||||||
TypeFeature = types.TypeFeature
|
TypeFeature = types.TypeFeature
|
||||||
TypeTask = types.TypeTask
|
TypeTask = types.TypeTask
|
||||||
TypeEpic = types.TypeEpic
|
TypeEpic = types.TypeEpic
|
||||||
TypeChore = types.TypeChore
|
TypeChore = types.TypeChore
|
||||||
TypeMolecule = types.TypeMolecule
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DependencyType constants
|
// DependencyType constants
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ func TestLoader_SkipExistingMolecules(t *testing.T) {
|
|||||||
existingMol := &types.Issue{
|
existingMol := &types.Issue{
|
||||||
ID: "mol-existing",
|
ID: "mol-existing",
|
||||||
Title: "Existing Molecule",
|
Title: "Existing Molecule",
|
||||||
IssueType: types.TypeMolecule,
|
IssueType: "molecule",
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
IsTemplate: true,
|
IsTemplate: true,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ func TestClient_GateLifecycleAndShutdown(t *testing.T) {
|
|||||||
if err := json.Unmarshal(showResp.Data, &gate); err != nil {
|
if err := json.Unmarshal(showResp.Data, &gate); err != nil {
|
||||||
t.Fatalf("unmarshal GateShow: %v", err)
|
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)
|
t.Fatalf("unexpected gate: %+v", gate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2053,7 +2053,7 @@ func (s *Server) handleGateCreate(req *Request) Response {
|
|||||||
// Create gate issue
|
// Create gate issue
|
||||||
gate := &types.Issue{
|
gate := &types.Issue{
|
||||||
Title: args.Title,
|
Title: args.Title,
|
||||||
IssueType: types.TypeGate,
|
IssueType: "gate",
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 1, // Gates are typically high priority
|
Priority: 1, // Gates are typically high priority
|
||||||
Assignee: "deacon/",
|
Assignee: "deacon/",
|
||||||
@@ -2104,7 +2104,7 @@ func (s *Server) handleGateList(req *Request) Response {
|
|||||||
ctx := s.reqCtx(req)
|
ctx := s.reqCtx(req)
|
||||||
|
|
||||||
// Build filter for gates
|
// Build filter for gates
|
||||||
gateType := types.TypeGate
|
gateType := types.IssueType("gate")
|
||||||
filter := types.IssueFilter{
|
filter := types.IssueFilter{
|
||||||
IssueType: &gateType,
|
IssueType: &gateType,
|
||||||
}
|
}
|
||||||
@@ -2169,7 +2169,7 @@ func (s *Server) handleGateShow(req *Request) Response {
|
|||||||
Error: fmt.Sprintf("gate %s not found", gateID),
|
Error: fmt.Sprintf("gate %s not found", gateID),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if gate.IssueType != types.TypeGate {
|
if gate.IssueType != "gate" {
|
||||||
return Response{
|
return Response{
|
||||||
Success: false,
|
Success: false,
|
||||||
Error: fmt.Sprintf("%s is not a gate (type: %s)", gateID, gate.IssueType),
|
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),
|
Error: fmt.Sprintf("gate %s not found", gateID),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if gate.IssueType != types.TypeGate {
|
if gate.IssueType != "gate" {
|
||||||
return Response{
|
return Response{
|
||||||
Success: false,
|
Success: false,
|
||||||
Error: fmt.Sprintf("%s is not a gate (type: %s)", gateID, gate.IssueType),
|
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),
|
Error: fmt.Sprintf("gate %s not found", gateID),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if gate.IssueType != types.TypeGate {
|
if gate.IssueType != "gate" {
|
||||||
return Response{
|
return Response{
|
||||||
Success: false,
|
Success: false,
|
||||||
Error: fmt.Sprintf("%s is not a gate (type: %s)", gateID, gate.IssueType),
|
Error: fmt.Sprintf("%s is not a gate (type: %s)", gateID, gate.IssueType),
|
||||||
|
|||||||
@@ -1085,8 +1085,9 @@ func (m *MemoryStorage) GetReadyWork(ctx context.Context, filter types.WorkFilte
|
|||||||
} else {
|
} else {
|
||||||
// Exclude workflow types from ready work by default
|
// Exclude workflow types from ready work by default
|
||||||
// These are internal workflow items, not work for polecats to claim
|
// These are internal workflow items, not work for polecats to claim
|
||||||
|
// (Gas Town types - not built into beads core)
|
||||||
switch issue.IssueType {
|
switch issue.IssueType {
|
||||||
case types.TypeMergeRequest, types.TypeGate, types.TypeMolecule, types.TypeMessage:
|
case "merge-request", "gate", "molecule", "message":
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}
|
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}
|
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}
|
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} {
|
for _, iss := range []*types.Issue{parent, child, blocker, pinned, workflow} {
|
||||||
if err := store.CreateIssue(ctx, iss, "actor"); err != nil {
|
if err := store.CreateIssue(ctx, iss, "actor"); err != nil {
|
||||||
t.Fatalf("CreateIssue %s: %v", iss.ID, err)
|
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.
|
// 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 {
|
if err != nil {
|
||||||
t.Fatalf("GetReadyWork type: %v", err)
|
t.Fatalf("GetReadyWork type: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func TestGateFieldsPreservedAcrossConnections(t *testing.T) {
|
|||||||
Title: "Test Gate",
|
Title: "Test Gate",
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 1,
|
Priority: 1,
|
||||||
IssueType: types.TypeGate,
|
IssueType: "gate",
|
||||||
Ephemeral: true,
|
Ephemeral: true,
|
||||||
AwaitType: "timer",
|
AwaitType: "timer",
|
||||||
AwaitID: "5s",
|
AwaitID: "5s",
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ func TestRepliesTo(t *testing.T) {
|
|||||||
Description: "Original content",
|
Description: "Original content",
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeMessage,
|
IssueType: "message",
|
||||||
Sender: "alice",
|
Sender: "alice",
|
||||||
Assignee: "bob",
|
Assignee: "bob",
|
||||||
Ephemeral: true,
|
Ephemeral: true,
|
||||||
@@ -304,7 +304,7 @@ func TestRepliesTo(t *testing.T) {
|
|||||||
Description: "Reply content",
|
Description: "Reply content",
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeMessage,
|
IssueType: "message",
|
||||||
Sender: "bob",
|
Sender: "bob",
|
||||||
Assignee: "alice",
|
Assignee: "alice",
|
||||||
Ephemeral: true,
|
Ephemeral: true,
|
||||||
@@ -360,7 +360,7 @@ func TestRepliesTo_Chain(t *testing.T) {
|
|||||||
Title: "Message",
|
Title: "Message",
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeMessage,
|
IssueType: "message",
|
||||||
Sender: "user",
|
Sender: "user",
|
||||||
Assignee: "inbox",
|
Assignee: "inbox",
|
||||||
Ephemeral: true,
|
Ephemeral: true,
|
||||||
@@ -414,7 +414,7 @@ func TestWispField(t *testing.T) {
|
|||||||
Title: "Wisp Issue",
|
Title: "Wisp Issue",
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeMessage,
|
IssueType: "message",
|
||||||
Ephemeral: true,
|
Ephemeral: true,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
@@ -467,7 +467,7 @@ func TestWispFilter(t *testing.T) {
|
|||||||
Title: "Wisp",
|
Title: "Wisp",
|
||||||
Status: types.StatusClosed, // Closed for cleanup test
|
Status: types.StatusClosed, // Closed for cleanup test
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeMessage,
|
IssueType: "message",
|
||||||
Ephemeral: true,
|
Ephemeral: true,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
@@ -534,7 +534,7 @@ func TestSenderField(t *testing.T) {
|
|||||||
Title: "Message",
|
Title: "Message",
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeMessage,
|
IssueType: "message",
|
||||||
Sender: "alice@example.com",
|
Sender: "alice@example.com",
|
||||||
Assignee: "bob@example.com",
|
Assignee: "bob@example.com",
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
@@ -565,7 +565,7 @@ func TestMessageType(t *testing.T) {
|
|||||||
Title: "Test Message",
|
Title: "Test Message",
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeMessage,
|
IssueType: "message",
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
@@ -579,12 +579,12 @@ func TestMessageType(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("GetIssue failed: %v", err)
|
t.Fatalf("GetIssue failed: %v", err)
|
||||||
}
|
}
|
||||||
if saved.IssueType != types.TypeMessage {
|
if saved.IssueType != "message" {
|
||||||
t.Errorf("IssueType = %q, want %q", saved.IssueType, types.TypeMessage)
|
t.Errorf("IssueType = %q, want %q", saved.IssueType, "message")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter by message type
|
// Filter by message type
|
||||||
messageType := types.TypeMessage
|
messageType := types.IssueType("message")
|
||||||
filter := types.IssueFilter{
|
filter := types.IssueFilter{
|
||||||
IssueType: &messageType,
|
IssueType: &messageType,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1128,7 +1128,7 @@ func TestUpsertPreservesGateFields(t *testing.T) {
|
|||||||
Title: "Test Gate",
|
Title: "Test Gate",
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 1,
|
Priority: 1,
|
||||||
IssueType: types.TypeGate,
|
IssueType: "gate",
|
||||||
Ephemeral: true,
|
Ephemeral: true,
|
||||||
AwaitType: "gh:run",
|
AwaitType: "gh:run",
|
||||||
AwaitID: "123456789",
|
AwaitID: "123456789",
|
||||||
@@ -1170,7 +1170,7 @@ func TestUpsertPreservesGateFields(t *testing.T) {
|
|||||||
Title: "Test Gate Updated", // Different title to trigger update
|
Title: "Test Gate Updated", // Different title to trigger update
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 1,
|
Priority: 1,
|
||||||
IssueType: types.TypeGate,
|
IssueType: "gate",
|
||||||
AwaitType: "", // Empty - simulating JSONL without await fields
|
AwaitType: "", // Empty - simulating JSONL without await fields
|
||||||
AwaitID: "", // Empty
|
AwaitID: "", // Empty
|
||||||
Timeout: 0,
|
Timeout: 0,
|
||||||
|
|||||||
@@ -524,14 +524,14 @@ func TestTransactionAddDependency_RepliesTo(t *testing.T) {
|
|||||||
Title: "Original Message",
|
Title: "Original Message",
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeMessage,
|
IssueType: "message",
|
||||||
Sender: "alice",
|
Sender: "alice",
|
||||||
}
|
}
|
||||||
reply := &types.Issue{
|
reply := &types.Issue{
|
||||||
Title: "Re: Original Message",
|
Title: "Re: Original Message",
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeMessage,
|
IssueType: "message",
|
||||||
Sender: "bob",
|
Sender: "bob",
|
||||||
}
|
}
|
||||||
if err := store.CreateIssue(ctx, original, "test-actor"); err != nil {
|
if err := store.CreateIssue(ctx, original, "test-actor"); err != nil {
|
||||||
|
|||||||
+4
-20
@@ -486,21 +486,9 @@ const (
|
|||||||
TypeChore IssueType = "chore"
|
TypeChore IssueType = "chore"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Well-known custom types - constants for code convenience.
|
// Note: Gas Town types (molecule, gate, convoy, merge-request, slot, agent, role, rig, event, message)
|
||||||
// These are NOT built-in types and require types.custom configuration for validation.
|
// were removed from beads core. They are now purely custom types with no built-in constants.
|
||||||
// Used by Gas Town and other infrastructure that extends beads.
|
// Use string literals like types.IssueType("molecule") if needed, and configure types.custom.
|
||||||
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)
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsValid checks if the issue type is a core work type.
|
// IsValid checks if the issue type is a core work type.
|
||||||
// Only core work types (bug, feature, task, epic, chore) are built-in.
|
// 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.
|
// 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.
|
// Case-insensitive to match util.NormalizeIssueType behavior.
|
||||||
func (t IssueType) Normalize() IssueType {
|
func (t IssueType) Normalize() IssueType {
|
||||||
switch strings.ToLower(string(t)) {
|
switch strings.ToLower(string(t)) {
|
||||||
case "enhancement", "feat":
|
case "enhancement", "feat":
|
||||||
return TypeFeature
|
return TypeFeature
|
||||||
case "mr":
|
|
||||||
return TypeMergeRequest
|
|
||||||
case "mol":
|
|
||||||
return TypeMolecule
|
|
||||||
default:
|
default:
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -442,12 +442,12 @@ func TestValidateForImport(t *testing.T) {
|
|||||||
wantErr: false, // Should pass - federation trust model
|
wantErr: false, // Should pass - federation trust model
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "built-in type agent passes",
|
name: "custom type passes (federation trust)",
|
||||||
issue: Issue{
|
issue: Issue{
|
||||||
Title: "Test Issue",
|
Title: "Test Issue",
|
||||||
Status: StatusOpen,
|
Status: StatusOpen,
|
||||||
Priority: 1,
|
Priority: 1,
|
||||||
IssueType: TypeAgent, // Gas Town built-in type
|
IssueType: IssueType("agent"), // Custom type (no longer built-in)
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
@@ -545,17 +545,17 @@ func TestIssueTypeIsValid(t *testing.T) {
|
|||||||
{TypeTask, true},
|
{TypeTask, true},
|
||||||
{TypeEpic, true},
|
{TypeEpic, true},
|
||||||
{TypeChore, true},
|
{TypeChore, true},
|
||||||
// Gas Town types require types.custom configuration
|
// Gas Town types are now custom types (not built-in)
|
||||||
{TypeMessage, false},
|
{IssueType("message"), false},
|
||||||
{TypeMergeRequest, false},
|
{IssueType("merge-request"), false},
|
||||||
{TypeMolecule, false},
|
{IssueType("molecule"), false},
|
||||||
{TypeGate, false},
|
{IssueType("gate"), false},
|
||||||
{TypeAgent, false},
|
{IssueType("agent"), false},
|
||||||
{TypeRole, false},
|
{IssueType("role"), false},
|
||||||
{TypeConvoy, false},
|
{IssueType("convoy"), false},
|
||||||
{TypeEvent, false},
|
{IssueType("event"), false},
|
||||||
{TypeSlot, false},
|
{IssueType("slot"), false},
|
||||||
{TypeRig, false},
|
{IssueType("rig"), false},
|
||||||
// Invalid types
|
// Invalid types
|
||||||
{IssueType("invalid"), false},
|
{IssueType("invalid"), false},
|
||||||
{IssueType(""), false},
|
{IssueType(""), false},
|
||||||
@@ -581,12 +581,12 @@ func TestIssueTypeRequiredSections(t *testing.T) {
|
|||||||
{TypeTask, 1, "## Acceptance Criteria"},
|
{TypeTask, 1, "## Acceptance Criteria"},
|
||||||
{TypeEpic, 1, "## Success Criteria"},
|
{TypeEpic, 1, "## Success Criteria"},
|
||||||
{TypeChore, 0, ""},
|
{TypeChore, 0, ""},
|
||||||
{TypeMessage, 0, ""},
|
// Gas Town types are now custom and have no required sections
|
||||||
{TypeMolecule, 0, ""},
|
{IssueType("message"), 0, ""},
|
||||||
{TypeGate, 0, ""},
|
{IssueType("molecule"), 0, ""},
|
||||||
{TypeEvent, 0, ""},
|
{IssueType("gate"), 0, ""},
|
||||||
{TypeMergeRequest, 0, ""},
|
{IssueType("event"), 0, ""},
|
||||||
// Gas Town types (agent, role, rig, convoy, slot) have been removed
|
{IssueType("merge-request"), 0, ""},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|||||||
@@ -123,13 +123,13 @@ Widget displays correctly`,
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "message has no requirements",
|
name: "message has no requirements",
|
||||||
issueType: types.TypeMessage,
|
issueType: "message",
|
||||||
description: "Hello",
|
description: "Hello",
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "molecule has no requirements",
|
name: "molecule has no requirements",
|
||||||
issueType: types.TypeMolecule,
|
issueType: "molecule",
|
||||||
description: "",
|
description: "",
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user