From 2b90f51d0cef803bc62efab3395f2d80af8a1f49 Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Mon, 29 Dec 2025 20:58:17 -0800 Subject: [PATCH] feat: add doctor check for issues.jsonl git tracking (GH#796) Safeguard for users with global gitignore patterns like *.jsonl that could cause issues.jsonl to be ignored, breaking bd sync. The check runs git check-ignore and warns if issues.jsonl would be ignored by any gitignore rule (global, parent directory, etc). --- cmd/bd/doctor.go | 5 +++++ cmd/bd/doctor/gitignore.go | 46 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/cmd/bd/doctor.go b/cmd/bd/doctor.go index 49ea34df..2a24af52 100644 --- a/cmd/bd/doctor.go +++ b/cmd/bd/doctor.go @@ -401,6 +401,11 @@ func runDiagnostics(path string) doctorResult { result.Checks = append(result.Checks, gitignoreCheck) // Don't fail overall check for gitignore, just warn + // Check 14a: issues.jsonl tracking (catches global gitignore conflicts) + issuesTrackingCheck := convertWithCategory(doctor.CheckIssuesTracking(), doctor.CategoryGit) + result.Checks = append(result.Checks, issuesTrackingCheck) + // Don't fail overall check for tracking issues, just warn + // Check 15: Git merge driver configuration mergeDriverCheck := convertWithCategory(doctor.CheckMergeDriver(path), doctor.CategoryGit) result.Checks = append(result.Checks, mergeDriverCheck) diff --git a/cmd/bd/doctor/gitignore.go b/cmd/bd/doctor/gitignore.go index 676447d8..b363da9f 100644 --- a/cmd/bd/doctor/gitignore.go +++ b/cmd/bd/doctor/gitignore.go @@ -2,6 +2,7 @@ package doctor import ( "os" + "os/exec" "path/filepath" "strings" ) @@ -120,3 +121,48 @@ func FixGitignore() error { return nil } + +// CheckIssuesTracking verifies that issues.jsonl is tracked by git. +// This catches cases where global gitignore patterns (e.g., *.jsonl) would +// cause issues.jsonl to be ignored, breaking bd sync. +func CheckIssuesTracking() DoctorCheck { + issuesPath := filepath.Join(".beads", "issues.jsonl") + + // First check if the file exists + if _, err := os.Stat(issuesPath); os.IsNotExist(err) { + // File doesn't exist yet - not an error, bd init may not have been run + return DoctorCheck{ + Name: "Issues Tracking", + Status: "ok", + Message: "No issues.jsonl yet (will be created on first issue)", + } + } + + // Check if git considers this file ignored + // git check-ignore exits 0 if ignored, 1 if not ignored, 128 if error + cmd := exec.Command("git", "check-ignore", "-q", issuesPath) + err := cmd.Run() + + if err == nil { + // Exit code 0 means the file IS ignored - this is bad + // Get details about what's ignoring it + detailCmd := exec.Command("git", "check-ignore", "-v", issuesPath) + output, _ := detailCmd.Output() + detail := strings.TrimSpace(string(output)) + + return DoctorCheck{ + Name: "Issues Tracking", + Status: "warning", + Message: "issues.jsonl is ignored by git (bd sync will fail)", + Detail: detail, + Fix: "Check global gitignore: git config --global core.excludesfile", + } + } + + // Exit code 1 means not ignored (good), any other error we ignore + return DoctorCheck{ + Name: "Issues Tracking", + Status: "ok", + Message: "issues.jsonl is tracked by git", + } +}