From 85dd150d750b8799997ed95d8aa0857a0e7fa3a1 Mon Sep 17 00:00:00 2001 From: joshuavial Date: Thu, 8 Jan 2026 21:53:05 +1300 Subject: [PATCH] Skip dot dirs when scanning polecats --- internal/cmd/hooks.go | 2 +- internal/cmd/polecat_dotdir_test.go | 201 ++++++++++++++++++++++++++++ internal/cmd/session.go | 3 + internal/cmd/up.go | 3 + 4 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 internal/cmd/polecat_dotdir_test.go diff --git a/internal/cmd/hooks.go b/internal/cmd/hooks.go index 53cec8b5..5fdff05c 100644 --- a/internal/cmd/hooks.go +++ b/internal/cmd/hooks.go @@ -139,7 +139,7 @@ func discoverHooks(townRoot string) ([]HookInfo, error) { polecatsDir := filepath.Join(rigPath, "polecats") if polecats, err := os.ReadDir(polecatsDir); err == nil { for _, p := range polecats { - if p.IsDir() { + if p.IsDir() && !strings.HasPrefix(p.Name(), ".") { locations = append(locations, struct { path string agent string diff --git a/internal/cmd/polecat_dotdir_test.go b/internal/cmd/polecat_dotdir_test.go new file mode 100644 index 00000000..b19fec3d --- /dev/null +++ b/internal/cmd/polecat_dotdir_test.go @@ -0,0 +1,201 @@ +package cmd + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/spf13/cobra" + "github.com/steveyegge/gastown/internal/config" +) + +func TestDiscoverHooksSkipsPolecatDotDirs(t *testing.T) { + townRoot := setupTestTownForDotDir(t) + rigPath := filepath.Join(townRoot, "gastown") + + settingsPath := filepath.Join(rigPath, "polecats", ".claude", ".claude", "settings.json") + if err := os.MkdirAll(filepath.Dir(settingsPath), 0755); err != nil { + t.Fatalf("mkdir settings dir: %v", err) + } + + settings := `{"hooks":{"SessionStart":[{"matcher":"*","hooks":[{"type":"Stop","command":"echo hi"}]}]}}` + if err := os.WriteFile(settingsPath, []byte(settings), 0644); err != nil { + t.Fatalf("write settings: %v", err) + } + + hooks, err := discoverHooks(townRoot) + if err != nil { + t.Fatalf("discoverHooks: %v", err) + } + + if len(hooks) != 0 { + t.Fatalf("expected no hooks, got %d", len(hooks)) + } +} + +func TestStartPolecatsWithWorkSkipsDotDirs(t *testing.T) { + townRoot := setupTestTownForDotDir(t) + rigName := "gastown" + rigPath := filepath.Join(townRoot, rigName) + + addRigEntry(t, townRoot, rigName) + + if err := os.MkdirAll(filepath.Join(rigPath, "polecats", ".claude"), 0755); err != nil { + t.Fatalf("mkdir .claude polecat: %v", err) + } + if err := os.MkdirAll(filepath.Join(rigPath, "polecats", "toast"), 0755); err != nil { + t.Fatalf("mkdir polecat: %v", err) + } + + binDir := t.TempDir() + bdScript := `#!/bin/sh +if [ "$1" = "--no-daemon" ]; then + shift +fi +cmd="$1" +case "$cmd" in + list) + if [ "$(basename "$PWD")" = ".claude" ]; then + echo '[{"id":"gt-1"}]' + else + echo '[]' + fi + exit 0 + ;; + *) + exit 0 + ;; +esac +` + writeScript(t, binDir, "bd", bdScript) + + tmuxScript := `#!/bin/sh +if [ "$1" = "has-session" ]; then + echo "tmux error" 1>&2 + exit 1 +fi +exit 0 +` + writeScript(t, binDir, "tmux", tmuxScript) + + t.Setenv("PATH", fmt.Sprintf("%s:%s", binDir, os.Getenv("PATH"))) + + cwd, err := os.Getwd() + if err != nil { + t.Fatalf("getwd: %v", err) + } + t.Cleanup(func() { _ = os.Chdir(cwd) }) + if err := os.Chdir(townRoot); err != nil { + t.Fatalf("chdir town root: %v", err) + } + + started, errs := startPolecatsWithWork(townRoot, rigName) + + if len(started) != 0 { + t.Fatalf("expected no polecats started, got %v", started) + } + if len(errs) != 0 { + t.Fatalf("expected no errors, got %v", errs) + } +} + +func TestRunSessionCheckSkipsDotDirs(t *testing.T) { + townRoot := setupTestTownForDotDir(t) + rigName := "gastown" + rigPath := filepath.Join(townRoot, rigName) + + addRigEntry(t, townRoot, rigName) + + if err := os.MkdirAll(filepath.Join(rigPath, "polecats", ".claude"), 0755); err != nil { + t.Fatalf("mkdir .claude polecat: %v", err) + } + + binDir := t.TempDir() + tmuxScript := `#!/bin/sh +if [ "$1" = "has-session" ]; then + echo "can't find session" 1>&2 + exit 1 +fi +exit 0 +` + writeScript(t, binDir, "tmux", tmuxScript) + t.Setenv("PATH", fmt.Sprintf("%s:%s", binDir, os.Getenv("PATH"))) + + cwd, err := os.Getwd() + if err != nil { + t.Fatalf("getwd: %v", err) + } + t.Cleanup(func() { _ = os.Chdir(cwd) }) + if err := os.Chdir(townRoot); err != nil { + t.Fatalf("chdir town root: %v", err) + } + + output := captureStdout(t, func() { + if err := runSessionCheck(&cobra.Command{}, []string{rigName}); err != nil { + t.Fatalf("runSessionCheck: %v", err) + } + }) + + if strings.Contains(output, ".claude") { + t.Fatalf("expected .claude to be ignored, output:\n%s", output) + } +} + +func addRigEntry(t *testing.T, townRoot, rigName string) { + t.Helper() + + rigsPath := filepath.Join(townRoot, "mayor", "rigs.json") + rigsConfig, err := config.LoadRigsConfig(rigsPath) + if err != nil { + t.Fatalf("load rigs.json: %v", err) + } + if rigsConfig.Rigs == nil { + rigsConfig.Rigs = make(map[string]config.RigEntry) + } + rigsConfig.Rigs[rigName] = config.RigEntry{ + GitURL: "file:///dev/null", + AddedAt: time.Now(), + } + if err := config.SaveRigsConfig(rigsPath, rigsConfig); err != nil { + t.Fatalf("save rigs.json: %v", err) + } +} + +func setupTestTownForDotDir(t *testing.T) string { + t.Helper() + + townRoot := t.TempDir() + + mayorDir := filepath.Join(townRoot, "mayor") + if err := os.MkdirAll(mayorDir, 0755); err != nil { + t.Fatalf("mkdir mayor: %v", err) + } + + rigsPath := filepath.Join(mayorDir, "rigs.json") + rigsConfig := &config.RigsConfig{ + Version: 1, + Rigs: make(map[string]config.RigEntry), + } + if err := config.SaveRigsConfig(rigsPath, rigsConfig); err != nil { + t.Fatalf("save rigs.json: %v", err) + } + + beadsDir := filepath.Join(townRoot, ".beads") + if err := os.MkdirAll(beadsDir, 0755); err != nil { + t.Fatalf("mkdir .beads: %v", err) + } + + return townRoot +} + +func writeScript(t *testing.T, dir, name, content string) { + t.Helper() + + path := filepath.Join(dir, name) + if err := os.WriteFile(path, []byte(content), 0755); err != nil { + t.Fatalf("write %s: %v", name, err) + } +} diff --git a/internal/cmd/session.go b/internal/cmd/session.go index f2bf6b2d..8ed40cb5 100644 --- a/internal/cmd/session.go +++ b/internal/cmd/session.go @@ -649,6 +649,9 @@ func runSessionCheck(cmd *cobra.Command, args []string) error { if !entry.IsDir() { continue } + if strings.HasPrefix(entry.Name(), ".") { + continue + } polecatName := entry.Name() sessionName := fmt.Sprintf("gt-%s-%s", r.Name, polecatName) totalChecked++ diff --git a/internal/cmd/up.go b/internal/cmd/up.go index ae310740..75293eed 100644 --- a/internal/cmd/up.go +++ b/internal/cmd/up.go @@ -451,6 +451,9 @@ func startPolecatsWithWork(townRoot, rigName string) ([]string, map[string]error if !entry.IsDir() { continue } + if strings.HasPrefix(entry.Name(), ".") { + continue + } polecatName := entry.Name() polecatPath := filepath.Join(polecatsDir, polecatName)