feat(spec): support tool aliases (#107)

closes #57

IRIS now automatically read and suggest internal configuration aliases
for Git (`.gitconfig`, `.git/config`) and Cargo (`.cargo/config.toml`)

- Added the `Provider` interface and extended registry for CLI tools
that define aliases
- Git: parse aliases via `git config --show-scope`, supporting raw
tab/space separation and scope differentiation
- Cargo: traverse the directory tree upwards to root to read the
`.cargo/config.toml` configuration
- 0ms hot-path caching: key caching is calculated entirely via `os.Stat`
mtime (no subprocess spawn) combined with a 2-second TTL window to
ensure 0ms latency when typing
- Lookup integration: display alias suggestions at the subcommand level
and automatically expand aliases as the user enters further arguments
(ignore shell pipeline commands starting with `!`)
This commit is contained in:
VERSE
2026-08-02 14:30:46 +07:00
committed by GitHub
parent 476ca3946b
commit 6faa2e3274
10 changed files with 732 additions and 2 deletions
+10
View File
@@ -0,0 +1,10 @@
package root
import "github.com/versenilvis/iris/spec/alias"
type mockGitProvider struct{}
func (m *mockGitProvider) ToolName() string { return "git" }
func (m *mockGitProvider) GetAliases(cwd string) []alias.AliasEntry {
return []alias.AliasEntry{{Name: "co", Expansion: "checkout", Scope: "local"}}
}
+4 -1
View File
@@ -25,7 +25,10 @@ func MergeResults(query string, mode string) []spec.Suggestion {
// add suggestion helper to deduplicate
addSuggestion := func(s spec.Suggestion) {
normalizedCmd := strings.TrimSpace(s.Cmd)
if normalizedCmd == "" || normalizedCmd == normalizedQuery {
if normalizedCmd == "" {
return
}
if s.Source != "alias" && normalizedCmd == normalizedQuery {
return
}
if s.Source == "" {
+17
View File
@@ -4,6 +4,7 @@ import (
"testing"
"github.com/versenilvis/iris/spec"
"github.com/versenilvis/iris/spec/alias"
)
func TestMergeResults(t *testing.T) {
@@ -48,6 +49,22 @@ func TestMergeResults(t *testing.T) {
t.Errorf("Expected promoted source 'ai', got %q", res[0].Source)
}
})
t.Run("Tool Alias Preserved When Matching Query", func(t *testing.T) {
alias.Register(&mockGitProvider{})
defer alias.Reset()
res := MergeResults("git co", "default")
foundCo := false
for _, r := range res {
if r.Cmd == "git co" {
foundCo = true
break
}
}
if !foundCo {
t.Errorf("expected 'git co' alias suggestion in results for query 'git co', got %v", res)
}
})
}
func TestPrevRecordedCommandState(t *testing.T) {
+1
View File
@@ -821,6 +821,7 @@ func runWrapper() {
overlay.ClearGhostTextState()
userNavigated.Store(false)
writeStdout([]byte(overlay.Render()))
renderOverlay()
}
i += consumed - 1
continue