From 0fc036a7b48ed3e428849a1de5d4a46b9c710631 Mon Sep 17 00:00:00 2001 From: Jon Gallant <2163001+jongio@users.noreply.github.com> Date: Fri, 24 Jul 2026 14:12:25 -0700 Subject: [PATCH] Add strict doctor status Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 340b4dd3-b4b3-4f1a-9163-66b16d96fa81 --- cmd/dispatch/cli.go | 69 ++++++++++++++++++++++++++++++++++------ cmd/dispatch/cli_test.go | 15 ++++++++- 2 files changed, 74 insertions(+), 10 deletions(-) diff --git a/cmd/dispatch/cli.go b/cmd/dispatch/cli.go index b1038cf..fe179f4 100644 --- a/cmd/dispatch/cli.go +++ b/cmd/dispatch/cli.go @@ -10,7 +10,6 @@ import ( "os/exec" "path/filepath" "runtime" - "slices" "strings" "time" @@ -117,13 +116,26 @@ func handleArgs(args []string, origStderr io.Writer, updateCh <-chan *update.Upd return true, cleanup, startupOptions{}, nil case "doctor": - if slices.Contains(args, "--json") { - if jErr := runDoctorJSON(os.Stdout); jErr != nil { + opts, dErr := parseDoctorArgs(args[i:]) + if dErr != nil { + fmt.Fprintf(os.Stderr, "doctor: %v\n", dErr) + return true, cleanup, startupOptions{}, dErr + } + var report doctorReport + if opts.JSON { + var jErr error + report, jErr = runDoctorJSON(os.Stdout) + if jErr != nil { fmt.Fprintf(os.Stderr, "doctor: %v\n", jErr) return true, cleanup, startupOptions{}, jErr } } else { - runDoctor(os.Stdout) + report = runDoctor(os.Stdout) + } + if opts.Strict && !report.OK { + err := errors.New("health checks failed") + fmt.Fprintf(os.Stderr, "doctor: %v\n", err) + return true, cleanup, startupOptions{}, err } showUpdateNotification(origStderr, updateCh) return true, cleanup, startupOptions{}, nil @@ -578,6 +590,7 @@ type doctorEntry struct { type doctorReport struct { Version string `json:"version"` OS string `json:"os"` + OK bool `json:"ok"` Config doctorEntry `json:"config"` SessionStore doctorEntry `json:"session_store"` SessionState doctorEntry `json:"session_state"` @@ -620,10 +633,45 @@ func collectDoctorReport() doctorReport { } r.SessionCount = doctorSessionCountFn() + r.OK = doctorEntryOK(r.Config) && + doctorEntryOK(r.SessionStore) && + doctorEntryOK(r.SessionState) && + doctorEntryOK(r.CopilotCLI) return r } +type doctorOptions struct { + JSON bool + Strict bool +} + +func parseDoctorArgs(args []string) (doctorOptions, error) { + var opts doctorOptions + rest := args + if len(rest) > 0 { + rest = rest[1:] + } + for _, arg := range rest { + switch arg { + case "--json": + opts.JSON = true + case "--strict": + opts.Strict = true + default: + if strings.HasPrefix(arg, "-") { + return opts, fmt.Errorf("unknown flag: %s", arg) + } + return opts, fmt.Errorf("doctor does not take positional arguments, got %q", arg) + } + } + return opts, nil +} + +func doctorEntryOK(e doctorEntry) bool { + return e.err == nil && e.Status == statusFound +} + // defaultCopilotVersion runs the Copilot CLI binary with --version and returns // the first line of its output. It returns "unknown" when the binary cannot be // run, keeping the doctor command non-fatal when the CLI misbehaves. @@ -677,7 +725,7 @@ func pathStatus(path string, wantDir bool) string { return statusFound } -func runDoctor(w io.Writer) { +func runDoctor(w io.Writer) doctorReport { if w == nil { w = io.Discard } @@ -700,20 +748,23 @@ func runDoctor(w io.Writer) { fmt.Fprintf(w, "Copilot CLI version: not detected\n") } fmt.Fprintf(w, "Stored sessions: %d\n", r.SessionCount) + fmt.Fprintf(w, "OK: %t\n", r.OK) + return r } // runDoctorJSON writes the diagnostics as a single JSON object followed by a // newline. -func runDoctorJSON(w io.Writer) error { +func runDoctorJSON(w io.Writer) (doctorReport, error) { if w == nil { w = io.Discard } - b, err := json.MarshalIndent(collectDoctorReport(), "", " ") + r := collectDoctorReport() + b, err := json.MarshalIndent(r, "", " ") if err != nil { - return err + return r, err } fmt.Fprintf(w, "%s\n", b) - return nil + return r, nil } // writeDoctorLine renders one diagnostic entry as human-readable text. diff --git a/cmd/dispatch/cli_test.go b/cmd/dispatch/cli_test.go index fb1c391..dba59a3 100644 --- a/cmd/dispatch/cli_test.go +++ b/cmd/dispatch/cli_test.go @@ -234,7 +234,7 @@ func TestRunDoctorJSON_Shape(t *testing.T) { t.Cleanup(func() { doctorSessionCountFn = origCount }) var buf bytes.Buffer - if err := runDoctorJSON(&buf); err != nil { + if _, err := runDoctorJSON(&buf); err != nil { t.Fatalf("runDoctorJSON: %v", err) } @@ -262,6 +262,19 @@ func TestRunDoctorJSON_Shape(t *testing.T) { } } +func TestParseDoctorArgs(t *testing.T) { + opts, err := parseDoctorArgs([]string{"doctor", "--json", "--strict"}) + if err != nil { + t.Fatalf("parseDoctorArgs: %v", err) + } + if !opts.JSON || !opts.Strict { + t.Fatalf("opts = %+v, want json and strict", opts) + } + if _, err := parseDoctorArgs([]string{"doctor", "--bogus"}); err == nil { + t.Fatal("expected error for unknown flag") + } +} + func TestDefaultCopilotVersion_EmptyBinary(t *testing.T) { if got := defaultCopilotVersion(""); got != "" { t.Errorf("defaultCopilotVersion(\"\") = %q, want empty", got)