feat: extract Gas Town types from beads core (bd-i54l)
Remove Gas Town-specific issue types (agent, role, rig, convoy, slot) from beads core. These types are now identified by labels instead: - gt:agent, gt:role, gt:rig, gt:convoy, gt:slot Changes: - internal/types/types.go: Remove TypeAgent, TypeRole, TypeRig, TypeConvoy, TypeSlot constants - cmd/bd/agent.go: Create agents with TypeTask + gt:agent label - cmd/bd/merge_slot.go: Create slots with TypeTask + gt:slot label - internal/storage/sqlite/queries.go, transaction.go: Query convoys by gt:convoy label - internal/rpc/server_issues_epics.go: Check gt:agent label for role_type/rig label auto-add - cmd/bd/create.go: Check gt:agent label for role_type/rig label auto-add - internal/ui/styles.go: Remove agent/role/rig type colors - cmd/bd/export_obsidian.go: Remove agent/role/rig/convoy type tag mappings - Update all affected tests This enables beads to be a generic issue tracker while Gas Town uses labels for its specific type semantics. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> Executed-By: beads/crew/dave Rig: beads Role: crew
This commit is contained in:
+44
-16
@@ -30,7 +30,7 @@ var agentCmd = &cobra.Command{
|
|||||||
Short: "Manage agent bead state",
|
Short: "Manage agent bead state",
|
||||||
Long: `Manage state on agent beads for ZFC-compliant state reporting.
|
Long: `Manage state on agent beads for ZFC-compliant state reporting.
|
||||||
|
|
||||||
Agent beads (type=agent) can self-report their state using these commands.
|
Agent beads (labeled gt:agent) can self-report their state using these commands.
|
||||||
This enables the Witness and other monitoring systems to track agent health.
|
This enables the Witness and other monitoring systems to track agent health.
|
||||||
|
|
||||||
States:
|
States:
|
||||||
@@ -207,7 +207,7 @@ func runAgentState(cmd *cobra.Command, args []string) error {
|
|||||||
agent = &types.Issue{
|
agent = &types.Issue{
|
||||||
ID: agentID,
|
ID: agentID,
|
||||||
Title: fmt.Sprintf("Agent: %s", agentID),
|
Title: fmt.Sprintf("Agent: %s", agentID),
|
||||||
IssueType: types.TypeAgent,
|
IssueType: types.TypeTask, // Use task type; gt:agent label marks it as agent
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
RoleType: roleType,
|
RoleType: roleType,
|
||||||
Rig: rig,
|
Rig: rig,
|
||||||
@@ -218,10 +218,11 @@ func runAgentState(cmd *cobra.Command, args []string) error {
|
|||||||
createArgs := &rpc.CreateArgs{
|
createArgs := &rpc.CreateArgs{
|
||||||
ID: agentID,
|
ID: agentID,
|
||||||
Title: agent.Title,
|
Title: agent.Title,
|
||||||
IssueType: string(types.TypeAgent),
|
IssueType: string(types.TypeTask), // Use task type; gt:agent label marks it as agent
|
||||||
RoleType: roleType,
|
RoleType: roleType,
|
||||||
Rig: rig,
|
Rig: rig,
|
||||||
CreatedBy: actor,
|
CreatedBy: actor,
|
||||||
|
Labels: []string{"gt:agent"}, // Gas Town agent label
|
||||||
}
|
}
|
||||||
resp, err := daemonClient.Create(createArgs)
|
resp, err := daemonClient.Create(createArgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -234,6 +235,10 @@ func runAgentState(cmd *cobra.Command, args []string) error {
|
|||||||
if err := activeStore.CreateIssue(ctx, agent, actor); err != nil {
|
if err := activeStore.CreateIssue(ctx, agent, actor); err != nil {
|
||||||
return fmt.Errorf("failed to auto-create agent bead %s: %w", agentID, err)
|
return fmt.Errorf("failed to auto-create agent bead %s: %w", agentID, err)
|
||||||
}
|
}
|
||||||
|
// Add gt:agent label to mark as agent bead
|
||||||
|
if err := activeStore.AddLabel(ctx, agent.ID, "gt:agent", actor); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "warning: failed to add gt:agent label: %v\n", err)
|
||||||
|
}
|
||||||
// Add role_type and rig labels for filtering
|
// Add role_type and rig labels for filtering
|
||||||
if roleType != "" {
|
if roleType != "" {
|
||||||
if err := activeStore.AddLabel(ctx, agent.ID, "role_type:"+roleType, actor); err != nil {
|
if err := activeStore.AddLabel(ctx, agent.ID, "role_type:"+roleType, actor); err != nil {
|
||||||
@@ -248,9 +253,12 @@ func runAgentState(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Get existing agent bead to verify it's an agent
|
// Get existing agent bead to verify it's an agent
|
||||||
|
var labels []string
|
||||||
if routedResult != nil && routedResult.Issue != nil {
|
if routedResult != nil && routedResult.Issue != nil {
|
||||||
// Already have the issue from routed resolution
|
// Already have the issue from routed resolution
|
||||||
agent = routedResult.Issue
|
agent = routedResult.Issue
|
||||||
|
// Get labels from routed store
|
||||||
|
labels, _ = routedResult.Store.GetLabels(ctx, agentID)
|
||||||
} else if daemonClient != nil && !needsRouting(agentArg) {
|
} else if daemonClient != nil && !needsRouting(agentArg) {
|
||||||
resp, err := daemonClient.Show(&rpc.ShowArgs{ID: agentID})
|
resp, err := daemonClient.Show(&rpc.ShowArgs{ID: agentID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -259,17 +267,19 @@ func runAgentState(cmd *cobra.Command, args []string) error {
|
|||||||
if err := json.Unmarshal(resp.Data, &agent); err != nil {
|
if err := json.Unmarshal(resp.Data, &agent); err != nil {
|
||||||
return fmt.Errorf("parsing response: %w", err)
|
return fmt.Errorf("parsing response: %w", err)
|
||||||
}
|
}
|
||||||
|
labels = agent.Labels
|
||||||
} else {
|
} else {
|
||||||
var err error
|
var err error
|
||||||
agent, err = activeStore.GetIssue(ctx, agentID)
|
agent, err = activeStore.GetIssue(ctx, agentID)
|
||||||
if err != nil || agent == nil {
|
if err != nil || agent == nil {
|
||||||
return fmt.Errorf("agent bead not found: %s", agentID)
|
return fmt.Errorf("agent bead not found: %s", agentID)
|
||||||
}
|
}
|
||||||
|
labels, _ = activeStore.GetLabels(ctx, agentID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify agent bead is actually an agent
|
// Verify agent bead is actually an agent (check for gt:agent label)
|
||||||
if agent.IssueType != "agent" {
|
if !isAgentBead(labels) {
|
||||||
return fmt.Errorf("%s is not an agent bead (type=%s)", agentID, agent.IssueType)
|
return fmt.Errorf("%s is not an agent bead (missing gt:agent label)", agentID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,9 +372,11 @@ func runAgentHeartbeat(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
// Get agent bead to verify it's an agent
|
// Get agent bead to verify it's an agent
|
||||||
var agent *types.Issue
|
var agent *types.Issue
|
||||||
|
var labels []string
|
||||||
if routedResult != nil && routedResult.Issue != nil {
|
if routedResult != nil && routedResult.Issue != nil {
|
||||||
// Already have the issue from routed resolution
|
// Already have the issue from routed resolution
|
||||||
agent = routedResult.Issue
|
agent = routedResult.Issue
|
||||||
|
labels, _ = routedResult.Store.GetLabels(ctx, agentID)
|
||||||
} else if daemonClient != nil && !needsRouting(agentArg) {
|
} else if daemonClient != nil && !needsRouting(agentArg) {
|
||||||
resp, err := daemonClient.Show(&rpc.ShowArgs{ID: agentID})
|
resp, err := daemonClient.Show(&rpc.ShowArgs{ID: agentID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -373,17 +385,19 @@ func runAgentHeartbeat(cmd *cobra.Command, args []string) error {
|
|||||||
if err := json.Unmarshal(resp.Data, &agent); err != nil {
|
if err := json.Unmarshal(resp.Data, &agent); err != nil {
|
||||||
return fmt.Errorf("parsing response: %w", err)
|
return fmt.Errorf("parsing response: %w", err)
|
||||||
}
|
}
|
||||||
|
labels = agent.Labels
|
||||||
} else {
|
} else {
|
||||||
var err error
|
var err error
|
||||||
agent, err = activeStore.GetIssue(ctx, agentID)
|
agent, err = activeStore.GetIssue(ctx, agentID)
|
||||||
if err != nil || agent == nil {
|
if err != nil || agent == nil {
|
||||||
return fmt.Errorf("agent bead not found: %s", agentID)
|
return fmt.Errorf("agent bead not found: %s", agentID)
|
||||||
}
|
}
|
||||||
|
labels, _ = activeStore.GetLabels(ctx, agentID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify agent bead is actually an agent
|
// Verify agent bead is actually an agent (check for gt:agent label)
|
||||||
if agent.IssueType != "agent" {
|
if !isAgentBead(labels) {
|
||||||
return fmt.Errorf("%s is not an agent bead (type=%s)", agentID, agent.IssueType)
|
return fmt.Errorf("%s is not an agent bead (missing gt:agent label)", agentID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update only last_activity
|
// Update only last_activity
|
||||||
@@ -464,9 +478,11 @@ func runAgentShow(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
// Get agent bead
|
// Get agent bead
|
||||||
var agent *types.Issue
|
var agent *types.Issue
|
||||||
|
var labels []string
|
||||||
if routedResult != nil && routedResult.Issue != nil {
|
if routedResult != nil && routedResult.Issue != nil {
|
||||||
// Already have the issue from routed resolution
|
// Already have the issue from routed resolution
|
||||||
agent = routedResult.Issue
|
agent = routedResult.Issue
|
||||||
|
labels, _ = routedResult.Store.GetLabels(ctx, agentID)
|
||||||
} else if daemonClient != nil && !needsRouting(agentArg) {
|
} else if daemonClient != nil && !needsRouting(agentArg) {
|
||||||
resp, err := daemonClient.Show(&rpc.ShowArgs{ID: agentID})
|
resp, err := daemonClient.Show(&rpc.ShowArgs{ID: agentID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -475,17 +491,19 @@ func runAgentShow(cmd *cobra.Command, args []string) error {
|
|||||||
if err := json.Unmarshal(resp.Data, &agent); err != nil {
|
if err := json.Unmarshal(resp.Data, &agent); err != nil {
|
||||||
return fmt.Errorf("parsing response: %w", err)
|
return fmt.Errorf("parsing response: %w", err)
|
||||||
}
|
}
|
||||||
|
labels = agent.Labels
|
||||||
} else {
|
} else {
|
||||||
var err error
|
var err error
|
||||||
agent, err = store.GetIssue(ctx, agentID)
|
agent, err = store.GetIssue(ctx, agentID)
|
||||||
if err != nil || agent == nil {
|
if err != nil || agent == nil {
|
||||||
return fmt.Errorf("agent bead not found: %s", agentID)
|
return fmt.Errorf("agent bead not found: %s", agentID)
|
||||||
}
|
}
|
||||||
|
labels, _ = store.GetLabels(ctx, agentID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify agent bead is actually an agent
|
// Verify agent bead is actually an agent (check for gt:agent label)
|
||||||
if agent.IssueType != "agent" {
|
if !isAgentBead(labels) {
|
||||||
return fmt.Errorf("%s is not an agent bead (type=%s)", agentID, agent.IssueType)
|
return fmt.Errorf("%s is not an agent bead (missing gt:agent label)", agentID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if jsonOutput {
|
if jsonOutput {
|
||||||
@@ -565,11 +583,11 @@ func runAgentBackfillLabels(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
ctx := rootCtx
|
ctx := rootCtx
|
||||||
|
|
||||||
// List all agent beads
|
// List all agent beads (by gt:agent label)
|
||||||
var agents []*types.Issue
|
var agents []*types.Issue
|
||||||
if daemonClient != nil {
|
if daemonClient != nil {
|
||||||
resp, err := daemonClient.List(&rpc.ListArgs{
|
resp, err := daemonClient.List(&rpc.ListArgs{
|
||||||
IssueType: "agent",
|
Labels: []string{"gt:agent"},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to list agents: %w", err)
|
return fmt.Errorf("failed to list agents: %w", err)
|
||||||
@@ -578,9 +596,8 @@ func runAgentBackfillLabels(cmd *cobra.Command, args []string) error {
|
|||||||
return fmt.Errorf("parsing response: %w", err)
|
return fmt.Errorf("parsing response: %w", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
agentType := types.TypeAgent
|
|
||||||
filter := types.IssueFilter{
|
filter := types.IssueFilter{
|
||||||
IssueType: &agentType,
|
Labels: []string{"gt:agent"},
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
agents, err = store.SearchIssues(ctx, "", filter)
|
agents, err = store.SearchIssues(ctx, "", filter)
|
||||||
@@ -756,6 +773,17 @@ func containsLabel(labels []string, label string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isAgentBead checks if an issue is an agent bead by looking for the gt:agent label.
|
||||||
|
// This replaces the previous type-based check (issue_type='agent') for Gas Town separation.
|
||||||
|
func isAgentBead(labels []string) bool {
|
||||||
|
for _, l := range labels {
|
||||||
|
if l == "gt:agent" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// parseAgentIDFields extracts role_type and rig from an agent bead ID.
|
// parseAgentIDFields extracts role_type and rig from an agent bead ID.
|
||||||
// Agent ID patterns:
|
// Agent ID patterns:
|
||||||
// - Town-level: <prefix>-<role> (e.g., gt-mayor) → role="mayor", rig=""
|
// - Town-level: <prefix>-<role> (e.g., gt-mayor) → role="mayor", rig=""
|
||||||
|
|||||||
@@ -45,11 +45,11 @@ func TestAgentStateWithRouting(t *testing.T) {
|
|||||||
rigDBPath := filepath.Join(rigBeadsDir, "beads.db")
|
rigDBPath := filepath.Join(rigBeadsDir, "beads.db")
|
||||||
rigStore := newTestStoreWithPrefix(t, rigDBPath, "gt")
|
rigStore := newTestStoreWithPrefix(t, rigDBPath, "gt")
|
||||||
|
|
||||||
// Create an agent bead in the rig database
|
// Create an agent bead in the rig database (using task type with gt:agent label)
|
||||||
agentBead := &types.Issue{
|
agentBead := &types.Issue{
|
||||||
ID: "gt-testrig-polecat-test",
|
ID: "gt-testrig-polecat-test",
|
||||||
Title: "Agent: gt-testrig-polecat-test",
|
Title: "Agent: gt-testrig-polecat-test",
|
||||||
IssueType: types.TypeAgent,
|
IssueType: types.TypeTask, // Use task type; gt:agent label marks it as agent
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
RoleType: "polecat",
|
RoleType: "polecat",
|
||||||
Rig: "testrig",
|
Rig: "testrig",
|
||||||
@@ -57,6 +57,9 @@ func TestAgentStateWithRouting(t *testing.T) {
|
|||||||
if err := rigStore.CreateIssue(ctx, agentBead, "test"); err != nil {
|
if err := rigStore.CreateIssue(ctx, agentBead, "test"); err != nil {
|
||||||
t.Fatalf("Failed to create agent bead: %v", err)
|
t.Fatalf("Failed to create agent bead: %v", err)
|
||||||
}
|
}
|
||||||
|
if err := rigStore.AddLabel(ctx, agentBead.ID, "gt:agent", "test"); err != nil {
|
||||||
|
t.Fatalf("Failed to add gt:agent label: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Create routes.jsonl in town .beads directory
|
// Create routes.jsonl in town .beads directory
|
||||||
routesContent := `{"prefix":"gt-","path":"rig"}`
|
routesContent := `{"prefix":"gt-","path":"rig"}`
|
||||||
@@ -92,8 +95,8 @@ func TestAgentStateWithRouting(t *testing.T) {
|
|||||||
t.Error("Expected result.Routed to be true for cross-repo lookup")
|
t.Error("Expected result.Routed to be true for cross-repo lookup")
|
||||||
}
|
}
|
||||||
|
|
||||||
if result.Issue.IssueType != types.TypeAgent {
|
if result.Issue.IssueType != types.TypeTask {
|
||||||
t.Errorf("Expected issue type %q, got %q", types.TypeAgent, result.Issue.IssueType)
|
t.Errorf("Expected issue type %q, got %q", types.TypeTask, result.Issue.IssueType)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Logf("Successfully resolved agent %s via routing", result.Issue.ID)
|
t.Logf("Successfully resolved agent %s via routing", result.Issue.ID)
|
||||||
@@ -136,11 +139,11 @@ func TestAgentHeartbeatWithRouting(t *testing.T) {
|
|||||||
rigDBPath := filepath.Join(rigBeadsDir, "beads.db")
|
rigDBPath := filepath.Join(rigBeadsDir, "beads.db")
|
||||||
rigStore := newTestStoreWithPrefix(t, rigDBPath, "gt")
|
rigStore := newTestStoreWithPrefix(t, rigDBPath, "gt")
|
||||||
|
|
||||||
// Create an agent bead in the rig database
|
// Create an agent bead in the rig database (using task type with gt:agent label)
|
||||||
agentBead := &types.Issue{
|
agentBead := &types.Issue{
|
||||||
ID: "gt-test-witness",
|
ID: "gt-test-witness",
|
||||||
Title: "Agent: gt-test-witness",
|
Title: "Agent: gt-test-witness",
|
||||||
IssueType: types.TypeAgent,
|
IssueType: types.TypeTask, // Use task type; gt:agent label marks it as agent
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
RoleType: "witness",
|
RoleType: "witness",
|
||||||
Rig: "test",
|
Rig: "test",
|
||||||
@@ -148,6 +151,9 @@ func TestAgentHeartbeatWithRouting(t *testing.T) {
|
|||||||
if err := rigStore.CreateIssue(ctx, agentBead, "test"); err != nil {
|
if err := rigStore.CreateIssue(ctx, agentBead, "test"); err != nil {
|
||||||
t.Fatalf("Failed to create agent bead: %v", err)
|
t.Fatalf("Failed to create agent bead: %v", err)
|
||||||
}
|
}
|
||||||
|
if err := rigStore.AddLabel(ctx, agentBead.ID, "gt:agent", "test"); err != nil {
|
||||||
|
t.Fatalf("Failed to add gt:agent label: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Create routes.jsonl
|
// Create routes.jsonl
|
||||||
routesContent := `{"prefix":"gt-","path":"rig"}`
|
routesContent := `{"prefix":"gt-","path":"rig"}`
|
||||||
@@ -207,11 +213,11 @@ func TestAgentShowWithRouting(t *testing.T) {
|
|||||||
rigDBPath := filepath.Join(rigBeadsDir, "beads.db")
|
rigDBPath := filepath.Join(rigBeadsDir, "beads.db")
|
||||||
rigStore := newTestStoreWithPrefix(t, rigDBPath, "gt")
|
rigStore := newTestStoreWithPrefix(t, rigDBPath, "gt")
|
||||||
|
|
||||||
// Create an agent bead in the rig database
|
// Create an agent bead in the rig database (using task type with gt:agent label)
|
||||||
agentBead := &types.Issue{
|
agentBead := &types.Issue{
|
||||||
ID: "gt-myrig-crew-alice",
|
ID: "gt-myrig-crew-alice",
|
||||||
Title: "Agent: gt-myrig-crew-alice",
|
Title: "Agent: gt-myrig-crew-alice",
|
||||||
IssueType: types.TypeAgent,
|
IssueType: types.TypeTask, // Use task type; gt:agent label marks it as agent
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
RoleType: "crew",
|
RoleType: "crew",
|
||||||
Rig: "myrig",
|
Rig: "myrig",
|
||||||
@@ -219,6 +225,9 @@ func TestAgentShowWithRouting(t *testing.T) {
|
|||||||
if err := rigStore.CreateIssue(ctx, agentBead, "test"); err != nil {
|
if err := rigStore.CreateIssue(ctx, agentBead, "test"); err != nil {
|
||||||
t.Fatalf("Failed to create agent bead: %v", err)
|
t.Fatalf("Failed to create agent bead: %v", err)
|
||||||
}
|
}
|
||||||
|
if err := rigStore.AddLabel(ctx, agentBead.ID, "gt:agent", "test"); err != nil {
|
||||||
|
t.Fatalf("Failed to add gt:agent label: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Create routes.jsonl
|
// Create routes.jsonl
|
||||||
routesContent := `{"prefix":"gt-","path":"rig"}`
|
routesContent := `{"prefix":"gt-","path":"rig"}`
|
||||||
@@ -246,8 +255,8 @@ func TestAgentShowWithRouting(t *testing.T) {
|
|||||||
t.Errorf("Expected issue ID %q, got %q", "gt-myrig-crew-alice", result.Issue.ID)
|
t.Errorf("Expected issue ID %q, got %q", "gt-myrig-crew-alice", result.Issue.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if result.Issue.IssueType != types.TypeAgent {
|
if result.Issue.IssueType != types.TypeTask {
|
||||||
t.Errorf("Expected issue type %q, got %q", types.TypeAgent, result.Issue.IssueType)
|
t.Errorf("Expected issue type %q, got %q", types.TypeTask, result.Issue.IssueType)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Logf("Successfully resolved agent %s via routing for show test", result.Issue.ID)
|
t.Logf("Successfully resolved agent %s via routing for show test", result.Issue.ID)
|
||||||
|
|||||||
+9
-1
@@ -474,7 +474,15 @@ var createCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Auto-add role_type/rig labels for agent beads (enables filtering queries)
|
// Auto-add role_type/rig labels for agent beads (enables filtering queries)
|
||||||
if issue.IssueType == types.TypeAgent {
|
// Check for gt:agent label to identify agent beads (Gas Town separation)
|
||||||
|
hasAgentLabel := false
|
||||||
|
for _, l := range labels {
|
||||||
|
if l == "gt:agent" {
|
||||||
|
hasAgentLabel = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hasAgentLabel {
|
||||||
if issue.RoleType != "" {
|
if issue.RoleType != "" {
|
||||||
agentLabel := "role_type:" + issue.RoleType
|
agentLabel := "role_type:" + issue.RoleType
|
||||||
if err := store.AddLabel(ctx, issue.ID, agentLabel, actor); err != nil {
|
if err := store.AddLabel(ctx, issue.ID, agentLabel, actor); err != nil {
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ var obsidianPriority = []string{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// obsidianTypeTag maps bd issue type to Obsidian tag
|
// 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.
|
||||||
var obsidianTypeTag = map[types.IssueType]string{
|
var obsidianTypeTag = map[types.IssueType]string{
|
||||||
types.TypeBug: "#Bug",
|
types.TypeBug: "#Bug",
|
||||||
types.TypeFeature: "#Feature",
|
types.TypeFeature: "#Feature",
|
||||||
@@ -43,10 +45,6 @@ var obsidianTypeTag = map[types.IssueType]string{
|
|||||||
types.TypeMergeRequest: "#MergeRequest",
|
types.TypeMergeRequest: "#MergeRequest",
|
||||||
types.TypeMolecule: "#Molecule",
|
types.TypeMolecule: "#Molecule",
|
||||||
types.TypeGate: "#Gate",
|
types.TypeGate: "#Gate",
|
||||||
types.TypeAgent: "#Agent",
|
|
||||||
types.TypeRole: "#Role",
|
|
||||||
types.TypeRig: "#Rig",
|
|
||||||
types.TypeConvoy: "#Convoy",
|
|
||||||
types.TypeEvent: "#Event",
|
types.TypeEvent: "#Event",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-5
@@ -25,7 +25,7 @@ A merge slot is an exclusive access primitive: only one agent can hold it at a t
|
|||||||
This prevents "monkey knife fights" where multiple polecats race to resolve conflicts
|
This prevents "monkey knife fights" where multiple polecats race to resolve conflicts
|
||||||
and create cascading conflicts.
|
and create cascading conflicts.
|
||||||
|
|
||||||
Each rig has one merge slot bead: <prefix>-merge-slot (type=slot).
|
Each rig has one merge slot bead: <prefix>-merge-slot (labeled gt:slot).
|
||||||
The slot uses:
|
The slot uses:
|
||||||
- status=open: slot is available
|
- status=open: slot is available
|
||||||
- status=in_progress: slot is held
|
- status=in_progress: slot is held
|
||||||
@@ -157,15 +157,15 @@ func runMergeSlotCreate(cmd *cobra.Command, args []string) error {
|
|||||||
// Create the merge slot bead
|
// Create the merge slot bead
|
||||||
title := "Merge Slot"
|
title := "Merge Slot"
|
||||||
description := "Exclusive access slot for serialized conflict resolution in the merge queue."
|
description := "Exclusive access slot for serialized conflict resolution in the merge queue."
|
||||||
slotType := types.TypeSlot
|
|
||||||
|
|
||||||
if daemonClient != nil {
|
if daemonClient != nil {
|
||||||
createArgs := &rpc.CreateArgs{
|
createArgs := &rpc.CreateArgs{
|
||||||
ID: slotID,
|
ID: slotID,
|
||||||
Title: title,
|
Title: title,
|
||||||
Description: description,
|
Description: description,
|
||||||
IssueType: string(slotType),
|
IssueType: string(types.TypeTask), // Use task type; gt:slot label marks it as slot
|
||||||
Priority: 0, // P0 - system infrastructure
|
Priority: 0, // P0 - system infrastructure
|
||||||
|
Labels: []string{"gt:slot"}, // Gas Town slot label
|
||||||
}
|
}
|
||||||
resp, err := daemonClient.Create(createArgs)
|
resp, err := daemonClient.Create(createArgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -179,13 +179,18 @@ func runMergeSlotCreate(cmd *cobra.Command, args []string) error {
|
|||||||
ID: slotID,
|
ID: slotID,
|
||||||
Title: title,
|
Title: title,
|
||||||
Description: description,
|
Description: description,
|
||||||
IssueType: slotType,
|
IssueType: types.TypeTask, // Use task type; gt:slot label marks it as slot
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
}
|
}
|
||||||
if err := store.CreateIssue(ctx, issue, actor); err != nil {
|
if err := store.CreateIssue(ctx, issue, actor); err != nil {
|
||||||
return fmt.Errorf("failed to create merge slot: %w", err)
|
return fmt.Errorf("failed to create merge slot: %w", err)
|
||||||
}
|
}
|
||||||
|
// Add gt:slot label to mark as slot bead
|
||||||
|
if err := store.AddLabel(ctx, slotID, "gt:slot", actor); err != nil {
|
||||||
|
// Non-fatal: log warning but don't fail creation
|
||||||
|
fmt.Fprintf(os.Stderr, "warning: failed to add gt:slot label: %v\n", err)
|
||||||
|
}
|
||||||
markDirtyAndScheduleFlush()
|
markDirtyAndScheduleFlush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,16 @@ import (
|
|||||||
"github.com/steveyegge/beads/internal/utils"
|
"github.com/steveyegge/beads/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// containsLabel checks if a label exists in the list
|
||||||
|
func containsLabel(labels []string, label string) bool {
|
||||||
|
for _, l := range labels {
|
||||||
|
if l == label {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// parseTimeRPC parses time strings in multiple formats (RFC3339, YYYY-MM-DD, etc.)
|
// parseTimeRPC parses time strings in multiple formats (RFC3339, YYYY-MM-DD, etc.)
|
||||||
// Matches the parseTimeFlag behavior in cmd/bd/list.go for CLI parity
|
// Matches the parseTimeFlag behavior in cmd/bd/list.go for CLI parity
|
||||||
func parseTimeRPC(s string) (time.Time, error) {
|
func parseTimeRPC(s string) (time.Time, error) {
|
||||||
@@ -346,7 +356,8 @@ func (s *Server) handleCreate(req *Request) Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Auto-add role_type/rig labels for agent beads (enables filtering queries)
|
// Auto-add role_type/rig labels for agent beads (enables filtering queries)
|
||||||
if issue.IssueType == types.TypeAgent {
|
// Check for gt:agent label to identify agent beads (Gas Town separation)
|
||||||
|
if containsLabel(createArgs.Labels, "gt:agent") {
|
||||||
if issue.RoleType != "" {
|
if issue.RoleType != "" {
|
||||||
label := "role_type:" + issue.RoleType
|
label := "role_type:" + issue.RoleType
|
||||||
if err := store.AddLabel(ctx, issue.ID, label, s.reqActor(req)); err != nil {
|
if err := store.AddLabel(ctx, issue.ID, label, s.reqActor(req)); err != nil {
|
||||||
@@ -590,13 +601,14 @@ func (s *Server) handleUpdate(req *Request) Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Auto-add role_type/rig labels for agent beads when these fields are set
|
// Auto-add role_type/rig labels for agent beads when these fields are set
|
||||||
// This enables filtering queries like: bd list --type=agent --label=role_type:witness
|
// This enables filtering queries like: bd list --label=gt:agent --label=role_type:witness
|
||||||
// Note: We remove old role_type/rig labels first to prevent accumulation
|
// Note: We remove old role_type/rig labels first to prevent accumulation
|
||||||
if issue.IssueType == types.TypeAgent {
|
// Check for gt:agent label to identify agent beads (Gas Town separation)
|
||||||
|
issueLabels, _ := store.GetLabels(ctx, updateArgs.ID)
|
||||||
|
if containsLabel(issueLabels, "gt:agent") {
|
||||||
if updateArgs.RoleType != nil && *updateArgs.RoleType != "" {
|
if updateArgs.RoleType != nil && *updateArgs.RoleType != "" {
|
||||||
// Remove any existing role_type:* labels first
|
// Remove any existing role_type:* labels first
|
||||||
existingLabels, _ := store.GetLabels(ctx, updateArgs.ID)
|
for _, l := range issueLabels {
|
||||||
for _, l := range existingLabels {
|
|
||||||
if strings.HasPrefix(l, "role_type:") {
|
if strings.HasPrefix(l, "role_type:") {
|
||||||
_ = store.RemoveLabel(ctx, updateArgs.ID, l, actor)
|
_ = store.RemoveLabel(ctx, updateArgs.ID, l, actor)
|
||||||
}
|
}
|
||||||
@@ -612,8 +624,7 @@ func (s *Server) handleUpdate(req *Request) Response {
|
|||||||
}
|
}
|
||||||
if updateArgs.Rig != nil && *updateArgs.Rig != "" {
|
if updateArgs.Rig != nil && *updateArgs.Rig != "" {
|
||||||
// Remove any existing rig:* labels first
|
// Remove any existing rig:* labels first
|
||||||
existingLabels, _ := store.GetLabels(ctx, updateArgs.ID)
|
for _, l := range issueLabels {
|
||||||
for _, l := range existingLabels {
|
|
||||||
if strings.HasPrefix(l, "rig:") {
|
if strings.HasPrefix(l, "rig:") {
|
||||||
_ = store.RemoveLabel(ctx, updateArgs.ID, l, actor)
|
_ = store.RemoveLabel(ctx, updateArgs.ID, l, actor)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1151,15 +1151,16 @@ func (s *SQLiteStorage) CloseIssue(ctx context.Context, id string, reason string
|
|||||||
|
|
||||||
// Reactive convoy completion: check if any convoys tracking this issue should auto-close
|
// Reactive convoy completion: check if any convoys tracking this issue should auto-close
|
||||||
// Find convoys that track this issue (convoy.issue_id tracks closed_issue.depends_on_id)
|
// Find convoys that track this issue (convoy.issue_id tracks closed_issue.depends_on_id)
|
||||||
|
// Uses gt:convoy label instead of issue_type for Gas Town separation
|
||||||
convoyRows, err := tx.QueryContext(ctx, `
|
convoyRows, err := tx.QueryContext(ctx, `
|
||||||
SELECT DISTINCT d.issue_id
|
SELECT DISTINCT d.issue_id
|
||||||
FROM dependencies d
|
FROM dependencies d
|
||||||
JOIN issues i ON d.issue_id = i.id
|
JOIN issues i ON d.issue_id = i.id
|
||||||
|
JOIN labels l ON i.id = l.issue_id AND l.label = 'gt:convoy'
|
||||||
WHERE d.depends_on_id = ?
|
WHERE d.depends_on_id = ?
|
||||||
AND d.type = ?
|
AND d.type = ?
|
||||||
AND i.issue_type = ?
|
|
||||||
AND i.status != ?
|
AND i.status != ?
|
||||||
`, id, types.DepTracks, types.TypeConvoy, types.StatusClosed)
|
`, id, types.DepTracks, types.StatusClosed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to find tracking convoys: %w", err)
|
return fmt.Errorf("failed to find tracking convoys: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1475,17 +1475,20 @@ func TestConvoyReactiveCompletion(t *testing.T) {
|
|||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// Create a convoy
|
// Create a convoy (using task type with gt:convoy label)
|
||||||
convoy := &types.Issue{
|
convoy := &types.Issue{
|
||||||
Title: "Test Convoy",
|
Title: "Test Convoy",
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeConvoy,
|
IssueType: types.TypeTask, // Use task type; gt:convoy label marks it as convoy
|
||||||
}
|
}
|
||||||
err := store.CreateIssue(ctx, convoy, "test-user")
|
err := store.CreateIssue(ctx, convoy, "test-user")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("CreateIssue convoy failed: %v", err)
|
t.Fatalf("CreateIssue convoy failed: %v", err)
|
||||||
}
|
}
|
||||||
|
if err := store.AddLabel(ctx, convoy.ID, "gt:convoy", "test-user"); err != nil {
|
||||||
|
t.Fatalf("Failed to add gt:convoy label: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Create two issues to track
|
// Create two issues to track
|
||||||
issue1 := &types.Issue{
|
issue1 := &types.Issue{
|
||||||
|
|||||||
@@ -572,15 +572,16 @@ func (t *sqliteTxStorage) CloseIssue(ctx context.Context, id string, reason stri
|
|||||||
|
|
||||||
// Reactive convoy completion: check if any convoys tracking this issue should auto-close
|
// Reactive convoy completion: check if any convoys tracking this issue should auto-close
|
||||||
// Find convoys that track this issue (convoy.issue_id tracks closed_issue.depends_on_id)
|
// Find convoys that track this issue (convoy.issue_id tracks closed_issue.depends_on_id)
|
||||||
|
// Uses gt:convoy label instead of issue_type for Gas Town separation
|
||||||
convoyRows, err := t.conn.QueryContext(ctx, `
|
convoyRows, err := t.conn.QueryContext(ctx, `
|
||||||
SELECT DISTINCT d.issue_id
|
SELECT DISTINCT d.issue_id
|
||||||
FROM dependencies d
|
FROM dependencies d
|
||||||
JOIN issues i ON d.issue_id = i.id
|
JOIN issues i ON d.issue_id = i.id
|
||||||
|
JOIN labels l ON i.id = l.issue_id AND l.label = 'gt:convoy'
|
||||||
WHERE d.depends_on_id = ?
|
WHERE d.depends_on_id = ?
|
||||||
AND d.type = ?
|
AND d.type = ?
|
||||||
AND i.issue_type = ?
|
|
||||||
AND i.status != ?
|
AND i.status != ?
|
||||||
`, id, types.DepTracks, types.TypeConvoy, types.StatusClosed)
|
`, id, types.DepTracks, types.StatusClosed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to find tracking convoys: %w", err)
|
return fmt.Errorf("failed to find tracking convoys: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -412,6 +412,9 @@ func (s Status) IsValidWithCustom(customStatuses []string) bool {
|
|||||||
type IssueType string
|
type IssueType string
|
||||||
|
|
||||||
// Issue type constants
|
// Issue type constants
|
||||||
|
// Note: Gas Town-specific types (agent, role, rig, convoy, slot) have been removed.
|
||||||
|
// Use custom types via `bd config set types.custom "agent,role,..."` if needed.
|
||||||
|
// These types are now identified by labels (gt:agent, gt:role, etc.) instead.
|
||||||
const (
|
const (
|
||||||
TypeBug IssueType = "bug"
|
TypeBug IssueType = "bug"
|
||||||
TypeFeature IssueType = "feature"
|
TypeFeature IssueType = "feature"
|
||||||
@@ -422,18 +425,13 @@ const (
|
|||||||
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
|
||||||
TypeGate IssueType = "gate" // Async coordination gate
|
TypeGate IssueType = "gate" // Async coordination gate
|
||||||
TypeAgent IssueType = "agent" // Agent identity bead
|
|
||||||
TypeRole IssueType = "role" // Agent role definition
|
|
||||||
TypeRig IssueType = "rig" // Rig identity bead (project container)
|
|
||||||
TypeConvoy IssueType = "convoy" // Cross-project tracking with reactive completion
|
|
||||||
TypeEvent IssueType = "event" // Operational state change record
|
TypeEvent IssueType = "event" // Operational state change record
|
||||||
TypeSlot IssueType = "slot" // Exclusive access slot (merge-slot gate)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsValid checks if the issue type value is valid (built-in types only)
|
// IsValid checks if the issue type value is valid (built-in types only)
|
||||||
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, TypeMessage, TypeMergeRequest, TypeMolecule, TypeGate, TypeEvent:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -480,7 +478,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, message, molecule, gate, event, merge-request
|
||||||
// have no required sections
|
// have no required sections
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -401,12 +401,15 @@ func TestIssueTypeIsValid(t *testing.T) {
|
|||||||
{TypeTask, true},
|
{TypeTask, true},
|
||||||
{TypeEpic, true},
|
{TypeEpic, true},
|
||||||
{TypeChore, true},
|
{TypeChore, true},
|
||||||
{TypeAgent, true},
|
{TypeMessage, true},
|
||||||
{TypeRole, true},
|
{TypeMergeRequest, true},
|
||||||
{TypeRig, true},
|
{TypeMolecule, true},
|
||||||
{TypeConvoy, true},
|
{TypeGate, true},
|
||||||
{TypeEvent, true},
|
{TypeEvent, true},
|
||||||
{TypeSlot, true},
|
// Gas Town types (agent, role, rig, convoy, slot) have been removed
|
||||||
|
// They are now identified by labels (gt:agent, etc.) instead
|
||||||
|
{IssueType("agent"), false}, // Now requires custom type config
|
||||||
|
{IssueType("convoy"), false}, // Now requires custom type config
|
||||||
{IssueType("invalid"), false},
|
{IssueType("invalid"), false},
|
||||||
{IssueType(""), false},
|
{IssueType(""), false},
|
||||||
}
|
}
|
||||||
@@ -434,12 +437,9 @@ func TestIssueTypeRequiredSections(t *testing.T) {
|
|||||||
{TypeMessage, 0, ""},
|
{TypeMessage, 0, ""},
|
||||||
{TypeMolecule, 0, ""},
|
{TypeMolecule, 0, ""},
|
||||||
{TypeGate, 0, ""},
|
{TypeGate, 0, ""},
|
||||||
{TypeAgent, 0, ""},
|
|
||||||
{TypeRole, 0, ""},
|
|
||||||
{TypeRig, 0, ""},
|
|
||||||
{TypeConvoy, 0, ""},
|
|
||||||
{TypeEvent, 0, ""},
|
{TypeEvent, 0, ""},
|
||||||
{TypeMergeRequest, 0, ""},
|
{TypeMergeRequest, 0, ""},
|
||||||
|
// Gas Town types (agent, role, rig, convoy, slot) have been removed
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|||||||
+5
-22
@@ -135,18 +135,8 @@ var (
|
|||||||
Light: "", // standard text color
|
Light: "", // standard text color
|
||||||
Dark: "",
|
Dark: "",
|
||||||
}
|
}
|
||||||
ColorTypeAgent = lipgloss.AdaptiveColor{
|
// Note: Gas Town-specific types (agent, role, rig) have been removed.
|
||||||
Light: "#59c2ff", // cyan - agent identity
|
// Use labels (gt:agent, gt:role, gt:rig) with custom styling if needed.
|
||||||
Dark: "#59c2ff",
|
|
||||||
}
|
|
||||||
ColorTypeRole = lipgloss.AdaptiveColor{
|
|
||||||
Light: "#7fd962", // green - role definition
|
|
||||||
Dark: "#7fd962",
|
|
||||||
}
|
|
||||||
ColorTypeRig = lipgloss.AdaptiveColor{
|
|
||||||
Light: "#e6a756", // orange - rig identity (project container)
|
|
||||||
Dark: "#e6a756",
|
|
||||||
}
|
|
||||||
|
|
||||||
// === Issue ID Color ===
|
// === Issue ID Color ===
|
||||||
// IDs use standard text color - subtle, not attention-grabbing
|
// IDs use standard text color - subtle, not attention-grabbing
|
||||||
@@ -194,9 +184,7 @@ var (
|
|||||||
TypeTaskStyle = lipgloss.NewStyle().Foreground(ColorTypeTask)
|
TypeTaskStyle = lipgloss.NewStyle().Foreground(ColorTypeTask)
|
||||||
TypeEpicStyle = lipgloss.NewStyle().Foreground(ColorTypeEpic)
|
TypeEpicStyle = lipgloss.NewStyle().Foreground(ColorTypeEpic)
|
||||||
TypeChoreStyle = lipgloss.NewStyle().Foreground(ColorTypeChore)
|
TypeChoreStyle = lipgloss.NewStyle().Foreground(ColorTypeChore)
|
||||||
TypeAgentStyle = lipgloss.NewStyle().Foreground(ColorTypeAgent)
|
// Note: Gas Town-specific type styles (agent, role, rig) have been removed.
|
||||||
TypeRoleStyle = lipgloss.NewStyle().Foreground(ColorTypeRole)
|
|
||||||
TypeRigStyle = lipgloss.NewStyle().Foreground(ColorTypeRig)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CategoryStyle for section headers - bold with accent color
|
// CategoryStyle for section headers - bold with accent color
|
||||||
@@ -331,7 +319,8 @@ func RenderPriority(priority int) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RenderType renders an issue type with semantic styling
|
// RenderType renders an issue type with semantic styling
|
||||||
// bugs get color; all other types use standard text
|
// bugs and epics get color; all other types use standard text
|
||||||
|
// Note: Gas Town-specific types (agent, role, rig) now fall through to default
|
||||||
func RenderType(issueType string) string {
|
func RenderType(issueType string) string {
|
||||||
switch issueType {
|
switch issueType {
|
||||||
case "bug":
|
case "bug":
|
||||||
@@ -344,12 +333,6 @@ func RenderType(issueType string) string {
|
|||||||
return TypeEpicStyle.Render(issueType)
|
return TypeEpicStyle.Render(issueType)
|
||||||
case "chore":
|
case "chore":
|
||||||
return TypeChoreStyle.Render(issueType)
|
return TypeChoreStyle.Render(issueType)
|
||||||
case "agent":
|
|
||||||
return TypeAgentStyle.Render(issueType)
|
|
||||||
case "role":
|
|
||||||
return TypeRoleStyle.Render(issueType)
|
|
||||||
case "rig":
|
|
||||||
return TypeRigStyle.Render(issueType)
|
|
||||||
default:
|
default:
|
||||||
return issueType
|
return issueType
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,9 +92,10 @@ func TestRenderTypeVariants(t *testing.T) {
|
|||||||
{"task", TypeTaskStyle.Render("task")},
|
{"task", TypeTaskStyle.Render("task")},
|
||||||
{"epic", TypeEpicStyle.Render("epic")},
|
{"epic", TypeEpicStyle.Render("epic")},
|
||||||
{"chore", TypeChoreStyle.Render("chore")},
|
{"chore", TypeChoreStyle.Render("chore")},
|
||||||
{"agent", TypeAgentStyle.Render("agent")},
|
// Gas Town types (agent, role, rig) have been removed - they now fall through to default
|
||||||
{"role", TypeRoleStyle.Render("role")},
|
{"agent", "agent"}, // Falls through to default (no styling)
|
||||||
{"rig", TypeRigStyle.Render("rig")},
|
{"role", "role"}, // Falls through to default (no styling)
|
||||||
|
{"rig", "rig"}, // Falls through to default (no styling)
|
||||||
{"custom", "custom"},
|
{"custom", "custom"},
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
|||||||
@@ -126,10 +126,10 @@ func TestParseIssueType(t *testing.T) {
|
|||||||
{"merge-request type", "merge-request", types.TypeMergeRequest, false, ""},
|
{"merge-request type", "merge-request", types.TypeMergeRequest, false, ""},
|
||||||
{"molecule type", "molecule", types.TypeMolecule, false, ""},
|
{"molecule type", "molecule", types.TypeMolecule, false, ""},
|
||||||
{"gate type", "gate", types.TypeGate, false, ""},
|
{"gate type", "gate", types.TypeGate, false, ""},
|
||||||
{"agent type", "agent", types.TypeAgent, false, ""},
|
{"event type", "event", types.TypeEvent, false, ""},
|
||||||
{"role type", "role", types.TypeRole, false, ""},
|
|
||||||
{"rig type", "rig", types.TypeRig, false, ""},
|
|
||||||
{"message type", "message", types.TypeMessage, false, ""},
|
{"message type", "message", types.TypeMessage, false, ""},
|
||||||
|
// Gas Town types (agent, role, rig, convoy, slot) have been removed
|
||||||
|
// 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