package doctor import ( "os" "path/filepath" "strings" ) // GitignoreTemplate is the canonical .beads/.gitignore content const GitignoreTemplate = `# SQLite databases *.db *.db?* *.db-journal *.db-wal *.db-shm # Daemon runtime files daemon.lock daemon.log daemon.pid bd.sock # Legacy database files db.sqlite bd.db # Merge artifacts (temporary files from 3-way merge) beads.base.jsonl beads.base.meta.json beads.left.jsonl beads.left.meta.json beads.right.jsonl beads.right.meta.json # Keep JSONL exports and config (source of truth for git) !issues.jsonl !metadata.json !config.json ` // requiredPatterns are patterns that MUST be in .beads/.gitignore var requiredPatterns = []string{ "beads.base.jsonl", "beads.left.jsonl", "beads.right.jsonl", "beads.base.meta.json", "beads.left.meta.json", "beads.right.meta.json", "*.db?*", } // CheckGitignore checks if .beads/.gitignore is up to date func CheckGitignore() DoctorCheck { gitignorePath := filepath.Join(".beads", ".gitignore") // Check if file exists content, err := os.ReadFile(gitignorePath) // #nosec G304 -- path is hardcoded if err != nil { return DoctorCheck{ Name: "Gitignore", Status: "warning", Message: ".beads/.gitignore not found", Fix: "Run: bd init (safe to re-run) or bd doctor --fix", } } // Check for required patterns contentStr := string(content) var missing []string for _, pattern := range requiredPatterns { if !strings.Contains(contentStr, pattern) { missing = append(missing, pattern) } } if len(missing) > 0 { return DoctorCheck{ Name: "Gitignore", Status: "warning", Message: "Outdated .beads/.gitignore (missing merge artifact patterns)", Detail: "Missing: " + strings.Join(missing, ", "), Fix: "Run: bd doctor --fix or bd init (safe to re-run)", } } return DoctorCheck{ Name: "Gitignore", Status: "ok", Message: "Up to date", } } // FixGitignore updates .beads/.gitignore to the current template func FixGitignore() error { gitignorePath := filepath.Join(".beads", ".gitignore") // If file exists and is read-only, fix permissions first if info, err := os.Stat(gitignorePath); err == nil { if info.Mode().Perm()&0200 == 0 { // No write permission for owner if err := os.Chmod(gitignorePath, 0600); err != nil { return err } } } // Write canonical template with secure file permissions if err := os.WriteFile(gitignorePath, []byte(GitignoreTemplate), 0600); err != nil { return err } // Ensure permissions are set correctly (some systems respect umask) if err := os.Chmod(gitignorePath, 0600); err != nil { return err } return nil }