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:
+63
-5
@@ -10,6 +10,7 @@ import (
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -28,6 +29,37 @@ import (
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
var (
|
||||
prevRecordedCommand string
|
||||
prevCmdCwd string
|
||||
prevCmdMu sync.Mutex
|
||||
)
|
||||
|
||||
func getPrevSkeleton() string {
|
||||
prevCmdMu.Lock()
|
||||
defer prevCmdMu.Unlock()
|
||||
if prevRecordedCommand == "" {
|
||||
return ""
|
||||
}
|
||||
return scoring.ExtractSkeleton(prevRecordedCommand)
|
||||
}
|
||||
|
||||
func getPrevRecordedInfo() (string, string) {
|
||||
prevCmdMu.Lock()
|
||||
defer prevCmdMu.Unlock()
|
||||
if prevRecordedCommand == "" {
|
||||
return "", ""
|
||||
}
|
||||
return scoring.ExtractSkeleton(prevRecordedCommand), prevCmdCwd
|
||||
}
|
||||
|
||||
func setPrevRecordedInfo(cmd, cwd string) {
|
||||
prevCmdMu.Lock()
|
||||
defer prevCmdMu.Unlock()
|
||||
prevRecordedCommand = cmd
|
||||
prevCmdCwd = cwd
|
||||
}
|
||||
|
||||
func loadMode() string {
|
||||
mode := config.Get().Core.Mode
|
||||
if mode == "last" {
|
||||
@@ -322,7 +354,13 @@ func runWrapper() {
|
||||
continue
|
||||
}
|
||||
|
||||
if query == "IRIS_CMD_STOP" {
|
||||
if query == "IRIS_CMD_STOP" || strings.HasPrefix(query, "IRIS_CMD_STOP:") {
|
||||
exitCode := 0
|
||||
if after, ok := strings.CutPrefix(query, "IRIS_CMD_STOP:"); ok {
|
||||
if code, err := strconv.Atoi(after); err == nil {
|
||||
exitCode = code
|
||||
}
|
||||
}
|
||||
isCommandActive.Store(false)
|
||||
SetCurrentAISuggestion(nil)
|
||||
bufferMu.Lock()
|
||||
@@ -330,8 +368,11 @@ func runWrapper() {
|
||||
lastSubmittedCommand = ""
|
||||
bufferMu.Unlock()
|
||||
if cmdToRecord != "" {
|
||||
integration.RecordSessionCommand(cmdToRecord)
|
||||
cwd := spec.GetCWD()
|
||||
go func(c, d string) {
|
||||
prevSkeleton, prevCwd := getPrevRecordedInfo()
|
||||
currSkeleton := scoring.ExtractSkeleton(cmdToRecord)
|
||||
go func(c, d string, code int, pSkel, pCwd, cSkel string) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
WriteCrashLog(r)
|
||||
@@ -340,9 +381,13 @@ func runWrapper() {
|
||||
ctxRecord, cancel := context.WithTimeout(context.Background(), 1500*time.Millisecond)
|
||||
defer cancel()
|
||||
if store, err := scoring.GetFrecencyStore(); err == nil && store != nil {
|
||||
_ = store.Record(ctxRecord, c, d)
|
||||
_ = store.Record(ctxRecord, c, d, code)
|
||||
if pSkel != "" && cSkel != "" {
|
||||
_ = store.RecordTransition(ctxRecord, pSkel, cSkel, d, code)
|
||||
}
|
||||
}
|
||||
}(cmdToRecord, cwd)
|
||||
}(cmdToRecord, cwd, exitCode, prevSkeleton, prevCwd, currSkeleton)
|
||||
setPrevRecordedInfo(cmdToRecord, cwd)
|
||||
}
|
||||
// hook: after user executes a command, print the update notice exactly once per session
|
||||
if !updatePrinted {
|
||||
@@ -605,17 +650,30 @@ func runWrapper() {
|
||||
if inputSlice[i+2] == 'A' {
|
||||
arrowDir = "up"
|
||||
}
|
||||
moved, _ := overlay.MoveCursor(arrowDir)
|
||||
moved, selectedCmd := overlay.MoveCursor(arrowDir)
|
||||
if !moved {
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
|
||||
bufferMu.Lock()
|
||||
activeModeMu.RLock()
|
||||
isHistMode := activeMode == "history"
|
||||
activeModeMu.RUnlock()
|
||||
var toWrite []byte
|
||||
if isHistMode && selectedCmd != "" {
|
||||
naiveBuffer = selectedCmd
|
||||
cursorOffset = 0
|
||||
toWrite = append([]byte{0x15}, selectedCmd...)
|
||||
}
|
||||
bufCopy := naiveBuffer
|
||||
offsetCopy := cursorOffset
|
||||
bufferMu.Unlock()
|
||||
|
||||
if len(toWrite) > 0 {
|
||||
_, _ = ptmx.Write(toWrite)
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
if !disableGhostText.Load() {
|
||||
b.WriteString(overlay.RenderGhostText(bufCopy, true, offsetCopy == 0))
|
||||
|
||||
Reference in New Issue
Block a user