Types: - Polecat: worker agent with state, clone, branch, issue - State: idle/active/working/done/stuck - Summary: concise status view Manager operations: - Add: clone rig, create branch, init state - Remove: delete polecat (checks for uncommitted changes) - List: enumerate all polecats - Get: retrieve specific polecat State management: - SetState: update lifecycle state - AssignIssue: assign work (sets StateWorking) - ClearIssue: remove assignment (sets StateIdle) - Wake: idle → active - Sleep: active → idle State persisted to polecats/<name>/state.json Closes gt-u1j.8 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
314 lines
6.5 KiB
Go
314 lines
6.5 KiB
Go
package polecat
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/steveyegge/gastown/internal/git"
|
|
"github.com/steveyegge/gastown/internal/rig"
|
|
)
|
|
|
|
func TestStateIsAvailable(t *testing.T) {
|
|
tests := []struct {
|
|
state State
|
|
available bool
|
|
}{
|
|
{StateIdle, true},
|
|
{StateActive, true},
|
|
{StateWorking, false},
|
|
{StateDone, false},
|
|
{StateStuck, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
if got := tt.state.IsAvailable(); got != tt.available {
|
|
t.Errorf("%s.IsAvailable() = %v, want %v", tt.state, got, tt.available)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStateIsWorking(t *testing.T) {
|
|
tests := []struct {
|
|
state State
|
|
working bool
|
|
}{
|
|
{StateIdle, false},
|
|
{StateActive, false},
|
|
{StateWorking, true},
|
|
{StateDone, false},
|
|
{StateStuck, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
if got := tt.state.IsWorking(); got != tt.working {
|
|
t.Errorf("%s.IsWorking() = %v, want %v", tt.state, got, tt.working)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPolecatSummary(t *testing.T) {
|
|
p := &Polecat{
|
|
Name: "Toast",
|
|
State: StateWorking,
|
|
Issue: "gt-abc",
|
|
}
|
|
|
|
summary := p.Summary()
|
|
if summary.Name != "Toast" {
|
|
t.Errorf("Name = %q, want Toast", summary.Name)
|
|
}
|
|
if summary.State != StateWorking {
|
|
t.Errorf("State = %v, want StateWorking", summary.State)
|
|
}
|
|
if summary.Issue != "gt-abc" {
|
|
t.Errorf("Issue = %q, want gt-abc", summary.Issue)
|
|
}
|
|
}
|
|
|
|
func TestListEmpty(t *testing.T) {
|
|
root := t.TempDir()
|
|
r := &rig.Rig{
|
|
Name: "test-rig",
|
|
Path: root,
|
|
}
|
|
m := NewManager(r, git.NewGit(root))
|
|
|
|
polecats, err := m.List()
|
|
if err != nil {
|
|
t.Fatalf("List: %v", err)
|
|
}
|
|
if len(polecats) != 0 {
|
|
t.Errorf("polecats count = %d, want 0", len(polecats))
|
|
}
|
|
}
|
|
|
|
func TestGetNotFound(t *testing.T) {
|
|
root := t.TempDir()
|
|
r := &rig.Rig{
|
|
Name: "test-rig",
|
|
Path: root,
|
|
}
|
|
m := NewManager(r, git.NewGit(root))
|
|
|
|
_, err := m.Get("nonexistent")
|
|
if err != ErrPolecatNotFound {
|
|
t.Errorf("Get = %v, want ErrPolecatNotFound", err)
|
|
}
|
|
}
|
|
|
|
func TestRemoveNotFound(t *testing.T) {
|
|
root := t.TempDir()
|
|
r := &rig.Rig{
|
|
Name: "test-rig",
|
|
Path: root,
|
|
}
|
|
m := NewManager(r, git.NewGit(root))
|
|
|
|
err := m.Remove("nonexistent")
|
|
if err != ErrPolecatNotFound {
|
|
t.Errorf("Remove = %v, want ErrPolecatNotFound", err)
|
|
}
|
|
}
|
|
|
|
func TestPolecatDir(t *testing.T) {
|
|
r := &rig.Rig{
|
|
Name: "test-rig",
|
|
Path: "/home/user/ai/test-rig",
|
|
}
|
|
m := NewManager(r, git.NewGit(r.Path))
|
|
|
|
dir := m.polecatDir("Toast")
|
|
expected := "/home/user/ai/test-rig/polecats/Toast"
|
|
if dir != expected {
|
|
t.Errorf("polecatDir = %q, want %q", dir, expected)
|
|
}
|
|
}
|
|
|
|
func TestStateFile(t *testing.T) {
|
|
r := &rig.Rig{
|
|
Name: "test-rig",
|
|
Path: "/home/user/ai/test-rig",
|
|
}
|
|
m := NewManager(r, git.NewGit(r.Path))
|
|
|
|
file := m.stateFile("Toast")
|
|
expected := "/home/user/ai/test-rig/polecats/Toast/state.json"
|
|
if file != expected {
|
|
t.Errorf("stateFile = %q, want %q", file, expected)
|
|
}
|
|
}
|
|
|
|
func TestStatePersistence(t *testing.T) {
|
|
root := t.TempDir()
|
|
polecatDir := filepath.Join(root, "polecats", "Test")
|
|
if err := os.MkdirAll(polecatDir, 0755); err != nil {
|
|
t.Fatalf("mkdir: %v", err)
|
|
}
|
|
|
|
r := &rig.Rig{
|
|
Name: "test-rig",
|
|
Path: root,
|
|
}
|
|
m := NewManager(r, git.NewGit(root))
|
|
|
|
// Save state
|
|
polecat := &Polecat{
|
|
Name: "Test",
|
|
Rig: "test-rig",
|
|
State: StateWorking,
|
|
ClonePath: polecatDir,
|
|
Issue: "gt-xyz",
|
|
}
|
|
if err := m.saveState(polecat); err != nil {
|
|
t.Fatalf("saveState: %v", err)
|
|
}
|
|
|
|
// Load state
|
|
loaded, err := m.loadState("Test")
|
|
if err != nil {
|
|
t.Fatalf("loadState: %v", err)
|
|
}
|
|
|
|
if loaded.Name != "Test" {
|
|
t.Errorf("Name = %q, want Test", loaded.Name)
|
|
}
|
|
if loaded.State != StateWorking {
|
|
t.Errorf("State = %v, want StateWorking", loaded.State)
|
|
}
|
|
if loaded.Issue != "gt-xyz" {
|
|
t.Errorf("Issue = %q, want gt-xyz", loaded.Issue)
|
|
}
|
|
}
|
|
|
|
func TestListWithPolecats(t *testing.T) {
|
|
root := t.TempDir()
|
|
|
|
// Create some polecat directories with state files
|
|
for _, name := range []string{"Toast", "Cheedo"} {
|
|
polecatDir := filepath.Join(root, "polecats", name)
|
|
if err := os.MkdirAll(polecatDir, 0755); err != nil {
|
|
t.Fatalf("mkdir: %v", err)
|
|
}
|
|
}
|
|
|
|
r := &rig.Rig{
|
|
Name: "test-rig",
|
|
Path: root,
|
|
}
|
|
m := NewManager(r, git.NewGit(root))
|
|
|
|
polecats, err := m.List()
|
|
if err != nil {
|
|
t.Fatalf("List: %v", err)
|
|
}
|
|
if len(polecats) != 2 {
|
|
t.Errorf("polecats count = %d, want 2", len(polecats))
|
|
}
|
|
}
|
|
|
|
func TestSetState(t *testing.T) {
|
|
root := t.TempDir()
|
|
polecatDir := filepath.Join(root, "polecats", "Test")
|
|
if err := os.MkdirAll(polecatDir, 0755); err != nil {
|
|
t.Fatalf("mkdir: %v", err)
|
|
}
|
|
|
|
r := &rig.Rig{
|
|
Name: "test-rig",
|
|
Path: root,
|
|
}
|
|
m := NewManager(r, git.NewGit(root))
|
|
|
|
// Initial state
|
|
if err := m.saveState(&Polecat{Name: "Test", State: StateIdle}); err != nil {
|
|
t.Fatalf("saveState: %v", err)
|
|
}
|
|
|
|
// Update state
|
|
if err := m.SetState("Test", StateActive); err != nil {
|
|
t.Fatalf("SetState: %v", err)
|
|
}
|
|
|
|
// Verify
|
|
polecat, err := m.Get("Test")
|
|
if err != nil {
|
|
t.Fatalf("Get: %v", err)
|
|
}
|
|
if polecat.State != StateActive {
|
|
t.Errorf("State = %v, want StateActive", polecat.State)
|
|
}
|
|
}
|
|
|
|
func TestAssignIssue(t *testing.T) {
|
|
root := t.TempDir()
|
|
polecatDir := filepath.Join(root, "polecats", "Test")
|
|
if err := os.MkdirAll(polecatDir, 0755); err != nil {
|
|
t.Fatalf("mkdir: %v", err)
|
|
}
|
|
|
|
r := &rig.Rig{
|
|
Name: "test-rig",
|
|
Path: root,
|
|
}
|
|
m := NewManager(r, git.NewGit(root))
|
|
|
|
// Initial state
|
|
if err := m.saveState(&Polecat{Name: "Test", State: StateIdle}); err != nil {
|
|
t.Fatalf("saveState: %v", err)
|
|
}
|
|
|
|
// Assign issue
|
|
if err := m.AssignIssue("Test", "gt-abc"); err != nil {
|
|
t.Fatalf("AssignIssue: %v", err)
|
|
}
|
|
|
|
// Verify
|
|
polecat, err := m.Get("Test")
|
|
if err != nil {
|
|
t.Fatalf("Get: %v", err)
|
|
}
|
|
if polecat.Issue != "gt-abc" {
|
|
t.Errorf("Issue = %q, want gt-abc", polecat.Issue)
|
|
}
|
|
if polecat.State != StateWorking {
|
|
t.Errorf("State = %v, want StateWorking", polecat.State)
|
|
}
|
|
}
|
|
|
|
func TestClearIssue(t *testing.T) {
|
|
root := t.TempDir()
|
|
polecatDir := filepath.Join(root, "polecats", "Test")
|
|
if err := os.MkdirAll(polecatDir, 0755); err != nil {
|
|
t.Fatalf("mkdir: %v", err)
|
|
}
|
|
|
|
r := &rig.Rig{
|
|
Name: "test-rig",
|
|
Path: root,
|
|
}
|
|
m := NewManager(r, git.NewGit(root))
|
|
|
|
// Initial state with issue
|
|
if err := m.saveState(&Polecat{Name: "Test", State: StateWorking, Issue: "gt-abc"}); err != nil {
|
|
t.Fatalf("saveState: %v", err)
|
|
}
|
|
|
|
// Clear issue
|
|
if err := m.ClearIssue("Test"); err != nil {
|
|
t.Fatalf("ClearIssue: %v", err)
|
|
}
|
|
|
|
// Verify
|
|
polecat, err := m.Get("Test")
|
|
if err != nil {
|
|
t.Fatalf("Get: %v", err)
|
|
}
|
|
if polecat.Issue != "" {
|
|
t.Errorf("Issue = %q, want empty", polecat.Issue)
|
|
}
|
|
if polecat.State != StateIdle {
|
|
t.Errorf("State = %v, want StateIdle", polecat.State)
|
|
}
|
|
}
|