From a70c3a8cbecad7550dce17a7669a2a5bd832644f Mon Sep 17 00:00:00 2001 From: dave Date: Tue, 6 Jan 2026 22:18:37 -0800 Subject: [PATCH] feat: extract Gas Town types from beads core (bd-i54l) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Executed-By: beads/crew/dave Rig: beads Role: crew --- cmd/bd/agent.go | 60 +++++++++++++++++++------- cmd/bd/agent_routing_test.go | 29 ++++++++----- cmd/bd/create.go | 10 ++++- cmd/bd/export_obsidian.go | 6 +-- cmd/bd/merge_slot.go | 15 ++++--- internal/rpc/server_issues_epics.go | 25 ++++++++--- internal/storage/sqlite/queries.go | 5 ++- internal/storage/sqlite/sqlite_test.go | 7 ++- internal/storage/sqlite/transaction.go | 5 ++- internal/types/types.go | 12 +++--- internal/types/types_test.go | 18 ++++---- internal/ui/styles.go | 27 +++--------- internal/ui/styles_test.go | 7 +-- internal/validation/bead_test.go | 6 +-- 14 files changed, 139 insertions(+), 93 deletions(-) diff --git a/cmd/bd/agent.go b/cmd/bd/agent.go index ec7bb424..c16d9e3a 100644 --- a/cmd/bd/agent.go +++ b/cmd/bd/agent.go @@ -30,7 +30,7 @@ var agentCmd = &cobra.Command{ Short: "Manage agent bead state", 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. States: @@ -207,7 +207,7 @@ func runAgentState(cmd *cobra.Command, args []string) error { agent = &types.Issue{ ID: 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, RoleType: roleType, Rig: rig, @@ -218,10 +218,11 @@ func runAgentState(cmd *cobra.Command, args []string) error { createArgs := &rpc.CreateArgs{ ID: agentID, Title: agent.Title, - IssueType: string(types.TypeAgent), + IssueType: string(types.TypeTask), // Use task type; gt:agent label marks it as agent RoleType: roleType, Rig: rig, CreatedBy: actor, + Labels: []string{"gt:agent"}, // Gas Town agent label } resp, err := daemonClient.Create(createArgs) if err != nil { @@ -234,6 +235,10 @@ func runAgentState(cmd *cobra.Command, args []string) error { if err := activeStore.CreateIssue(ctx, agent, actor); err != nil { 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 if roleType != "" { 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 { // Get existing agent bead to verify it's an agent + var labels []string if routedResult != nil && routedResult.Issue != nil { // Already have the issue from routed resolution agent = routedResult.Issue + // Get labels from routed store + labels, _ = routedResult.Store.GetLabels(ctx, agentID) } else if daemonClient != nil && !needsRouting(agentArg) { resp, err := daemonClient.Show(&rpc.ShowArgs{ID: agentID}) if err != nil { @@ -259,17 +267,19 @@ func runAgentState(cmd *cobra.Command, args []string) error { if err := json.Unmarshal(resp.Data, &agent); err != nil { return fmt.Errorf("parsing response: %w", err) } + labels = agent.Labels } else { var err error agent, err = activeStore.GetIssue(ctx, agentID) if err != nil || agent == nil { return fmt.Errorf("agent bead not found: %s", agentID) } + labels, _ = activeStore.GetLabels(ctx, agentID) } - // Verify agent bead is actually an agent - if agent.IssueType != "agent" { - return fmt.Errorf("%s is not an agent bead (type=%s)", agentID, agent.IssueType) + // Verify agent bead is actually an agent (check for gt:agent label) + if !isAgentBead(labels) { + 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 var agent *types.Issue + var labels []string if routedResult != nil && routedResult.Issue != nil { // Already have the issue from routed resolution agent = routedResult.Issue + labels, _ = routedResult.Store.GetLabels(ctx, agentID) } else if daemonClient != nil && !needsRouting(agentArg) { resp, err := daemonClient.Show(&rpc.ShowArgs{ID: agentID}) if err != nil { @@ -373,17 +385,19 @@ func runAgentHeartbeat(cmd *cobra.Command, args []string) error { if err := json.Unmarshal(resp.Data, &agent); err != nil { return fmt.Errorf("parsing response: %w", err) } + labels = agent.Labels } else { var err error agent, err = activeStore.GetIssue(ctx, agentID) if err != nil || agent == nil { return fmt.Errorf("agent bead not found: %s", agentID) } + labels, _ = activeStore.GetLabels(ctx, agentID) } - // Verify agent bead is actually an agent - if agent.IssueType != "agent" { - return fmt.Errorf("%s is not an agent bead (type=%s)", agentID, agent.IssueType) + // Verify agent bead is actually an agent (check for gt:agent label) + if !isAgentBead(labels) { + return fmt.Errorf("%s is not an agent bead (missing gt:agent label)", agentID) } // Update only last_activity @@ -464,9 +478,11 @@ func runAgentShow(cmd *cobra.Command, args []string) error { // Get agent bead var agent *types.Issue + var labels []string if routedResult != nil && routedResult.Issue != nil { // Already have the issue from routed resolution agent = routedResult.Issue + labels, _ = routedResult.Store.GetLabels(ctx, agentID) } else if daemonClient != nil && !needsRouting(agentArg) { resp, err := daemonClient.Show(&rpc.ShowArgs{ID: agentID}) if err != nil { @@ -475,17 +491,19 @@ func runAgentShow(cmd *cobra.Command, args []string) error { if err := json.Unmarshal(resp.Data, &agent); err != nil { return fmt.Errorf("parsing response: %w", err) } + labels = agent.Labels } else { var err error agent, err = store.GetIssue(ctx, agentID) if err != nil || agent == nil { return fmt.Errorf("agent bead not found: %s", agentID) } + labels, _ = store.GetLabels(ctx, agentID) } - // Verify agent bead is actually an agent - if agent.IssueType != "agent" { - return fmt.Errorf("%s is not an agent bead (type=%s)", agentID, agent.IssueType) + // Verify agent bead is actually an agent (check for gt:agent label) + if !isAgentBead(labels) { + return fmt.Errorf("%s is not an agent bead (missing gt:agent label)", agentID) } if jsonOutput { @@ -565,11 +583,11 @@ func runAgentBackfillLabels(cmd *cobra.Command, args []string) error { ctx := rootCtx - // List all agent beads + // List all agent beads (by gt:agent label) var agents []*types.Issue if daemonClient != nil { resp, err := daemonClient.List(&rpc.ListArgs{ - IssueType: "agent", + Labels: []string{"gt:agent"}, }) if err != nil { 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) } } else { - agentType := types.TypeAgent filter := types.IssueFilter{ - IssueType: &agentType, + Labels: []string{"gt:agent"}, } var err error agents, err = store.SearchIssues(ctx, "", filter) @@ -756,6 +773,17 @@ func containsLabel(labels []string, label string) bool { 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. // Agent ID patterns: // - Town-level: - (e.g., gt-mayor) → role="mayor", rig="" diff --git a/cmd/bd/agent_routing_test.go b/cmd/bd/agent_routing_test.go index a1ebdd25..7ba73645 100644 --- a/cmd/bd/agent_routing_test.go +++ b/cmd/bd/agent_routing_test.go @@ -45,11 +45,11 @@ func TestAgentStateWithRouting(t *testing.T) { rigDBPath := filepath.Join(rigBeadsDir, "beads.db") 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{ ID: "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, RoleType: "polecat", Rig: "testrig", @@ -57,6 +57,9 @@ func TestAgentStateWithRouting(t *testing.T) { if err := rigStore.CreateIssue(ctx, agentBead, "test"); err != nil { 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 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") } - if result.Issue.IssueType != types.TypeAgent { - t.Errorf("Expected issue type %q, got %q", types.TypeAgent, result.Issue.IssueType) + if result.Issue.IssueType != types.TypeTask { + t.Errorf("Expected issue type %q, got %q", types.TypeTask, result.Issue.IssueType) } 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") 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{ ID: "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, RoleType: "witness", Rig: "test", @@ -148,6 +151,9 @@ func TestAgentHeartbeatWithRouting(t *testing.T) { if err := rigStore.CreateIssue(ctx, agentBead, "test"); err != nil { 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 routesContent := `{"prefix":"gt-","path":"rig"}` @@ -207,11 +213,11 @@ func TestAgentShowWithRouting(t *testing.T) { rigDBPath := filepath.Join(rigBeadsDir, "beads.db") 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{ ID: "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, RoleType: "crew", Rig: "myrig", @@ -219,6 +225,9 @@ func TestAgentShowWithRouting(t *testing.T) { if err := rigStore.CreateIssue(ctx, agentBead, "test"); err != nil { 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 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) } - if result.Issue.IssueType != types.TypeAgent { - t.Errorf("Expected issue type %q, got %q", types.TypeAgent, result.Issue.IssueType) + if result.Issue.IssueType != types.TypeTask { + 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) diff --git a/cmd/bd/create.go b/cmd/bd/create.go index 2f3cf59e..7d1dc38d 100644 --- a/cmd/bd/create.go +++ b/cmd/bd/create.go @@ -474,7 +474,15 @@ var createCmd = &cobra.Command{ } // 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 != "" { agentLabel := "role_type:" + issue.RoleType if err := store.AddLabel(ctx, issue.ID, agentLabel, actor); err != nil { diff --git a/cmd/bd/export_obsidian.go b/cmd/bd/export_obsidian.go index 82812ea6..bbde8778 100644 --- a/cmd/bd/export_obsidian.go +++ b/cmd/bd/export_obsidian.go @@ -33,6 +33,8 @@ var obsidianPriority = []string{ } // 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{ types.TypeBug: "#Bug", types.TypeFeature: "#Feature", @@ -43,10 +45,6 @@ var obsidianTypeTag = map[types.IssueType]string{ types.TypeMergeRequest: "#MergeRequest", types.TypeMolecule: "#Molecule", types.TypeGate: "#Gate", - types.TypeAgent: "#Agent", - types.TypeRole: "#Role", - types.TypeRig: "#Rig", - types.TypeConvoy: "#Convoy", types.TypeEvent: "#Event", } diff --git a/cmd/bd/merge_slot.go b/cmd/bd/merge_slot.go index df99eda3..4d02e415 100644 --- a/cmd/bd/merge_slot.go +++ b/cmd/bd/merge_slot.go @@ -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 and create cascading conflicts. -Each rig has one merge slot bead: -merge-slot (type=slot). +Each rig has one merge slot bead: -merge-slot (labeled gt:slot). The slot uses: - status=open: slot is available - status=in_progress: slot is held @@ -157,15 +157,15 @@ func runMergeSlotCreate(cmd *cobra.Command, args []string) error { // Create the merge slot bead title := "Merge Slot" description := "Exclusive access slot for serialized conflict resolution in the merge queue." - slotType := types.TypeSlot if daemonClient != nil { createArgs := &rpc.CreateArgs{ ID: slotID, Title: title, Description: description, - IssueType: string(slotType), - Priority: 0, // P0 - system infrastructure + IssueType: string(types.TypeTask), // Use task type; gt:slot label marks it as slot + Priority: 0, // P0 - system infrastructure + Labels: []string{"gt:slot"}, // Gas Town slot label } resp, err := daemonClient.Create(createArgs) if err != nil { @@ -179,13 +179,18 @@ func runMergeSlotCreate(cmd *cobra.Command, args []string) error { ID: slotID, Title: title, Description: description, - IssueType: slotType, + IssueType: types.TypeTask, // Use task type; gt:slot label marks it as slot Status: types.StatusOpen, Priority: 0, } if err := store.CreateIssue(ctx, issue, actor); err != nil { 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() } diff --git a/internal/rpc/server_issues_epics.go b/internal/rpc/server_issues_epics.go index eb0cf674..22986326 100644 --- a/internal/rpc/server_issues_epics.go +++ b/internal/rpc/server_issues_epics.go @@ -14,6 +14,16 @@ import ( "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.) // Matches the parseTimeFlag behavior in cmd/bd/list.go for CLI parity 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) - 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 != "" { label := "role_type:" + issue.RoleType 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 - // 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 - 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 != "" { // Remove any existing role_type:* labels first - existingLabels, _ := store.GetLabels(ctx, updateArgs.ID) - for _, l := range existingLabels { + for _, l := range issueLabels { if strings.HasPrefix(l, "role_type:") { _ = store.RemoveLabel(ctx, updateArgs.ID, l, actor) } @@ -612,8 +624,7 @@ func (s *Server) handleUpdate(req *Request) Response { } if updateArgs.Rig != nil && *updateArgs.Rig != "" { // Remove any existing rig:* labels first - existingLabels, _ := store.GetLabels(ctx, updateArgs.ID) - for _, l := range existingLabels { + for _, l := range issueLabels { if strings.HasPrefix(l, "rig:") { _ = store.RemoveLabel(ctx, updateArgs.ID, l, actor) } diff --git a/internal/storage/sqlite/queries.go b/internal/storage/sqlite/queries.go index 42014ba3..b665cd29 100644 --- a/internal/storage/sqlite/queries.go +++ b/internal/storage/sqlite/queries.go @@ -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 // 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, ` SELECT DISTINCT d.issue_id FROM dependencies d 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 = ? AND d.type = ? - AND i.issue_type = ? AND i.status != ? - `, id, types.DepTracks, types.TypeConvoy, types.StatusClosed) + `, id, types.DepTracks, types.StatusClosed) if err != nil { return fmt.Errorf("failed to find tracking convoys: %w", err) } diff --git a/internal/storage/sqlite/sqlite_test.go b/internal/storage/sqlite/sqlite_test.go index f469bab2..b0bcdf9e 100644 --- a/internal/storage/sqlite/sqlite_test.go +++ b/internal/storage/sqlite/sqlite_test.go @@ -1475,17 +1475,20 @@ func TestConvoyReactiveCompletion(t *testing.T) { ctx := context.Background() - // Create a convoy + // Create a convoy (using task type with gt:convoy label) convoy := &types.Issue{ Title: "Test Convoy", Status: types.StatusOpen, 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") if err != nil { 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 issue1 := &types.Issue{ diff --git a/internal/storage/sqlite/transaction.go b/internal/storage/sqlite/transaction.go index 17b2bb74..919dae39 100644 --- a/internal/storage/sqlite/transaction.go +++ b/internal/storage/sqlite/transaction.go @@ -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 // 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, ` SELECT DISTINCT d.issue_id FROM dependencies d 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 = ? AND d.type = ? - AND i.issue_type = ? AND i.status != ? - `, id, types.DepTracks, types.TypeConvoy, types.StatusClosed) + `, id, types.DepTracks, types.StatusClosed) if err != nil { return fmt.Errorf("failed to find tracking convoys: %w", err) } diff --git a/internal/types/types.go b/internal/types/types.go index 37d861e5..02467bae 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -412,6 +412,9 @@ func (s Status) IsValidWithCustom(customStatuses []string) bool { type IssueType string // 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 ( TypeBug IssueType = "bug" TypeFeature IssueType = "feature" @@ -422,18 +425,13 @@ const ( 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 (project container) - 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 value is valid (built-in types only) func (t IssueType) IsValid() bool { 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 false @@ -480,7 +478,7 @@ func (t IssueType) RequiredSections() []RequiredSection { {Heading: "## Success Criteria", Hint: "Define high-level success criteria"}, } default: - // Chore, message, molecule, gate, agent, role, convoy, event, merge-request + // Chore, message, molecule, gate, event, merge-request // have no required sections return nil } diff --git a/internal/types/types_test.go b/internal/types/types_test.go index 3c6644a2..393c74bf 100644 --- a/internal/types/types_test.go +++ b/internal/types/types_test.go @@ -401,12 +401,15 @@ func TestIssueTypeIsValid(t *testing.T) { {TypeTask, true}, {TypeEpic, true}, {TypeChore, true}, - {TypeAgent, true}, - {TypeRole, true}, - {TypeRig, true}, - {TypeConvoy, true}, + {TypeMessage, true}, + {TypeMergeRequest, true}, + {TypeMolecule, true}, + {TypeGate, 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(""), false}, } @@ -434,12 +437,9 @@ func TestIssueTypeRequiredSections(t *testing.T) { {TypeMessage, 0, ""}, {TypeMolecule, 0, ""}, {TypeGate, 0, ""}, - {TypeAgent, 0, ""}, - {TypeRole, 0, ""}, - {TypeRig, 0, ""}, - {TypeConvoy, 0, ""}, {TypeEvent, 0, ""}, {TypeMergeRequest, 0, ""}, + // Gas Town types (agent, role, rig, convoy, slot) have been removed } for _, tt := range tests { diff --git a/internal/ui/styles.go b/internal/ui/styles.go index 23e14dc4..5f923fc9 100644 --- a/internal/ui/styles.go +++ b/internal/ui/styles.go @@ -135,18 +135,8 @@ var ( Light: "", // standard text color Dark: "", } - ColorTypeAgent = lipgloss.AdaptiveColor{ - Light: "#59c2ff", // cyan - agent identity - Dark: "#59c2ff", - } - ColorTypeRole = lipgloss.AdaptiveColor{ - Light: "#7fd962", // green - role definition - Dark: "#7fd962", - } - ColorTypeRig = lipgloss.AdaptiveColor{ - Light: "#e6a756", // orange - rig identity (project container) - Dark: "#e6a756", - } + // Note: Gas Town-specific types (agent, role, rig) have been removed. + // Use labels (gt:agent, gt:role, gt:rig) with custom styling if needed. // === Issue ID Color === // IDs use standard text color - subtle, not attention-grabbing @@ -194,9 +184,7 @@ var ( TypeTaskStyle = lipgloss.NewStyle().Foreground(ColorTypeTask) TypeEpicStyle = lipgloss.NewStyle().Foreground(ColorTypeEpic) TypeChoreStyle = lipgloss.NewStyle().Foreground(ColorTypeChore) - TypeAgentStyle = lipgloss.NewStyle().Foreground(ColorTypeAgent) - TypeRoleStyle = lipgloss.NewStyle().Foreground(ColorTypeRole) - TypeRigStyle = lipgloss.NewStyle().Foreground(ColorTypeRig) + // Note: Gas Town-specific type styles (agent, role, rig) have been removed. ) // 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 -// 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 { switch issueType { case "bug": @@ -344,12 +333,6 @@ func RenderType(issueType string) string { return TypeEpicStyle.Render(issueType) case "chore": return TypeChoreStyle.Render(issueType) - case "agent": - return TypeAgentStyle.Render(issueType) - case "role": - return TypeRoleStyle.Render(issueType) - case "rig": - return TypeRigStyle.Render(issueType) default: return issueType } diff --git a/internal/ui/styles_test.go b/internal/ui/styles_test.go index 4091328d..e065ef61 100644 --- a/internal/ui/styles_test.go +++ b/internal/ui/styles_test.go @@ -92,9 +92,10 @@ func TestRenderTypeVariants(t *testing.T) { {"task", TypeTaskStyle.Render("task")}, {"epic", TypeEpicStyle.Render("epic")}, {"chore", TypeChoreStyle.Render("chore")}, - {"agent", TypeAgentStyle.Render("agent")}, - {"role", TypeRoleStyle.Render("role")}, - {"rig", TypeRigStyle.Render("rig")}, + // Gas Town types (agent, role, rig) have been removed - they now fall through to default + {"agent", "agent"}, // Falls through to default (no styling) + {"role", "role"}, // Falls through to default (no styling) + {"rig", "rig"}, // Falls through to default (no styling) {"custom", "custom"}, } for _, tc := range cases { diff --git a/internal/validation/bead_test.go b/internal/validation/bead_test.go index eaf96694..a8c303b8 100644 --- a/internal/validation/bead_test.go +++ b/internal/validation/bead_test.go @@ -126,10 +126,10 @@ func TestParseIssueType(t *testing.T) { {"merge-request type", "merge-request", types.TypeMergeRequest, false, ""}, {"molecule type", "molecule", types.TypeMolecule, false, ""}, {"gate type", "gate", types.TypeGate, false, ""}, - {"agent type", "agent", types.TypeAgent, false, ""}, - {"role type", "role", types.TypeRole, false, ""}, - {"rig type", "rig", types.TypeRig, false, ""}, + {"event type", "event", types.TypeEvent, 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) {"uppercase bug", "BUG", types.TypeTask, true, "invalid issue type"},