From d33f1b3c9d955954aba65948de599701b52b7061 Mon Sep 17 00:00:00 2001 From: anmarhindi <153752112+anmarhindi@users.noreply.github.com> Date: Thu, 14 May 2026 17:03:08 +0200 Subject: [PATCH] fix: suppress misleading "Did you mean" suggestions --- pkg/cmd/suggest.go | 19 +++++++++-- pkg/cmd/suggest_test.go | 70 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 pkg/cmd/suggest_test.go diff --git a/pkg/cmd/suggest.go b/pkg/cmd/suggest.go index b4b637c..a7efa3e 100644 --- a/pkg/cmd/suggest.go +++ b/pkg/cmd/suggest.go @@ -98,10 +98,22 @@ func jaroWinkler(a, b string) float64 { return jaroDist + 0.1*prefixMatch*(1.0-jaroDist) } +// suggestionThreshold is the minimum jaro-winkler similarity required before we +// will print a "Did you mean" suggestion. Below this, the closest match is too +// dissimilar to be a useful guess, so we stay silent rather than mislead. +// 0.7 matches the boostThreshold used by jaroWinkler above — the prefix boost +// only kicks in past that, so it's a natural "plausibly the same word" cutoff. +const suggestionThreshold = 0.7 + // suggestCommand takes a list of commands and a provided string to suggest a -// command name +// command name. Returns an empty string when no command is sufficiently +// similar; the upstream urfave/cli error formatter omits the suggestion clause +// in that case. func suggestCommand(commands []*cli.Command, provided string) string { - distance := 0.0 + if provided == "" { + return "" + } + distance := suggestionThreshold var lineage []*cli.Command for _, command := range commands { for _, name := range command.Names() { @@ -112,6 +124,9 @@ func suggestCommand(commands []*cli.Command, provided string) string { } } } + if lineage == nil { + return "" + } var parts []string for _, command := range lineage { diff --git a/pkg/cmd/suggest_test.go b/pkg/cmd/suggest_test.go new file mode 100644 index 0000000..43c79d2 --- /dev/null +++ b/pkg/cmd/suggest_test.go @@ -0,0 +1,70 @@ +package cmd + +import ( + "testing" + + "github.com/urfave/cli/v3" +) + +func TestSuggestCommand(t *testing.T) { + commands := []*cli.Command{ + {Name: "create"}, + {Name: "retrieve"}, + {Name: "list"}, + {Name: "delete"}, + {Name: "chat:completions"}, + {Name: "completions"}, + } + + tests := []struct { + name string + provided string + want string + }{ + { + name: "close typo suggests the corrected command", + provided: "creat", + want: "Did you mean 'create'?", + }, + { + name: "near-exact suggests the corrected command", + provided: "chat:completion", + want: "Did you mean 'chat:completions'?", + }, + { + name: "exact match still suggests itself", + provided: "create", + want: "Did you mean 'create'?", + }, + { + name: "unrelated input returns no suggestion", + provided: "zzzzz", + want: "", + }, + { + name: "low-similarity input returns no suggestion", + provided: "totallybogus", + want: "", + }, + { + name: "empty input returns no suggestion", + provided: "", + want: "", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := suggestCommand(commands, tc.provided) + if got != tc.want { + t.Errorf("suggestCommand(%q) = %q, want %q", tc.provided, got, tc.want) + } + }) + } +} + +func TestSuggestCommandEmptyCommands(t *testing.T) { + if got := suggestCommand(nil, "anything"); got != "" { + t.Errorf("suggestCommand(nil, %q) = %q, want empty string", "anything", got) + } +}