diff --git a/internal/polecat/manager_test.go b/internal/polecat/manager_test.go index 5ee3e531..0f0abb73 100644 --- a/internal/polecat/manager_test.go +++ b/internal/polecat/manager_test.go @@ -529,8 +529,9 @@ func TestReconcilePoolWith(t *testing.T) { defer func() { _ = os.RemoveAll(tmpDir) }() // Create rig and manager (nil tmux for unit test) + // Use "myrig" which hashes to mad-max theme r := &rig.Rig{ - Name: "testrig", + Name: "myrig", Path: tmpDir, } m := NewManager(r, nil, nil) @@ -591,8 +592,9 @@ func TestReconcilePoolWith_Allocation(t *testing.T) { } defer func() { _ = os.RemoveAll(tmpDir) }() + // Use "myrig" which hashes to mad-max theme r := &rig.Rig{ - Name: "testrig", + Name: "myrig", Path: tmpDir, } m := NewManager(r, nil, nil) @@ -627,8 +629,9 @@ func TestReconcilePoolWith_OrphanDoesNotBlockAllocation(t *testing.T) { } defer func() { _ = os.RemoveAll(tmpDir) }() + // Use "myrig" which hashes to mad-max theme r := &rig.Rig{ - Name: "testrig", + Name: "myrig", Path: tmpDir, } m := NewManager(r, nil, nil) diff --git a/internal/polecat/namepool.go b/internal/polecat/namepool.go index 23209289..92400620 100644 --- a/internal/polecat/namepool.go +++ b/internal/polecat/namepool.go @@ -103,7 +103,7 @@ type NamePool struct { func NewNamePool(rigPath, rigName string) *NamePool { return &NamePool{ RigName: rigName, - Theme: DefaultTheme, + Theme: ThemeForRig(rigName), InUse: make(map[string]bool), OverflowNext: DefaultPoolSize + 1, MaxSize: DefaultPoolSize, @@ -352,6 +352,21 @@ func ListThemes() []string { return themes } +// ThemeForRig returns a deterministic theme for a rig based on its name. +// This provides variety across rigs without requiring manual configuration. +func ThemeForRig(rigName string) string { + themes := ListThemes() + if len(themes) == 0 { + return DefaultTheme + } + // Hash using prime multiplier for better distribution + var hash uint32 + for _, b := range []byte(rigName) { + hash = hash*31 + uint32(b) + } + return themes[hash%uint32(len(themes))] +} + // GetThemeNames returns the names in a specific theme. func GetThemeNames(theme string) ([]string, error) { if names, ok := BuiltinThemes[theme]; ok { diff --git a/internal/polecat/namepool_test.go b/internal/polecat/namepool_test.go index 85814b91..0b447887 100644 --- a/internal/polecat/namepool_test.go +++ b/internal/polecat/namepool_test.go @@ -13,7 +13,7 @@ func TestNamePool_Allocate(t *testing.T) { } defer func() { _ = os.RemoveAll(tmpDir) }() - pool := NewNamePool(tmpDir, "testrig") + pool := NewNamePoolWithConfig(tmpDir, "testrig", "mad-max", nil, DefaultPoolSize) // First allocation should be first themed name (furiosa) name, err := pool.Allocate() @@ -41,7 +41,7 @@ func TestNamePool_Release(t *testing.T) { } defer func() { _ = os.RemoveAll(tmpDir) }() - pool := NewNamePool(tmpDir, "testrig") + pool := NewNamePoolWithConfig(tmpDir, "testrig", "mad-max", nil, DefaultPoolSize) // Allocate first two name1, _ := pool.Allocate() @@ -68,7 +68,7 @@ func TestNamePool_PrefersOrder(t *testing.T) { } defer func() { _ = os.RemoveAll(tmpDir) }() - pool := NewNamePool(tmpDir, "testrig") + pool := NewNamePoolWithConfig(tmpDir, "testrig", "mad-max", nil, DefaultPoolSize) // Allocate first 5 for i := 0; i < 5; i++ { @@ -209,7 +209,7 @@ func TestNamePool_Reconcile(t *testing.T) { } defer func() { _ = os.RemoveAll(tmpDir) }() - pool := NewNamePool(tmpDir, "testrig") + pool := NewNamePoolWithConfig(tmpDir, "testrig", "mad-max", nil, DefaultPoolSize) // Simulate existing polecats from filesystem existing := []string{"slit", "valkyrie", "some-other-name"} @@ -234,7 +234,7 @@ func TestNamePool_IsPoolName(t *testing.T) { } defer func() { _ = os.RemoveAll(tmpDir) }() - pool := NewNamePool(tmpDir, "testrig") + pool := NewNamePoolWithConfig(tmpDir, "testrig", "mad-max", nil, DefaultPoolSize) tests := []struct { name string @@ -263,7 +263,7 @@ func TestNamePool_ActiveNames(t *testing.T) { } defer func() { _ = os.RemoveAll(tmpDir) }() - pool := NewNamePool(tmpDir, "testrig") + pool := NewNamePoolWithConfig(tmpDir, "testrig", "mad-max", nil, DefaultPoolSize) pool.Allocate() // furiosa pool.Allocate() // nux @@ -287,7 +287,7 @@ func TestNamePool_MarkInUse(t *testing.T) { } defer func() { _ = os.RemoveAll(tmpDir) }() - pool := NewNamePool(tmpDir, "testrig") + pool := NewNamePoolWithConfig(tmpDir, "testrig", "mad-max", nil, DefaultPoolSize) // Mark some slots as in use pool.MarkInUse("dementus") @@ -417,7 +417,7 @@ func TestNamePool_Reset(t *testing.T) { } defer func() { _ = os.RemoveAll(tmpDir) }() - pool := NewNamePool(tmpDir, "testrig") + pool := NewNamePoolWithConfig(tmpDir, "testrig", "mad-max", nil, DefaultPoolSize) // Allocate several names for i := 0; i < 10; i++ { @@ -441,3 +441,24 @@ func TestNamePool_Reset(t *testing.T) { t.Errorf("expected furiosa after reset, got %s", name) } } + +func TestThemeForRig(t *testing.T) { + // Different rigs should get different themes (with high probability) + themes := make(map[string]bool) + for _, rigName := range []string{"gastown", "beads", "myproject", "webapp"} { + themes[ThemeForRig(rigName)] = true + } + // Should have at least 2 different themes across 4 rigs + if len(themes) < 2 { + t.Errorf("expected variety in themes, got only %d unique theme(s)", len(themes)) + } +} + +func TestThemeForRigDeterministic(t *testing.T) { + // Same rig name should always get same theme + theme1 := ThemeForRig("myrig") + theme2 := ThemeForRig("myrig") + if theme1 != theme2 { + t.Errorf("theme not deterministic: got %q and %q", theme1, theme2) + } +}