feat(scoring): transition workflow learning and fix history UX (#39)
- add transition scoring engine to learn sequential workflows per directory using command skeletons - prioritize current active git branch over older branches in suggestions - restore strict chronological order for history navigation and bypass AI re-ranking - add in-memory session history for instant access to just-run commands - fix PTY prompt not syncing when using arrow keys in the history menu - fix history tie-breaker to properly prioritize newer commands by assigning larger IDs - fix potential mutex deadlock between bufferMu and pty write during history navigation - fix workspace git detection to remove depth limits and correctly identify repositories with detached HEADs - fix SQLite connection leaks in frecency queries - fix global state leaks in history tests by snapshotting and restoring registry states - modernize string prefix checks using strings.CutPrefix
This commit is contained in:
@@ -12,6 +12,7 @@ type ScoreBreakdown struct {
|
||||
BasePriority int
|
||||
ContextBonus int
|
||||
Frecency int
|
||||
Transition int
|
||||
MatchQuality int
|
||||
}
|
||||
|
||||
@@ -25,13 +26,15 @@ type ScoreConfig struct {
|
||||
WeightBasePriority float64
|
||||
WeightContextBonus float64
|
||||
WeightFrecency float64
|
||||
WeightTransition float64
|
||||
WeightMatchQuality float64
|
||||
}
|
||||
|
||||
var DefaultScoreConfig = ScoreConfig{
|
||||
WeightBasePriority: 0.30,
|
||||
WeightContextBonus: 0.25,
|
||||
WeightFrecency: 0.25,
|
||||
WeightFrecency: 0.15,
|
||||
WeightTransition: 0.10,
|
||||
WeightMatchQuality: 0.20,
|
||||
}
|
||||
|
||||
@@ -71,11 +74,13 @@ func ScoreWithConfig(suggestions []spec.Suggestion, signals SignalSet, config Sc
|
||||
bp := basePriorityFor(s)
|
||||
cb := ApplyContextRules(signals.Workspace, s.Cmd)
|
||||
frec := normFrec[i]
|
||||
trans := transitionScoreFor(ExtractSkeleton(s.Cmd), signals.TransitionEntries, signals.TransitionIsLocal)
|
||||
mq := matchQualityScore(s.Cmd, signals.Query)
|
||||
|
||||
total := config.WeightBasePriority*float64(bp) +
|
||||
config.WeightContextBonus*float64(cb) +
|
||||
config.WeightFrecency*float64(frec) +
|
||||
config.WeightTransition*float64(trans) +
|
||||
config.WeightMatchQuality*float64(mq)
|
||||
|
||||
scored[i] = ScoredSuggestion{
|
||||
@@ -85,6 +90,7 @@ func ScoreWithConfig(suggestions []spec.Suggestion, signals SignalSet, config Sc
|
||||
BasePriority: bp,
|
||||
ContextBonus: cb,
|
||||
Frecency: frec,
|
||||
Transition: trans,
|
||||
MatchQuality: mq,
|
||||
},
|
||||
}
|
||||
@@ -94,6 +100,9 @@ func ScoreWithConfig(suggestions []spec.Suggestion, signals SignalSet, config Sc
|
||||
if scored[i].Score != scored[j].Score {
|
||||
return scored[i].Score > scored[j].Score
|
||||
}
|
||||
if scored[i].Breakdown.Transition != scored[j].Breakdown.Transition {
|
||||
return scored[i].Breakdown.Transition > scored[j].Breakdown.Transition
|
||||
}
|
||||
if scored[i].Breakdown.Frecency != scored[j].Breakdown.Frecency {
|
||||
return scored[i].Breakdown.Frecency > scored[j].Breakdown.Frecency
|
||||
}
|
||||
@@ -106,6 +115,26 @@ func ScoreWithConfig(suggestions []spec.Suggestion, signals SignalSet, config Sc
|
||||
return scored
|
||||
}
|
||||
|
||||
func transitionScoreFor(cmdSkeleton string, entries []TransitionEntry, isLocal bool) int {
|
||||
if len(entries) == 0 {
|
||||
return 0 // cold-start: no data, contributes 0 (must check before accessing entries[0])
|
||||
}
|
||||
maxCount := entries[0].Count
|
||||
if maxCount <= 0 {
|
||||
return 0
|
||||
}
|
||||
for _, e := range entries {
|
||||
if e.NextSkeleton == cmdSkeleton {
|
||||
score := (float64(e.Count) / float64(maxCount)) * 100.0
|
||||
if !isLocal {
|
||||
score *= 0.7
|
||||
}
|
||||
return int(math.Round(score))
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func basePriorityFor(s spec.Suggestion) int {
|
||||
if s.Priority > 0 {
|
||||
if s.Priority > 100 {
|
||||
|
||||
Reference in New Issue
Block a user