package doctor import ( "fmt" "os" "path/filepath" "github.com/steveyegge/gastown/internal/git" ) // SparseCheckoutCheck verifies that git clones/worktrees have sparse checkout configured // to exclude .claude/ from source repos. This ensures source repo settings don't override // Gas Town agent settings. type SparseCheckoutCheck struct { FixableCheck rigPath string affectedRepos []string // repos missing sparse checkout configuration } // NewSparseCheckoutCheck creates a new sparse checkout check. func NewSparseCheckoutCheck() *SparseCheckoutCheck { return &SparseCheckoutCheck{ FixableCheck: FixableCheck{ BaseCheck: BaseCheck{ CheckName: "sparse-checkout", CheckDescription: "Verify sparse checkout is configured to exclude .claude/", }, }, } } // Run checks if sparse checkout is configured for all git repos in the rig. func (c *SparseCheckoutCheck) Run(ctx *CheckContext) *CheckResult { c.rigPath = ctx.RigPath() if c.rigPath == "" { return &CheckResult{ Name: c.Name(), Status: StatusError, Message: "No rig specified", } } c.affectedRepos = nil // Check all git repo locations repoPaths := []string{ filepath.Join(c.rigPath, "mayor", "rig"), filepath.Join(c.rigPath, "refinery", "rig"), } // Add crew clones crewDir := filepath.Join(c.rigPath, "crew") if entries, err := os.ReadDir(crewDir); err == nil { for _, entry := range entries { if entry.IsDir() && entry.Name() != "README.md" { repoPaths = append(repoPaths, filepath.Join(crewDir, entry.Name())) } } } // Add polecat worktrees polecatDir := filepath.Join(c.rigPath, "polecats") if entries, err := os.ReadDir(polecatDir); err == nil { for _, entry := range entries { if entry.IsDir() { repoPaths = append(repoPaths, filepath.Join(polecatDir, entry.Name())) } } } for _, repoPath := range repoPaths { // Skip if not a git repo if _, err := os.Stat(filepath.Join(repoPath, ".git")); os.IsNotExist(err) { continue } // Check if sparse checkout is configured (not just if .claude/ exists) if !git.IsSparseCheckoutConfigured(repoPath) { c.affectedRepos = append(c.affectedRepos, repoPath) } } if len(c.affectedRepos) == 0 { return &CheckResult{ Name: c.Name(), Status: StatusOK, Message: "All repos have sparse checkout configured to exclude .claude/", } } // Build details with relative paths var details []string for _, repoPath := range c.affectedRepos { relPath, _ := filepath.Rel(c.rigPath, repoPath) if relPath == "" { relPath = repoPath } details = append(details, relPath) } return &CheckResult{ Name: c.Name(), Status: StatusError, Message: fmt.Sprintf("%d repo(s) missing sparse checkout configuration", len(c.affectedRepos)), Details: details, FixHint: "Run 'gt doctor --fix' to configure sparse checkout", } } // Fix configures sparse checkout for affected repos to exclude .claude/. func (c *SparseCheckoutCheck) Fix(ctx *CheckContext) error { for _, repoPath := range c.affectedRepos { if err := git.ConfigureSparseCheckout(repoPath); err != nil { relPath, _ := filepath.Rel(c.rigPath, repoPath) return fmt.Errorf("failed to configure sparse checkout for %s: %w", relPath, err) } } return nil }