From 3ec30d5eb808f0f3989d099fcc166edb2bf1ade2 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Wed, 7 Oct 2026 16:47:55 +0000 Subject: [PATCH] Present help failures through the common diagnostic path --- docs/roadmap/remediation.md | 3 +++ execute.go | 4 ++-- execute_external_test.go | 24 ++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/docs/roadmap/remediation.md b/docs/roadmap/remediation.md index fec6ccf..72d0702 100644 --- a/docs/roadmap/remediation.md +++ b/docs/roadmap/remediation.md @@ -54,6 +54,9 @@ architecture. The issues below affect behavior within the implemented structure. - R-03 complete: standard inspection handlers check cancellation before runtime construction/acquisition, after acquisition, and before prepared report delivery. +- R-04 complete: every help entry form finalizes report failures through the + common diagnostic presentation path once. + ## Findings and priority P1 means a supported invocation is blocked or its report content is corrupted. diff --git a/execute.go b/execute.go index 325ce15..84cb5b5 100644 --- a/execute.go +++ b/execute.go @@ -29,13 +29,13 @@ func (a *App) Execute(ctx context.Context, args []string, streams Streams) Outco } command, ok := a.commandAt(parsed.Path) if !ok || command.payload == nil { - return a.renderHelpOutcome(parsed.Path, streams) + return a.present(a.renderHelpOutcome(parsed.Path, streams), parsed.Path, streams) } if parsed.HelpRequested || !command.payload.hasRun() { if _, err := command.payload.prepare(parsed.FlagValues, parsed.Operands, false); err != nil { return a.present(Usage(err), parsed.Path, streams) } - return a.renderHelpOutcome(parsed.Path, streams) + return a.present(a.renderHelpOutcome(parsed.Path, streams), parsed.Path, streams) } buildInput, err := command.payload.prepare(parsed.FlagValues, parsed.Operands, true) if err != nil { diff --git a/execute_external_test.go b/execute_external_test.go index 21caa71..cf24111 100644 --- a/execute_external_test.go +++ b/execute_external_test.go @@ -413,3 +413,27 @@ func (shortWriter) Write(data []byte) (int, error) { } return len(data) - 1, nil } + +func TestHelpEntryFormsPresentReportAndDiagnosticFailures(t *testing.T) { + group, err := commandkit.Group("notes", "Notes metadata.") + if err != nil { + t.Fatal(err) + } + app, err := commandkit.New(commandkit.Identity{Name: "example"}, group) + if err != nil { + t.Fatal(err) + } + reportErr := errors.New("report failed") + diagnosticErr := errors.New("diagnostic failed") + for _, args := range [][]string{nil, {"notes"}, {"--help"}, {"-h"}, {"notes", "--help"}, {"help"}, {"help", "notes"}} { + var diagnostics bytes.Buffer + result := app.Execute(context.Background(), args, commandkit.Streams{Out: failingWriter{reportErr}, Err: &diagnostics}) + if result.Kind != commandkit.OutcomeOperational || !result.ReportIncomplete || !errors.Is(result.Cause, reportErr) || !errors.Is(result.DeliveryError, reportErr) || result.DiagnosticDelivery != commandkit.DeliveryComplete || strings.Count(diagnostics.String(), reportErr.Error()) != 1 { + t.Fatalf("%v: result=%#v diagnostics=%q", args, result, diagnostics.String()) + } + result = app.Execute(context.Background(), args, commandkit.Streams{Out: failingWriter{reportErr}, Err: failingWriter{diagnosticErr}}) + if result.DiagnosticDelivery != commandkit.DeliveryIncomplete || !errors.Is(result.Cause, reportErr) || !errors.Is(result.DeliveryError, reportErr) || !errors.Is(result.DeliveryError, diagnosticErr) { + t.Fatalf("%v: result=%#v", args, result) + } + } +}