go-session/search.go
Snider 89a431c1b9
All checks were successful
Security Scan / security (push) Successful in 11s
Test / test (push) Successful in 1m58s
feat: modernise to Go 1.26 iterators and stdlib helpers
Add ListSessionsSeq, EventsSeq, SearchSeq iterators for streaming.
Use slices.SortFunc, slices.Sorted(maps.Keys()), slices.Collect
in ListSessions, Search, FormatAnalytics, extractToolInput.

Co-Authored-By: Gemini <noreply@google.com>
Co-Authored-By: Virgil <virgil@lethean.io>
2026-02-23 05:25:55 +00:00

63 lines
1.4 KiB
Go

package session
import (
"iter"
"path/filepath"
"slices"
"strings"
"time"
)
// SearchResult represents a match found in a session transcript.
type SearchResult struct {
SessionID string
Timestamp time.Time
Tool string
Match string
}
// Search finds events matching the query across all sessions in the directory.
func Search(projectsDir, query string) ([]SearchResult, error) {
return slices.Collect(SearchSeq(projectsDir, query)), nil
}
// SearchSeq returns an iterator over search results matching the query across all sessions.
func SearchSeq(projectsDir, query string) iter.Seq[SearchResult] {
return func(yield func(SearchResult) bool) {
matches, err := filepath.Glob(filepath.Join(projectsDir, "*.jsonl"))
if err != nil {
return
}
query = strings.ToLower(query)
for _, path := range matches {
sess, _, err := ParseTranscript(path)
if err != nil {
continue
}
for _, evt := range sess.Events {
if evt.Type != "tool_use" {
continue
}
text := strings.ToLower(evt.Input + " " + evt.Output)
if strings.Contains(text, query) {
matchCtx := evt.Input
if matchCtx == "" {
matchCtx = truncate(evt.Output, 120)
}
res := SearchResult{
SessionID: sess.ID,
Timestamp: evt.Timestamp,
Tool: evt.Tool,
Match: matchCtx,
}
if !yield(res) {
return
}
}
}
}
}
}