From d7951ec3624fa3c8fed15426292de905cf496e3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=9D=E5=BF=83Yearth?= Date: Sun, 20 Sep 2026 06:25:43 +0800 Subject: [PATCH] fix(tui): rank skill autocomplete by bare name (#9120) --- packages/tui/src/autocomplete.ts | 6 ++- .../tui/test/autocomplete-skill-slash.test.ts | 38 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 packages/tui/test/autocomplete-skill-slash.test.ts diff --git a/packages/tui/src/autocomplete.ts b/packages/tui/src/autocomplete.ts index f90a6eda3..47275f4bf 100644 --- a/packages/tui/src/autocomplete.ts +++ b/packages/tui/src/autocomplete.ts @@ -332,7 +332,11 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider { }; }); - const filtered = fuzzyFilter(commandItems, prefix, (item) => item.name).map((item) => ({ + const filtered = fuzzyFilter(commandItems, prefix, (item) => + !prefix.startsWith("skill:") && item.name.startsWith("skill:") + ? item.name.slice("skill:".length) + : item.name, + ).map((item) => ({ value: item.name, label: item.label, ...(item.description && { description: item.description }), diff --git a/packages/tui/test/autocomplete-skill-slash.test.ts b/packages/tui/test/autocomplete-skill-slash.test.ts new file mode 100644 index 000000000..cbc29a5db --- /dev/null +++ b/packages/tui/test/autocomplete-skill-slash.test.ts @@ -0,0 +1,38 @@ +import assert from "node:assert"; +import { describe, it } from "node:test"; +import { CombinedAutocompleteProvider } from "../src/autocomplete.ts"; + +describe("CombinedAutocompleteProvider slash-command filter", () => { + const commands = [ + { name: "skill:deep-research", description: "Multi-agent deep research" }, + { name: "skill:research-idea", description: "Refine a raw idea into a falsifiable seed" }, + { name: "skill:to-sidecar", description: "Route work to a sidecar" }, + { name: "model", description: "Select the active model" }, + ]; + + async function suggestionsFor(prefix: string): Promise { + const provider = new CombinedAutocompleteProvider(commands, process.cwd()); + const line = `/${prefix}`; + const result = await provider.getSuggestions([line], 0, line.length, { + signal: new AbortController().signal, + }); + assert.ok(result, `expected suggestions for "/${prefix}"`); + return result.items.map((item) => item.value); + } + + it("ranks skill:research-idea first for query 'idea'", async () => { + const items = await suggestionsFor("idea"); + assert.equal(items[0], "skill:research-idea"); + assert.ok(!items.includes("skill:deep-research")); + }); + + it("keeps ordinary slash commands matching", async () => { + const items = await suggestionsFor("mod"); + assert.ok(items.includes("model")); + }); + + it("keeps explicit skill: queries working", async () => { + const items = await suggestionsFor("skill:side"); + assert.ok(items.includes("skill:to-sidecar")); + }); +});