From e5de1db5853080a2b1ffa9b6662e49f90d64a6c4 Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Wed, 3 Dec 2025 11:41:00 -0800 Subject: [PATCH] feat(doctor): add --output flag to export diagnostics (bd-9cc) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add ability to save doctor diagnostics to a JSON file for historical analysis and bug reporting. The export includes timestamp and platform info (OS, Go version, SQLite version) for tracking intermittent issues. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- cmd/bd/doctor.go | 57 +++++++++++++++++++++++++++++++++++++------ cmd/bd/doctor/perf.go | 10 +++++--- 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/cmd/bd/doctor.go b/cmd/bd/doctor.go index eda1de76..50cbe765 100644 --- a/cmd/bd/doctor.go +++ b/cmd/bd/doctor.go @@ -41,16 +41,19 @@ type doctorCheck struct { } type doctorResult struct { - Path string `json:"path"` - Checks []doctorCheck `json:"checks"` - OverallOK bool `json:"overall_ok"` - CLIVersion string `json:"cli_version"` + Path string `json:"path"` + Checks []doctorCheck `json:"checks"` + OverallOK bool `json:"overall_ok"` + CLIVersion string `json:"cli_version"` + Timestamp string `json:"timestamp,omitempty"` // bd-9cc: ISO8601 timestamp for historical tracking + Platform map[string]string `json:"platform,omitempty"` // bd-9cc: platform info for debugging } var ( doctorFix bool doctorYes bool - doctorDryRun bool // bd-a5z: preview fixes without applying + doctorDryRun bool // bd-a5z: preview fixes without applying + doctorOutput string // bd-9cc: export diagnostics to file perfMode bool checkHealthMode bool ) @@ -86,6 +89,10 @@ Performance Mode (--perf): - Generates CPU profile for analysis - Outputs shareable report for bug reports +Export Mode (--output): + Save diagnostics to a JSON file for historical analysis and bug reporting. + Includes timestamp and platform info for tracking intermittent issues. + Examples: bd doctor # Check current directory bd doctor /path/to/repo # Check specific repository @@ -93,7 +100,8 @@ Examples: bd doctor --fix # Automatically fix issues (with confirmation) bd doctor --fix --yes # Automatically fix issues (no confirmation) bd doctor --dry-run # Preview what --fix would do without making changes - bd doctor --perf # Performance diagnostics`, + bd doctor --perf # Performance diagnostics + bd doctor --output diagnostics.json # Export diagnostics to file`, Run: func(cmd *cobra.Command, args []string) { // Use global jsonOutput set by PersistentPreRun @@ -134,10 +142,26 @@ Examples: result = runDiagnostics(absPath) } + // bd-9cc: Add timestamp and platform info for export + if doctorOutput != "" || jsonOutput { + result.Timestamp = time.Now().UTC().Format(time.RFC3339) + result.Platform = doctor.CollectPlatformInfo(absPath) + } + + // bd-9cc: Export to file if --output specified + if doctorOutput != "" { + if err := exportDiagnostics(result, doctorOutput); err != nil { + fmt.Fprintf(os.Stderr, "Error: failed to export diagnostics: %v\n", err) + os.Exit(1) + } + fmt.Printf("✓ Diagnostics exported to %s\n", doctorOutput) + } + // Output results if jsonOutput { outputJSON(result) - } else { + } else if doctorOutput == "" { + // Only print to console if not exporting (to avoid duplicate output) printDiagnostics(result) } @@ -1166,6 +1190,24 @@ func fetchLatestGitHubRelease() (string, error) { return version, nil } +// exportDiagnostics writes the doctor result to a JSON file (bd-9cc) +func exportDiagnostics(result doctorResult, outputPath string) error { + // #nosec G304 - outputPath is a user-provided flag value for file generation + f, err := os.Create(outputPath) + if err != nil { + return fmt.Errorf("failed to create output file: %w", err) + } + defer f.Close() + + encoder := json.NewEncoder(f) + encoder.SetIndent("", " ") + if err := encoder.Encode(result); err != nil { + return fmt.Errorf("failed to write JSON: %w", err) + } + + return nil +} + func printDiagnostics(result doctorResult) { // Print header fmt.Println("\nDiagnostics") @@ -2590,4 +2632,5 @@ func init() { rootCmd.AddCommand(doctorCmd) doctorCmd.Flags().BoolVar(&perfMode, "perf", false, "Run performance diagnostics and generate CPU profile") doctorCmd.Flags().BoolVar(&checkHealthMode, "check-health", false, "Quick health check for git hooks (silent on success)") + doctorCmd.Flags().StringVarP(&doctorOutput, "output", "o", "", "Export diagnostics to JSON file (bd-9cc)") } diff --git a/cmd/bd/doctor/perf.go b/cmd/bd/doctor/perf.go index a1542e54..43a3bfeb 100644 --- a/cmd/bd/doctor/perf.go +++ b/cmd/bd/doctor/perf.go @@ -36,7 +36,7 @@ func RunPerformanceDiagnostics(path string) { } // Collect platform info - platformInfo := collectPlatformInfo(dbPath) + platformInfo := CollectPlatformInfo(path) fmt.Printf("\nPlatform: %s\n", platformInfo["os_arch"]) fmt.Printf("Go: %s\n", platformInfo["go_version"]) fmt.Printf("SQLite: %s\n", platformInfo["sqlite_version"]) @@ -95,7 +95,9 @@ func RunPerformanceDiagnostics(path string) { fmt.Printf(" go tool pprof -http=:8080 %s\n\n", profilePath) } -func collectPlatformInfo(dbPath string) map[string]string { +// CollectPlatformInfo gathers platform information for diagnostics. +// bd-9cc: Exported for use by --output flag. +func CollectPlatformInfo(path string) map[string]string { info := make(map[string]string) // OS and architecture @@ -104,7 +106,9 @@ func collectPlatformInfo(dbPath string) map[string]string { // Go version info["go_version"] = runtime.Version() - // SQLite version + // SQLite version - try to find database + beadsDir := filepath.Join(path, ".beads") + dbPath := filepath.Join(beadsDir, beads.CanonicalDatabaseName) db, err := sql.Open("sqlite3", "file:"+dbPath+"?mode=ro") if err == nil { defer db.Close()