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
264 lines
8.5 KiB
Go
264 lines
8.5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/steveyegge/beads/internal/types"
|
|
)
|
|
|
|
// TestAgentStateWithRouting tests that bd agent state respects routes.jsonl
|
|
// for cross-repo agent resolution. This is a regression test for the bug where
|
|
// bd agent state failed to find agents in routed databases while bd show worked.
|
|
func TestAgentStateWithRouting(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
// Create temp directory structure:
|
|
// tmpDir/
|
|
// .beads/
|
|
// beads.db (town database)
|
|
// routes.jsonl (routing config)
|
|
// rig/
|
|
// .beads/
|
|
// beads.db (rig database with agent)
|
|
tmpDir := t.TempDir()
|
|
|
|
// Create town .beads directory
|
|
townBeadsDir := filepath.Join(tmpDir, ".beads")
|
|
if err := os.MkdirAll(townBeadsDir, 0755); err != nil {
|
|
t.Fatalf("Failed to create town beads dir: %v", err)
|
|
}
|
|
|
|
// Create rig .beads directory
|
|
rigBeadsDir := filepath.Join(tmpDir, "rig", ".beads")
|
|
if err := os.MkdirAll(rigBeadsDir, 0755); err != nil {
|
|
t.Fatalf("Failed to create rig beads dir: %v", err)
|
|
}
|
|
|
|
// Initialize town database using helper (prefix without trailing hyphen)
|
|
townDBPath := filepath.Join(townBeadsDir, "beads.db")
|
|
townStore := newTestStoreWithPrefix(t, townDBPath, "hq")
|
|
|
|
// Initialize rig database using helper (prefix without trailing hyphen)
|
|
rigDBPath := filepath.Join(rigBeadsDir, "beads.db")
|
|
rigStore := newTestStoreWithPrefix(t, rigDBPath, "gt")
|
|
|
|
// 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.TypeTask, // Use task type; gt:agent label marks it as agent
|
|
Status: types.StatusOpen,
|
|
RoleType: "polecat",
|
|
Rig: "testrig",
|
|
}
|
|
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"}`
|
|
routesPath := filepath.Join(townBeadsDir, "routes.jsonl")
|
|
if err := os.WriteFile(routesPath, []byte(routesContent), 0644); err != nil {
|
|
t.Fatalf("Failed to write routes.jsonl: %v", err)
|
|
}
|
|
|
|
// Set up global state for routing to work
|
|
oldDbPath := dbPath
|
|
dbPath = townDBPath
|
|
t.Cleanup(func() { dbPath = oldDbPath })
|
|
|
|
// Test the routed resolution
|
|
result, err := resolveAndGetIssueWithRouting(ctx, townStore, "gt-testrig-polecat-test")
|
|
if err != nil {
|
|
t.Fatalf("resolveAndGetIssueWithRouting failed: %v", err)
|
|
}
|
|
if result == nil {
|
|
t.Fatal("resolveAndGetIssueWithRouting returned nil result")
|
|
}
|
|
defer result.Close()
|
|
|
|
if result.Issue == nil {
|
|
t.Fatal("resolveAndGetIssueWithRouting returned nil issue")
|
|
}
|
|
|
|
if result.Issue.ID != "gt-testrig-polecat-test" {
|
|
t.Errorf("Expected issue ID %q, got %q", "gt-testrig-polecat-test", result.Issue.ID)
|
|
}
|
|
|
|
if !result.Routed {
|
|
t.Error("Expected result.Routed to be true for cross-repo lookup")
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
// TestNeedsRoutingFunction tests the needsRouting function
|
|
func TestNeedsRoutingFunction(t *testing.T) {
|
|
// Without dbPath set, needsRouting should return false
|
|
oldDbPath := dbPath
|
|
dbPath = ""
|
|
t.Cleanup(func() { dbPath = oldDbPath })
|
|
|
|
if needsRouting("any-id") {
|
|
t.Error("needsRouting should return false when dbPath is empty")
|
|
}
|
|
}
|
|
|
|
// TestAgentHeartbeatWithRouting tests that bd agent heartbeat respects routes.jsonl
|
|
func TestAgentHeartbeatWithRouting(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
tmpDir := t.TempDir()
|
|
|
|
// Create town .beads directory
|
|
townBeadsDir := filepath.Join(tmpDir, ".beads")
|
|
if err := os.MkdirAll(townBeadsDir, 0755); err != nil {
|
|
t.Fatalf("Failed to create town beads dir: %v", err)
|
|
}
|
|
|
|
// Create rig .beads directory
|
|
rigBeadsDir := filepath.Join(tmpDir, "rig", ".beads")
|
|
if err := os.MkdirAll(rigBeadsDir, 0755); err != nil {
|
|
t.Fatalf("Failed to create rig beads dir: %v", err)
|
|
}
|
|
|
|
// Initialize databases (prefix without trailing hyphen)
|
|
townDBPath := filepath.Join(townBeadsDir, "beads.db")
|
|
townStore := newTestStoreWithPrefix(t, townDBPath, "hq")
|
|
|
|
rigDBPath := filepath.Join(rigBeadsDir, "beads.db")
|
|
rigStore := newTestStoreWithPrefix(t, rigDBPath, "gt")
|
|
|
|
// 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.TypeTask, // Use task type; gt:agent label marks it as agent
|
|
Status: types.StatusOpen,
|
|
RoleType: "witness",
|
|
Rig: "test",
|
|
}
|
|
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"}`
|
|
routesPath := filepath.Join(townBeadsDir, "routes.jsonl")
|
|
if err := os.WriteFile(routesPath, []byte(routesContent), 0644); err != nil {
|
|
t.Fatalf("Failed to write routes.jsonl: %v", err)
|
|
}
|
|
|
|
// Set up global state
|
|
oldDbPath := dbPath
|
|
dbPath = townDBPath
|
|
t.Cleanup(func() { dbPath = oldDbPath })
|
|
|
|
// Test that we can resolve the agent from the town directory
|
|
result, err := resolveAndGetIssueWithRouting(ctx, townStore, "gt-test-witness")
|
|
if err != nil {
|
|
t.Fatalf("resolveAndGetIssueWithRouting failed: %v", err)
|
|
}
|
|
if result == nil || result.Issue == nil {
|
|
t.Fatal("resolveAndGetIssueWithRouting returned nil")
|
|
}
|
|
defer result.Close()
|
|
|
|
if result.Issue.ID != "gt-test-witness" {
|
|
t.Errorf("Expected issue ID %q, got %q", "gt-test-witness", result.Issue.ID)
|
|
}
|
|
|
|
if !result.Routed {
|
|
t.Error("Expected result.Routed to be true")
|
|
}
|
|
|
|
t.Logf("Successfully resolved agent %s via routing for heartbeat test", result.Issue.ID)
|
|
}
|
|
|
|
// TestAgentShowWithRouting tests that bd agent show respects routes.jsonl
|
|
func TestAgentShowWithRouting(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
tmpDir := t.TempDir()
|
|
|
|
// Create town .beads directory
|
|
townBeadsDir := filepath.Join(tmpDir, ".beads")
|
|
if err := os.MkdirAll(townBeadsDir, 0755); err != nil {
|
|
t.Fatalf("Failed to create town beads dir: %v", err)
|
|
}
|
|
|
|
// Create rig .beads directory
|
|
rigBeadsDir := filepath.Join(tmpDir, "rig", ".beads")
|
|
if err := os.MkdirAll(rigBeadsDir, 0755); err != nil {
|
|
t.Fatalf("Failed to create rig beads dir: %v", err)
|
|
}
|
|
|
|
// Initialize databases (prefix without trailing hyphen)
|
|
townDBPath := filepath.Join(townBeadsDir, "beads.db")
|
|
townStore := newTestStoreWithPrefix(t, townDBPath, "hq")
|
|
|
|
rigDBPath := filepath.Join(rigBeadsDir, "beads.db")
|
|
rigStore := newTestStoreWithPrefix(t, rigDBPath, "gt")
|
|
|
|
// 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.TypeTask, // Use task type; gt:agent label marks it as agent
|
|
Status: types.StatusOpen,
|
|
RoleType: "crew",
|
|
Rig: "myrig",
|
|
}
|
|
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"}`
|
|
routesPath := filepath.Join(townBeadsDir, "routes.jsonl")
|
|
if err := os.WriteFile(routesPath, []byte(routesContent), 0644); err != nil {
|
|
t.Fatalf("Failed to write routes.jsonl: %v", err)
|
|
}
|
|
|
|
// Set up global state
|
|
oldDbPath := dbPath
|
|
dbPath = townDBPath
|
|
t.Cleanup(func() { dbPath = oldDbPath })
|
|
|
|
// Test that we can resolve the agent from the town directory
|
|
result, err := resolveAndGetIssueWithRouting(ctx, townStore, "gt-myrig-crew-alice")
|
|
if err != nil {
|
|
t.Fatalf("resolveAndGetIssueWithRouting failed: %v", err)
|
|
}
|
|
if result == nil || result.Issue == nil {
|
|
t.Fatal("resolveAndGetIssueWithRouting returned nil")
|
|
}
|
|
defer result.Close()
|
|
|
|
if result.Issue.ID != "gt-myrig-crew-alice" {
|
|
t.Errorf("Expected issue ID %q, got %q", "gt-myrig-crew-alice", result.Issue.ID)
|
|
}
|
|
|
|
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)
|
|
}
|