From ec5e9d1025fee66938153140b5f37e35b50c67ed Mon Sep 17 00:00:00 2001 From: Lee Won Jun Date: Wed, 16 Jul 2025 08:45:10 +0900 Subject: [PATCH] Improve altName completion behavior in slash commands (#4227) Co-authored-by: Jacob Richman --- .../hooks/useCompletion.integration.test.ts | 35 +++++++++++++++-- .../cli/src/ui/hooks/useCompletion.test.ts | 38 +++++++++++++++---- packages/cli/src/ui/hooks/useCompletion.ts | 2 +- 3 files changed, 63 insertions(+), 12 deletions(-) diff --git a/packages/cli/src/ui/hooks/useCompletion.integration.test.ts b/packages/cli/src/ui/hooks/useCompletion.integration.test.ts index 705b2735..37075e3c 100644 --- a/packages/cli/src/ui/hooks/useCompletion.integration.test.ts +++ b/packages/cli/src/ui/hooks/useCompletion.integration.test.ts @@ -54,6 +54,12 @@ describe('useCompletion git-aware filtering integration', () => { description: 'Show help', action: vi.fn(), }, + { + name: 'stats', + altName: 'usage', + description: 'check session stats. Usage: /stats [model|tools]', + action: vi.fn(), + }, { name: 'clear', description: 'Clear the screen', @@ -511,10 +517,27 @@ describe('useCompletion git-aware filtering integration', () => { expect(result.current.showSuggestions).toBe(true); }); - it('should suggest commands based on altName', async () => { + it.each([['/?'], ['/usage']])( + 'should not suggest commands when altName is fully typed', + async (altName) => { + const { result } = renderHook(() => + useCompletion( + altName, + '/test/cwd', + true, + mockSlashCommands, + mockCommandContext, + ), + ); + + expect(result.current.suggestions).toHaveLength(0); + }, + ); + + it('should suggest commands based on partial altName matches', async () => { const { result } = renderHook(() => useCompletion( - '/?', + '/usag', // part of the word "usage" '/test/cwd', true, mockSlashCommands, @@ -523,7 +546,11 @@ describe('useCompletion git-aware filtering integration', () => { ); expect(result.current.suggestions).toEqual([ - { label: 'help', value: 'help', description: 'Show help' }, + { + label: 'stats', + value: 'stats', + description: 'check session stats. Usage: /stats [model|tools]', + }, ]); }); @@ -734,7 +761,7 @@ describe('useCompletion git-aware filtering integration', () => { expect(result.current.suggestions.length).toBe(mockSlashCommands.length); expect(result.current.suggestions.map((s) => s.label)).toEqual( - expect.arrayContaining(['help', 'clear', 'memory', 'chat']), + expect.arrayContaining(['help', 'clear', 'memory', 'chat', 'stats']), ); }); diff --git a/packages/cli/src/ui/hooks/useCompletion.test.ts b/packages/cli/src/ui/hooks/useCompletion.test.ts index b2790808..7f2823c7 100644 --- a/packages/cli/src/ui/hooks/useCompletion.test.ts +++ b/packages/cli/src/ui/hooks/useCompletion.test.ts @@ -64,6 +64,12 @@ describe('useCompletion', () => { description: 'Show help', action: vi.fn(), }, + { + name: 'stats', + altName: 'usage', + description: 'check session stats. Usage: /stats [model|tools]', + action: vi.fn(), + }, { name: 'clear', description: 'Clear the screen', @@ -299,7 +305,7 @@ describe('useCompletion', () => { ), ); - expect(result.current.suggestions.length).toBe(4); + expect(result.current.suggestions.length).toBe(5); expect(result.current.activeSuggestionIndex).toBe(0); act(() => { @@ -325,7 +331,7 @@ describe('useCompletion', () => { act(() => { result.current.navigateUp(); }); - expect(result.current.activeSuggestionIndex).toBe(3); + expect(result.current.activeSuggestionIndex).toBe(4); }); it('should handle navigation with large suggestion lists and scrolling', () => { @@ -372,9 +378,9 @@ describe('useCompletion', () => { ), ); - expect(result.current.suggestions).toHaveLength(4); + expect(result.current.suggestions).toHaveLength(5); expect(result.current.suggestions.map((s) => s.label)).toEqual( - expect.arrayContaining(['help', 'clear', 'memory', 'chat']), + expect.arrayContaining(['help', 'clear', 'memory', 'chat', 'stats']), ); expect(result.current.showSuggestions).toBe(true); expect(result.current.activeSuggestionIndex).toBe(0); @@ -397,10 +403,28 @@ describe('useCompletion', () => { expect(result.current.suggestions[0].description).toBe('Show help'); }); - it('should suggest commands by altName', () => { + it.each([['/?'], ['/usage']])( + 'should not suggest commands when altName is fully typed', + (altName) => { + const { result } = renderHook(() => + useCompletion( + altName, + testCwd, + true, + mockSlashCommands, + mockCommandContext, + mockConfig, + ), + ); + + expect(result.current.suggestions).toHaveLength(0); + }, + ); + + it('should suggest commands based on partial altName matches', () => { const { result } = renderHook(() => useCompletion( - '/?', + '/usag', // part of the word "usage" testCwd, true, mockSlashCommands, @@ -410,7 +434,7 @@ describe('useCompletion', () => { ); expect(result.current.suggestions).toHaveLength(1); - expect(result.current.suggestions[0].label).toBe('help'); + expect(result.current.suggestions[0].label).toBe('stats'); }); it('should not show suggestions for exact leaf command match', () => { diff --git a/packages/cli/src/ui/hooks/useCompletion.ts b/packages/cli/src/ui/hooks/useCompletion.ts index 8b3f6991..d3de5c6b 100644 --- a/packages/cli/src/ui/hooks/useCompletion.ts +++ b/packages/cli/src/ui/hooks/useCompletion.ts @@ -221,7 +221,7 @@ export function useCompletion( // enter should submit immediately. if (potentialSuggestions.length > 0 && !hasTrailingSpace) { const perfectMatch = potentialSuggestions.find( - (s) => s.name === partial, + (s) => s.name === partial || s.altName === partial, ); if (perfectMatch && !perfectMatch.subCommands) { potentialSuggestions = [];