feat(doctor): add --output flag to export diagnostics (bd-9cc)

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 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-03 11:41:00 -08:00
parent e1e3427d9b
commit e5de1db585
2 changed files with 57 additions and 10 deletions

View File

@@ -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)")
}

View File

@@ -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()