feat(polecat): add repo path to worktrees for LLM ergonomics (GH#283)

Changes polecat worktree structure from:
  polecats/<name>/
to:
  polecats/<name>/<rigname>/

This gives Claude Code agents a recognizable directory name (e.g., tidepool/)
in their cwd instead of just the polecat name, preventing confusion about
which repo they are working in.

Key changes:
- Add clonePath() method to manager.go and session_manager.go for the actual
  git worktree path, keeping polecatDir() for existence checks
- Update Add(), RepairWorktree(), Remove() to use new structure
- Update daemon lifecycle and restart code for new paths
- Update witness handlers to detect both structures
- Update doctor checks (rig_check, branch_check, config_check,
  claude_settings_check) for backward compatibility
- All code includes fallback to old structure for existing polecats

Fixes #283

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gastown/crew/max
2026-01-08 23:15:51 -08:00
committed by Steve Yegge
parent c8c97fdf64
commit 9b2f4a7652
10 changed files with 189 additions and 52 deletions

View File

@@ -468,12 +468,20 @@ func (c *CloneDivergenceCheck) findAllClones(townRoot string) []string {
}
}
// Add polecats
// Add polecats (handle both new and old structures)
// New structure: polecats/<name>/<rigname>/
// Old structure: polecats/<name>/
rigName := entry.Name()
polecatsPath := filepath.Join(rigPath, "polecats")
if polecatEntries, err := os.ReadDir(polecatsPath); err == nil {
for _, polecat := range polecatEntries {
if polecat.IsDir() && !strings.HasPrefix(polecat.Name(), ".") {
path := filepath.Join(polecatsPath, polecat.Name())
// Try new structure first
path := filepath.Join(polecatsPath, polecat.Name(), rigName)
if !c.isGitRepo(path) {
// Fall back to old structure
path = filepath.Join(polecatsPath, polecat.Name())
}
if c.isGitRepo(path) {
clones = append(clones, path)
}

View File

@@ -288,15 +288,23 @@ func (c *ClaudeSettingsCheck) findSettingsFiles(townRoot string) []staleSettings
if !pcEntry.IsDir() || pcEntry.Name() == ".claude" {
continue
}
pcWrongSettings := filepath.Join(polecatsDir, pcEntry.Name(), ".claude", "settings.json")
if fileExists(pcWrongSettings) {
files = append(files, staleSettingsInfo{
path: pcWrongSettings,
agentType: "polecat",
rigName: rigName,
sessionName: fmt.Sprintf("gt-%s-%s", rigName, pcEntry.Name()),
wrongLocation: true,
})
// Check for wrong settings in both structures:
// Old structure: polecats/<name>/.claude/settings.json
// New structure: polecats/<name>/<rigname>/.claude/settings.json
wrongPaths := []string{
filepath.Join(polecatsDir, pcEntry.Name(), ".claude", "settings.json"),
filepath.Join(polecatsDir, pcEntry.Name(), rigName, ".claude", "settings.json"),
}
for _, pcWrongSettings := range wrongPaths {
if fileExists(pcWrongSettings) {
files = append(files, staleSettingsInfo{
path: pcWrongSettings,
agentType: "polecat",
rigName: rigName,
sessionName: fmt.Sprintf("gt-%s-%s", rigName, pcEntry.Name()),
wrongLocation: true,
})
}
}
}
}

View File

@@ -460,14 +460,24 @@ func (c *SessionHookCheck) findSettingsFiles(townRoot string) []string {
}
}
// Polecats
// Polecats (handle both new and old structures)
// New structure: polecats/<name>/<rigname>/.claude/settings.json
// Old structure: polecats/<name>/.claude/settings.json
rigName := filepath.Base(rig)
polecatsPath := filepath.Join(rig, "polecats")
if polecatEntries, err := os.ReadDir(polecatsPath); err == nil {
for _, polecat := range polecatEntries {
if polecat.IsDir() && !strings.HasPrefix(polecat.Name(), ".") {
polecatSettings := filepath.Join(polecatsPath, polecat.Name(), ".claude", "settings.json")
// Try new structure first
polecatSettings := filepath.Join(polecatsPath, polecat.Name(), rigName, ".claude", "settings.json")
if _, err := os.Stat(polecatSettings); err == nil {
files = append(files, polecatSettings)
} else {
// Fall back to old structure
polecatSettings = filepath.Join(polecatsPath, polecat.Name(), ".claude", "settings.json")
if _, err := os.Stat(polecatSettings); err == nil {
files = append(files, polecatSettings)
}
}
}
}

View File

@@ -698,14 +698,24 @@ func (c *PolecatClonesValidCheck) Run(ctx *CheckContext) *CheckResult {
var warnings []string
validCount := 0
// Get rig name for new structure path detection
rigName := ctx.RigName
for _, entry := range entries {
if !entry.IsDir() || strings.HasPrefix(entry.Name(), ".") {
continue
}
polecatPath := filepath.Join(polecatsDir, entry.Name())
polecatName := entry.Name()
// Determine worktree path (handle both new and old structures)
// New structure: polecats/<name>/<rigname>/
// Old structure: polecats/<name>/
polecatPath := filepath.Join(polecatsDir, polecatName, rigName)
if _, err := os.Stat(polecatPath); os.IsNotExist(err) {
polecatPath = filepath.Join(polecatsDir, polecatName)
}
// Check if it's a git clone
gitPath := filepath.Join(polecatPath, ".git")
if _, err := os.Stat(gitPath); os.IsNotExist(err) {