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:
@@ -3,11 +3,13 @@ package workspace
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type WorkspaceInfo struct {
|
||||
HasGit bool
|
||||
GitBranch string
|
||||
HasNodeProject bool
|
||||
HasGoProject bool
|
||||
HasRustProject bool
|
||||
@@ -57,9 +59,55 @@ func Detect(cwd string) WorkspaceInfo {
|
||||
}
|
||||
}
|
||||
|
||||
info.HasGit, info.GitBranch = detectGitInfo(cwd)
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
func resolveGitHeadPath(cwd string) (hasGit bool, headPath string) {
|
||||
dir := cwd
|
||||
for dir != "" {
|
||||
gitPath := filepath.Join(dir, ".git")
|
||||
info, err := os.Stat(gitPath)
|
||||
if err == nil {
|
||||
if info.IsDir() {
|
||||
return true, filepath.Join(gitPath, "HEAD")
|
||||
}
|
||||
content, errRead := os.ReadFile(gitPath)
|
||||
if errRead == nil {
|
||||
s := strings.TrimSpace(string(content))
|
||||
if after, ok := strings.CutPrefix(s, "gitdir: "); ok {
|
||||
gitDir := strings.TrimSpace(after)
|
||||
if !filepath.IsAbs(gitDir) {
|
||||
gitDir = filepath.Join(dir, gitDir)
|
||||
}
|
||||
return true, filepath.Join(gitDir, "HEAD")
|
||||
}
|
||||
}
|
||||
return true, "" // found .git but couldn't resolve HEAD
|
||||
}
|
||||
parent := filepath.Dir(dir)
|
||||
if parent == dir {
|
||||
break
|
||||
}
|
||||
dir = parent
|
||||
}
|
||||
return false, ""
|
||||
}
|
||||
|
||||
func detectGitInfo(cwd string) (hasGit bool, branch string) {
|
||||
hasGit, headPath := resolveGitHeadPath(cwd)
|
||||
if headPath != "" {
|
||||
if data, errHead := os.ReadFile(headPath); errHead == nil {
|
||||
s := strings.TrimSpace(string(data))
|
||||
if after, ok := strings.CutPrefix(s, "ref: refs/heads/"); ok {
|
||||
return hasGit, after
|
||||
}
|
||||
}
|
||||
}
|
||||
return hasGit, ""
|
||||
}
|
||||
|
||||
type cacheEntry struct {
|
||||
key string // cwd + "|" + dirModTime
|
||||
info WorkspaceInfo
|
||||
@@ -71,7 +119,7 @@ var (
|
||||
)
|
||||
|
||||
// DetectCached returns cached workspace info, invalidating when directory modtime changes
|
||||
// this handles mid-session file creation (e.g. go mod init) without requiring cd
|
||||
// or when the Git HEAD file changes (to catch branch switches that don't affect cwd modtime).
|
||||
func DetectCached(cwd string) WorkspaceInfo {
|
||||
dirInfo, err := os.Stat(cwd)
|
||||
if err != nil {
|
||||
@@ -79,6 +127,13 @@ func DetectCached(cwd string) WorkspaceInfo {
|
||||
}
|
||||
key := cwd + "|" + dirInfo.ModTime().String()
|
||||
|
||||
// Incorporate Git HEAD modtime into cache key to catch branch switches
|
||||
if _, headPath := resolveGitHeadPath(cwd); headPath != "" {
|
||||
if headInfo, err := os.Stat(headPath); err == nil {
|
||||
key += "|HEAD:" + headInfo.ModTime().String()
|
||||
}
|
||||
}
|
||||
|
||||
wsCacheMu.Lock()
|
||||
defer wsCacheMu.Unlock()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user