go-session/search.go
Snider c7696920e0
All checks were successful
Security Scan / security (pull_request) Successful in 7s
Test / test (pull_request) Successful in 1m6s
fix(dx): fix coreerr.E() signatures, add SPDX headers and tests
Fix two coreerr.E() calls missing the required third (error) argument,
add SPDX-Licence-Identifier headers to all source files, and add tests
for PruneSessions, IsExpired, and FetchSession (coverage 88.1% → 92.1%).

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-17 08:03:31 +00:00

64 lines
1.4 KiB
Go

// SPDX-Licence-Identifier: EUPL-1.2
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.EventsSeq() {
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
}
}
}
}
}
}