fix: align assigneeID format with hooked issue format (#946)

The polecat manager was using rig/name format while sling sets
rig/polecats/name. This caused gt polecat status to show 'done'
for working polecats because GetAssignedIssue couldn't find them.

Fixes gt-oohy
This commit is contained in:
Artem Bambalov
2026-01-26 04:05:44 +02:00
committed by GitHub
parent 6d18e0a88b
commit c94a2301eb
3 changed files with 57 additions and 55 deletions

View File

@@ -327,7 +327,7 @@ func (b *Beads) List(opts ListOptions) ([]*Issue, error) {
} }
// ListByAssignee returns all issues assigned to a specific assignee. // ListByAssignee returns all issues assigned to a specific assignee.
// The assignee is typically in the format "rig/polecatName" (e.g., "gastown/Toast"). // The assignee is typically in the format "rig/polecats/polecatName" (e.g., "gastown/polecats/Toast").
func (b *Beads) ListByAssignee(assignee string) ([]*Issue, error) { func (b *Beads) ListByAssignee(assignee string) ([]*Issue, error) {
return b.List(ListOptions{ return b.List(ListOptions{
Status: "all", // Include both open and closed for state derivation Status: "all", // Include both open and closed for state derivation

View File

@@ -90,9 +90,9 @@ func NewManager(r *rig.Rig, g *git.Git, t *tmux.Tmux) *Manager {
} }
// assigneeID returns the beads assignee identifier for a polecat. // assigneeID returns the beads assignee identifier for a polecat.
// Format: "rig/polecatName" (e.g., "gastown/Toast") // Format: "rig/polecats/polecatName" (e.g., "gastown/polecats/Toast")
func (m *Manager) assigneeID(name string) string { func (m *Manager) assigneeID(name string) string {
return fmt.Sprintf("%s/%s", m.rig.Name, name) return fmt.Sprintf("%s/polecats/%s", m.rig.Name, name)
} }
// agentBeadID returns the agent bead ID for a polecat. // agentBeadID returns the agent bead ID for a polecat.
@@ -265,8 +265,8 @@ func (m *Manager) buildBranchName(name, issue string) string {
// {year} and {month} // {year} and {month}
now := time.Now() now := time.Now()
vars["{year}"] = now.Format("06") // YY format vars["{year}"] = now.Format("06") // YY format
vars["{month}"] = now.Format("01") // MM format vars["{month}"] = now.Format("01") // MM format
// {name} // {name}
vars["{name}"] = name vars["{name}"] = name
@@ -1108,13 +1108,13 @@ func (m *Manager) CleanupStaleBranches() (int, error) {
// StalenessInfo contains details about a polecat's staleness. // StalenessInfo contains details about a polecat's staleness.
type StalenessInfo struct { type StalenessInfo struct {
Name string Name string
CommitsBehind int // How many commits behind origin/main CommitsBehind int // How many commits behind origin/main
HasActiveSession bool // Whether tmux session is running HasActiveSession bool // Whether tmux session is running
HasUncommittedWork bool // Whether there's uncommitted or unpushed work HasUncommittedWork bool // Whether there's uncommitted or unpushed work
AgentState string // From agent bead (empty if no bead) AgentState string // From agent bead (empty if no bead)
IsStale bool // Overall assessment: safe to clean up IsStale bool // Overall assessment: safe to clean up
Reason string // Why it's considered stale (or not) Reason string // Why it's considered stale (or not)
} }
// DetectStalePolecats identifies polecats that are candidates for cleanup. // DetectStalePolecats identifies polecats that are candidates for cleanup.

View File

@@ -135,7 +135,7 @@ func TestAssigneeID(t *testing.T) {
m := NewManager(r, git.NewGit(r.Path), nil) m := NewManager(r, git.NewGit(r.Path), nil)
id := m.assigneeID("Toast") id := m.assigneeID("Toast")
expected := "test-rig/Toast" expected := "test-rig/polecats/Toast"
if id != expected { if id != expected {
t.Errorf("assigneeID = %q, want %q", id, expected) t.Errorf("assigneeID = %q, want %q", id, expected)
} }
@@ -446,81 +446,83 @@ func TestAddWithOptions_AgentsMDFallback(t *testing.T) {
t.Errorf("AGENTS.md content = %q, want %q", gotContent, wantContent) t.Errorf("AGENTS.md content = %q, want %q", gotContent, wantContent)
} }
} }
// TestReconcilePoolWith tests all permutations of directory and session existence. // TestReconcilePoolWith tests all permutations of directory and session existence.
// This is the core allocation policy logic. // This is the core allocation policy logic.
// //
// Truth table: // Truth table:
// HasDir | HasSession | Result //
// -------|------------|------------------ // HasDir | HasSession | Result
// false | false | available (not in-use) // -------|------------|------------------
// true | false | in-use (normal finished polecat) // false | false | available (not in-use)
// false | true | orphan → kill session, available // true | false | in-use (normal finished polecat)
// true | true | in-use (normal working polecat) // false | true | orphan → kill session, available
// true | true | in-use (normal working polecat)
func TestReconcilePoolWith(t *testing.T) { func TestReconcilePoolWith(t *testing.T) {
t.Parallel() t.Parallel()
tests := []struct { tests := []struct {
name string name string
namesWithDirs []string namesWithDirs []string
namesWithSessions []string namesWithSessions []string
wantInUse []string // names that should be marked in-use wantInUse []string // names that should be marked in-use
wantOrphans []string // sessions that should be killed wantOrphans []string // sessions that should be killed
}{ }{
{ {
name: "no dirs, no sessions - all available", name: "no dirs, no sessions - all available",
namesWithDirs: []string{}, namesWithDirs: []string{},
namesWithSessions: []string{}, namesWithSessions: []string{},
wantInUse: []string{}, wantInUse: []string{},
wantOrphans: []string{}, wantOrphans: []string{},
}, },
{ {
name: "has dir, no session - in use", name: "has dir, no session - in use",
namesWithDirs: []string{"toast"}, namesWithDirs: []string{"toast"},
namesWithSessions: []string{}, namesWithSessions: []string{},
wantInUse: []string{"toast"}, wantInUse: []string{"toast"},
wantOrphans: []string{}, wantOrphans: []string{},
}, },
{ {
name: "no dir, has session - orphan killed", name: "no dir, has session - orphan killed",
namesWithDirs: []string{}, namesWithDirs: []string{},
namesWithSessions: []string{"nux"}, namesWithSessions: []string{"nux"},
wantInUse: []string{}, wantInUse: []string{},
wantOrphans: []string{"nux"}, wantOrphans: []string{"nux"},
}, },
{ {
name: "has dir, has session - in use", name: "has dir, has session - in use",
namesWithDirs: []string{"capable"}, namesWithDirs: []string{"capable"},
namesWithSessions: []string{"capable"}, namesWithSessions: []string{"capable"},
wantInUse: []string{"capable"}, wantInUse: []string{"capable"},
wantOrphans: []string{}, wantOrphans: []string{},
}, },
{ {
name: "mixed: one with dir, one orphan session", name: "mixed: one with dir, one orphan session",
namesWithDirs: []string{"toast"}, namesWithDirs: []string{"toast"},
namesWithSessions: []string{"toast", "nux"}, namesWithSessions: []string{"toast", "nux"},
wantInUse: []string{"toast"}, wantInUse: []string{"toast"},
wantOrphans: []string{"nux"}, wantOrphans: []string{"nux"},
}, },
{ {
name: "multiple dirs, no sessions", name: "multiple dirs, no sessions",
namesWithDirs: []string{"toast", "nux", "capable"}, namesWithDirs: []string{"toast", "nux", "capable"},
namesWithSessions: []string{}, namesWithSessions: []string{},
wantInUse: []string{"capable", "nux", "toast"}, wantInUse: []string{"capable", "nux", "toast"},
wantOrphans: []string{}, wantOrphans: []string{},
}, },
{ {
name: "multiple orphan sessions", name: "multiple orphan sessions",
namesWithDirs: []string{}, namesWithDirs: []string{},
namesWithSessions: []string{"slit", "rictus"}, namesWithSessions: []string{"slit", "rictus"},
wantInUse: []string{}, wantInUse: []string{},
wantOrphans: []string{"rictus", "slit"}, wantOrphans: []string{"rictus", "slit"},
}, },
{ {
name: "complex: dirs, valid sessions, orphan sessions", name: "complex: dirs, valid sessions, orphan sessions",
namesWithDirs: []string{"toast", "capable"}, namesWithDirs: []string{"toast", "capable"},
namesWithSessions: []string{"toast", "nux", "slit"}, namesWithSessions: []string{"toast", "nux", "slit"},
wantInUse: []string{"capable", "toast"}, wantInUse: []string{"capable", "toast"},
wantOrphans: []string{"nux", "slit"}, wantOrphans: []string{"nux", "slit"},
}, },
} }