fix: address prefix matching recursion and skill command coverage
Per teknium1 review on PR #968: 1. Guard against infinite recursion: if expanded name equals the typed token (already exact), fall through to Unknown command instead of redispatching the same string forever. 2. Include skill slash commands in prefix resolution so execution-time matching agrees with tab-completion (set(COMMANDS) | set(_skill_commands)). 3. Add missing test cases: - unambiguous prefix with extra args does not recurse - exact command with args does not loop - skill command prefix matches correctly - exact builtin takes priority over skill prefix ambiguity 8 tests passing.
This commit is contained in:
parent
a50550fdb4
commit
fbdce27b9a
2 changed files with 90 additions and 23 deletions
24
cli.py
24
cli.py
|
|
@ -3094,15 +3094,27 @@ class HermesCLI:
|
|||
else:
|
||||
self.console.print(f"[bold red]Failed to load skill for {base_cmd}[/]")
|
||||
else:
|
||||
# Prefix matching: if input uniquely identifies one command, execute it
|
||||
# Prefix matching: if input uniquely identifies one command, execute it.
|
||||
# Matches against both built-in COMMANDS and installed skill commands so
|
||||
# that execution-time resolution agrees with tab-completion.
|
||||
from hermes_cli.commands import COMMANDS
|
||||
typed_base = cmd_lower.split()[0]
|
||||
matches = [c for c in COMMANDS if c.startswith(typed_base)]
|
||||
all_known = set(COMMANDS) | set(_skill_commands)
|
||||
matches = [c for c in all_known if c.startswith(typed_base)]
|
||||
if len(matches) == 1:
|
||||
# Re-dispatch with the full command name, preserving any arguments
|
||||
remainder = cmd_original.strip()[len(typed_base):]
|
||||
full_cmd = matches[0] + remainder
|
||||
return self.process_command(full_cmd)
|
||||
# Expand the prefix to the full command name, preserving arguments.
|
||||
# Guard against redispatching the same token to avoid infinite
|
||||
# recursion when the expanded name still doesn't hit an exact branch
|
||||
# (e.g. /config with extra args that are not yet handled above).
|
||||
full_name = matches[0]
|
||||
if full_name == typed_base:
|
||||
# Already an exact token — no expansion possible; fall through
|
||||
self.console.print(f"[bold red]Unknown command: {cmd_lower}[/]")
|
||||
self.console.print("[dim #B8860B]Type /help for available commands[/]")
|
||||
else:
|
||||
remainder = cmd_original.strip()[len(typed_base):]
|
||||
full_cmd = full_name + remainder
|
||||
return self.process_command(full_cmd)
|
||||
elif len(matches) > 1:
|
||||
self.console.print(f"[bold yellow]Ambiguous command: {cmd_lower}[/]")
|
||||
self.console.print(f"[dim]Did you mean: {', '.join(sorted(matches))}?[/]")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue