feat(cli): add fuzzy matching to the slash-command completion menu#3833
Merged
Merged
Conversation
Replace the strict prefix filter in updateCompletion with a case-insensitive subsequence matcher, so typing a partial slash token like /mdl surfaces /compact, /model, /memory and other commands whose letters appear in order, not just commands that start with the typed prefix. The new matcher keeps the previous menu semantics for every input the old filter already handled: an empty query (bare /) lists every command, and a query that matches nothing closes the menu. Prefix hits rank first within the result list, so /mo still puts /model on top and the rest of the subsequence matches follow in their original slashItems order. Scope is intentionally limited to the in-TUI completion popup; typed-input resolution (/name + Enter) and the model's slash_command tool still use exact-name lookup to keep the change reviewable. Tested with new unit tests in complete_test.go covering subsequence matching, prefix-first ranking, case insensitivity, empty query, no-match menu close, custom command coverage, and end-to-end accept.
cebef87 to
dec5906
Compare
esengine
approved these changes
Jun 11, 2026
esengine
left a comment
Owner
There was a problem hiding this comment.
Reviewed: the subsequence matcher is correct and minimal, prefix hits ranking ahead of subsequence-only hits is the right UX call, and the empty-query / no-match contracts deliberately mirror the old prefix filter so the menu open/close behavior is unchanged. Keeping typed-input resolution and the slash_command tool on exact lookup is exactly the scope I'd have asked for. Test coverage is thorough — the unit table on subsequenceMatch plus the end-to-end accept path is the right split. Merging, thanks!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤖 Generated by AI (CnsMaple + Reasonix)
Summary
Replace the strict
strings.HasPrefixfilter in the chat-TUI slash-commandcompletion popup with a case-insensitive subsequence matcher, so typing a
partial token like
/mdlsurfaces/compact,/model,/memoryand anyother command whose letters appear in order — not just commands that begin
with the typed prefix.
Why
Users naturally abbreviate or fat-finger slash commands (
/compt,/mdl,/skll,/brnch). The current matcher silently closes the menu onanything that isn't an exact prefix, forcing the user to retype or look
the command up via
/help. A subsequence match keeps the "type a fewletters, see the candidates" muscle memory from shells like fzf.
Scope
Inentionally limited to the live TUI completion popup. Typed-input
resolution (
/name+ Enter) and the model'sslash_commandtool stilluse exact-name lookup, so the routing layer stays predictable. Both are
one-liner follow-ups if reviewers want them in the same change.
Implementation
internal/cli/complete.gofuzzyFilterSlash(items, query): returns the matched compItemswith prefix hits ranked first, subsequence-only hits after, each
group preserving the original
slashItems()order. Empty querylists everything (same as before). Zero matches returns
nilsoupdateCompletionfalls through and closes the menu.subsequenceMatch(target, query): rune-level subsequence teston already-case-folded strings. O(n+m), no allocations beyond the
rune slice.
updateCompletionslash branch:filterByPrefix(...)→fuzzyFilterSlash(...). No other call sites touched.Tests
8 new tests in
internal/cli/complete_test.go:TestFuzzyFilterSlashSubsequence—/mdlhits/model, not/mcpTestFuzzyFilterSlashPrefixFirst—/moranks/modelfirstTestFuzzyFilterSlashCaseInsensitive—/COMPhits/compactTestFuzzyFilterSlashEmptyQueryMatchesAll— bare/lists everythingTestFuzzyFilterSlashNoMatchClosesMenu—/xzqzqzcloses the menuTestFuzzyFilterSlashAppliesToCustomCommands— custom commands coveredTestFuzzyFilterSlashAcceptFillsInput— Tab/accept end-to-endTestSubsequenceMatchUnit— direct matcher coverage incl. negativesAll 4 pre-existing slash-completion tests still pass.
gofmt -landgo vet ./internal/cli/...are clean. The 4 unrelated failures ininternal/cli/...(TestApplyMCPModeDropsLegacyTier,TestModelRefsSkipsUnconfigured,TestOneLine,TestRenderRewindSmoke)are pre-existing on
main-v2and were verified withgit stashto beunrelated to this change.
Risk
Low. The matcher is a strict superset of the previous prefix filter:
every input that matched before still matches, and the menu's
"open/close" contract is preserved (empty = list all, no match = close).
The accept/insert path is untouched, so Tab still fills the input with
the top-ranked hit's
insertstring.Checklist
upstream/main-v2(was 51 commits behind)gofmt -l internal/cli/...clean on the LF content git will pushgo vet ./internal/cli/...cleanmain-v2per CONTRIBUTING.md