Skip to content

Commit bc973c7

Browse files
committed
feat: enhance filterSlashCommands to prioritize prefix matches and maintain input order
1 parent 8931e24 commit bc973c7

2 files changed

Lines changed: 56 additions & 2 deletions

File tree

‎packages/cli/src/tests/slash-commands.test.ts‎

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,54 @@ test("filterSlashCommands matches partial prefixes", () => {
4141
assert.deepEqual(matched, ["skill-writer", "skills"]);
4242
});
4343

44+
test("filterSlashCommands ranks /model before /commondao for /mo", () => {
45+
const items = buildSlashCommands([
46+
{ name: "commondao", path: "/skills/commondao/SKILL.md", description: "CommonDAO guide" },
47+
]);
48+
assert.deepEqual(
49+
filterSlashCommands(items, "/mo").map((item) => item.name),
50+
["model", "commondao"]
51+
);
52+
});
53+
54+
test("filterSlashCommands preserves order within match groups without mutating input", () => {
55+
const items = buildSlashCommands(
56+
["commondao", "motion", "demo", "model-helper"].map((name) => ({
57+
name,
58+
path: `/skills/${name}/SKILL.md`,
59+
description: name,
60+
}))
61+
);
62+
const original = [...items];
63+
assert.deepEqual(
64+
filterSlashCommands(items, "/mo").map((item) => item.name),
65+
["motion", "model-helper", "model", "commondao", "demo"]
66+
);
67+
assert.deepEqual(items, original);
68+
});
69+
70+
test("filterSlashCommands matches and prioritizes prefixes case-insensitively", () => {
71+
const items = buildSlashCommands(
72+
["ComMonDAO", "Motion"].map((name) => ({
73+
name,
74+
path: `/skills/${name}/SKILL.md`,
75+
description: name,
76+
}))
77+
);
78+
assert.deepEqual(
79+
filterSlashCommands(items, "/MO").map((item) => item.name),
80+
["Motion", "model", "ComMonDAO"]
81+
);
82+
});
83+
4484
test("filterSlashCommands returns all entries on bare slash", () => {
4585
const items = buildSlashCommands(skills);
4686
const matched = filterSlashCommands(items, "/");
47-
assert.equal(matched.length, items.length);
87+
assert.deepEqual(matched, items);
88+
});
89+
90+
test("filterSlashCommands returns nothing when no names match", () => {
91+
assert.deepEqual(filterSlashCommands(buildSlashCommands(skills), "/missing"), []);
4892
});
4993

5094
test("filterSlashCommands returns nothing for non-slash tokens", () => {

‎packages/cli/src/ui/core/slash-commands.ts‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,17 @@ export function filterSlashCommands(items: SlashCommandItem[], token: string): S
119119
if (!query) {
120120
return items;
121121
}
122-
return items.filter((item) => item.name.toLowerCase().includes(query));
122+
const prefixMatches: SlashCommandItem[] = [];
123+
const otherMatches: SlashCommandItem[] = [];
124+
for (const item of items) {
125+
const name = item.name.toLowerCase();
126+
if (name.startsWith(query)) {
127+
prefixMatches.push(item);
128+
} else if (name.includes(query)) {
129+
otherMatches.push(item);
130+
}
131+
}
132+
return [...prefixMatches, ...otherMatches];
123133
}
124134

125135
export function findExactSlashCommand(items: SlashCommandItem[], token: string): SlashCommandItem | null {

0 commit comments

Comments
 (0)