Enhance lookup mechanics and priority propagation (`spec/lookup.go`) Remove the requirement of typing a leading `-` when searching for options, allowing flag suggestions to surface naturally during input Accurately forward the `Priority` field from registry specs into the `Suggestion` struct so the scoring engine evaluates relevance properly Integrate scoring into the main suggestion pipeline (`root/suggestions.go`) Collect context signals using `scoring.CollectSignals(cwd, query, rootCmd, store)` with the live working directory retrieved from `spec.GetCWD()` Pass the entire deduplicated list across spec, history and AI items through `scoring.Score(deduped, signals)` to compute scores and sort by descending order before rendering on screen Record execution history in Frecency Store (`root/wrapper.go`) Save the last executed command to `lastSubmittedCommand` whenever Enter is pressed or a suggestion is selected Trigger `store.Record(...)` asynchronously within the `IRIS_CMD_STOP` hook using a dedicated goroutine guarded by panic recovery and timeout limits, preventing blocks on the main shell thread Optimize workspace identification for AI (`internal/ai/context_provider.go`) Use `workspace.DetectCached(cwd)` to instantly fetch ecosystem context across Git, Node, Go, Rust, Python and Docker alongside active git branch state This caching mechanism avoids redundant subprocess execution when outside git repositories, significantly speeding up prompt context generation
153 lines
3.7 KiB
Go
153 lines
3.7 KiB
Go
package root
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/versenilvis/iris/integration"
|
|
"github.com/versenilvis/iris/internal/ai"
|
|
"github.com/versenilvis/iris/internal/config"
|
|
"github.com/versenilvis/iris/internal/logger"
|
|
"github.com/versenilvis/iris/internal/scoring"
|
|
"github.com/versenilvis/iris/spec"
|
|
)
|
|
|
|
// MergeResults collects and dedupes suggestions for a query and mode
|
|
func MergeResults(query string, mode string) []spec.Suggestion {
|
|
maxSugg := config.Get().UI.MaxSuggestions
|
|
seen := make(map[string]bool)
|
|
deduped := []spec.Suggestion{}
|
|
|
|
// always call lookup to scan aliases and get spec suggestions
|
|
logger.Debugf("Merge Calling Lookup for '%s'", query)
|
|
cmdResults := spec.Lookup(query)
|
|
|
|
// search history if in history mode
|
|
var histResults []integration.HistResult
|
|
if mode == "history" {
|
|
aliases := spec.GetAliasesCopy()
|
|
histResults, _ = integration.SearchHistory(query, aliases)
|
|
}
|
|
|
|
normalizedQuery := strings.TrimSpace(query)
|
|
|
|
// add suggestion helper to deduplicate
|
|
addSuggestion := func(s spec.Suggestion) {
|
|
normalizedCmd := strings.TrimSpace(s.Cmd)
|
|
if normalizedCmd == "" {
|
|
return
|
|
}
|
|
// filter exact match to avoid loops and redundant suggestions
|
|
if normalizedCmd == normalizedQuery {
|
|
return
|
|
}
|
|
if s.Source == "" {
|
|
s.Source = "spec"
|
|
if s.Confidence == 0 {
|
|
s.Confidence = 50
|
|
}
|
|
}
|
|
if !seen[s.Cmd] {
|
|
seen[s.Cmd] = true
|
|
deduped = append(deduped, s)
|
|
}
|
|
}
|
|
|
|
if mode == "history" {
|
|
// history mode: history first, then spec/alias
|
|
for _, h := range histResults {
|
|
addSuggestion(spec.Suggestion{
|
|
Cmd: h.Cmd,
|
|
Desc: "history",
|
|
Icon: "history",
|
|
Source: "history",
|
|
Confidence: 70,
|
|
})
|
|
}
|
|
for _, s := range cmdResults {
|
|
addSuggestion(s)
|
|
}
|
|
} else {
|
|
// spec mode: spec/alias only
|
|
for _, s := range cmdResults {
|
|
addSuggestion(s)
|
|
}
|
|
}
|
|
|
|
if aiSugg := GetCurrentAISuggestion(); aiSugg != nil {
|
|
normalizedCmd := strings.TrimSpace(aiSugg.Cmd)
|
|
if normalizedCmd != "" && normalizedCmd != normalizedQuery && strings.HasPrefix(strings.ToLower(normalizedCmd), strings.ToLower(normalizedQuery)) {
|
|
if !seen[aiSugg.Cmd] {
|
|
seen[aiSugg.Cmd] = true
|
|
deduped = append(deduped, *aiSugg)
|
|
} else {
|
|
for i, item := range deduped {
|
|
if item.Cmd == aiSugg.Cmd && aiSugg.Confidence > item.Confidence {
|
|
deduped[i].Confidence = aiSugg.Confidence
|
|
if deduped[i].Source == "" || deduped[i].Source == "spec" || deduped[i].Source == "history" {
|
|
deduped[i].Source = "ai"
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
cwd := spec.GetCWD()
|
|
tokens := spec.Tokenize(query)
|
|
rootCmd := ""
|
|
if len(tokens) > 0 {
|
|
rootCmd = tokens[0]
|
|
}
|
|
ctxTimeout, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
|
defer cancel()
|
|
store, _ := scoring.GetFrecencyStore()
|
|
signals := scoring.CollectSignals(ctxTimeout, cwd, query, rootCmd, store)
|
|
scored := scoring.Score(deduped, signals)
|
|
|
|
finalResults := make([]spec.Suggestion, 0, len(scored))
|
|
for _, sc := range scored {
|
|
finalResults = append(finalResults, sc.Suggestion)
|
|
}
|
|
|
|
if len(finalResults) > maxSugg {
|
|
finalResults = finalResults[:maxSugg]
|
|
}
|
|
return finalResults
|
|
}
|
|
|
|
var (
|
|
aiEngine *ai.AIEngine
|
|
aiEngineOnce sync.Once
|
|
)
|
|
|
|
func GetAIEngine() *ai.AIEngine {
|
|
aiEngineOnce.Do(func() {
|
|
aiEngine = ai.NewAIEngine(nil)
|
|
for _, p := range ai.DefaultProviders {
|
|
aiEngine.RegisterProvider(p)
|
|
}
|
|
})
|
|
return aiEngine
|
|
}
|
|
|
|
var (
|
|
currentAISugg *spec.Suggestion
|
|
aiSuggMu sync.RWMutex
|
|
)
|
|
|
|
func SetCurrentAISuggestion(sugg *spec.Suggestion) {
|
|
aiSuggMu.Lock()
|
|
defer aiSuggMu.Unlock()
|
|
currentAISugg = sugg
|
|
}
|
|
|
|
func GetCurrentAISuggestion() *spec.Suggestion {
|
|
aiSuggMu.RLock()
|
|
defer aiSuggMu.RUnlock()
|
|
return currentAISugg
|
|
}
|