Files
gastown/internal/doctor/sparse_checkout_check.go
Ryan Snodgrass e1f2bb8b4b feat(ui): import comprehensive UX system from beads
Import beads' UX design system into gastown:

- Add internal/ui/ package with Ayu theme colors and semantic styling
  - styles.go: AdaptiveColor definitions for light/dark mode
  - terminal.go: TTY detection, NO_COLOR/CLICOLOR support
  - markdown.go: Glamour rendering with agent mode bypass
  - pager.go: Smart paging with GT_PAGER support

- Add colorized help output (internal/cmd/help.go)
  - Group headers in accent color
  - Command names styled for scannability
  - Flag types and defaults muted

- Add gt thanks command (internal/cmd/thanks.go)
  - Contributor display with same logic as bd thanks
  - Styled with Ayu theme colors

- Update gt doctor to match bd doctor UX
  - Category grouping (Core, Infrastructure, Rig, Patrol, etc.)
  - Semantic icons (✓ ⚠ ✖) with Ayu colors
  - Tree connectors for detail lines
  - Summary line with pass/warn/fail counts
  - Warnings section at end with numbered issues

- Migrate existing styles to use ui package
  - internal/style/style.go uses ui.ColorPass etc.
  - internal/tui/feed/styles.go uses ui package colors

Co-Authored-By: SageOx <ox@sageox.ai>
2026-01-09 22:46:06 -08:00

132 lines
3.9 KiB
Go

package doctor
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/steveyegge/gastown/internal/git"
)
// SparseCheckoutCheck verifies that git clones/worktrees have sparse checkout configured
// to exclude Claude Code context files from source repos. This ensures source repo settings
// and instructions don't override Gas Town agent configuration.
// Excluded files: .claude/, CLAUDE.md, CLAUDE.local.md, .mcp.json
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 excludes Claude context files (.claude/, CLAUDE.md, etc.)",
CheckCategory: CategoryRig,
},
},
}
}
// 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 context files",
}
}
// 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 context files.
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)
}
// Check if any excluded files remain (untracked or modified files won't be removed by git read-tree)
if remaining := git.CheckExcludedFilesExist(repoPath); len(remaining) > 0 {
relPath, _ := filepath.Rel(c.rigPath, repoPath)
return fmt.Errorf("sparse checkout configured for %s but these files still exist: %s\n"+
"These files are untracked or modified and were not removed by git.\n"+
"Please manually remove or revert these files in %s",
relPath, strings.Join(remaining, ", "), repoPath)
}
}
return nil
}