Initial commit: knox knowledge index
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
package ingest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type jsonKind int
|
||||
|
||||
const (
|
||||
jsonObject jsonKind = iota
|
||||
jsonArray
|
||||
)
|
||||
|
||||
var _ Ingester = (*SessionDiffIngester)(nil)
|
||||
|
||||
type SessionDiffIngester struct {
|
||||
Version string
|
||||
}
|
||||
|
||||
func NewSessionDiffIngester() *SessionDiffIngester {
|
||||
return &SessionDiffIngester{Version: "session-diff/v1"}
|
||||
}
|
||||
|
||||
func (s *SessionDiffIngester) SourceID() string { return "opencode-session" }
|
||||
|
||||
func (s *SessionDiffIngester) Ingest(path string) (*IngestResult, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read session diff: %w", err)
|
||||
}
|
||||
if len(data) < 10 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
raw, kind := parseJSON(data)
|
||||
if raw == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
sessionID := guessString(raw, "session_id", "id", "sessionId")
|
||||
project := guessString(raw, "project", "project_id", "projectPath", "projectId")
|
||||
title := guessString(raw, "title", "name", "label")
|
||||
createdAt := guessString(raw, "created_at", "timestamp", "date", "started_at")
|
||||
if createdAt == "" {
|
||||
if fi, err := os.Stat(path); err == nil {
|
||||
createdAt = fi.ModTime().UTC().Format(time.RFC3339)
|
||||
}
|
||||
}
|
||||
|
||||
if sessionID == "" {
|
||||
base := filepath.Base(path)
|
||||
sessionID = strings.TrimSuffix(base, filepath.Ext(base))
|
||||
sessionID = strings.TrimPrefix(sessionID, "ses_")
|
||||
}
|
||||
if title == "" {
|
||||
title = fmt.Sprintf("Session %s", sessionID[:min(8, len(sessionID))])
|
||||
}
|
||||
|
||||
summary := extractSummary(raw, kind)
|
||||
fp := FingerprintWithMeta(data, map[string]string{
|
||||
"session_id": sessionID,
|
||||
"source": "session_diff",
|
||||
})
|
||||
|
||||
confidence := 0.7
|
||||
if kind == jsonArray {
|
||||
confidence = 0.4
|
||||
}
|
||||
|
||||
return &IngestResult{
|
||||
Fingerprint: fp,
|
||||
SourceID: s.SourceID(),
|
||||
SourcePath: path,
|
||||
Project: project,
|
||||
ContentType: ".json",
|
||||
Title: title,
|
||||
Summary: summary,
|
||||
CreatedAt: createdAt,
|
||||
LineStart: 0,
|
||||
LineEnd: 0,
|
||||
Confidence: confidence,
|
||||
IngesterVersion: s.Version,
|
||||
Provenance: map[string]any{
|
||||
"session_id": sessionID,
|
||||
"kind": kindName(kind),
|
||||
"field_count": len(raw),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseJSON(data []byte) (map[string]any, jsonKind) {
|
||||
var obj map[string]any
|
||||
if err := json.Unmarshal(data, &obj); err == nil {
|
||||
return obj, jsonObject
|
||||
}
|
||||
var arr []any
|
||||
if err := json.Unmarshal(data, &arr); err == nil {
|
||||
m := make(map[string]any)
|
||||
m["_count"] = float64(len(arr))
|
||||
m["_kind"] = "array"
|
||||
if len(arr) > 0 {
|
||||
if first, ok := arr[0].(map[string]any); ok {
|
||||
for k, v := range first {
|
||||
if str, ok := v.(string); ok && len(str) < 200 {
|
||||
m["_sample_"+k] = str
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return m, jsonArray
|
||||
}
|
||||
return nil, jsonObject
|
||||
}
|
||||
|
||||
func extractSummary(raw map[string]any, kind jsonKind) string {
|
||||
if msg, ok := raw["message"].(string); ok && len(msg) > 0 {
|
||||
return truncate(msg, 200)
|
||||
}
|
||||
if msgs, ok := raw["messages"].([]any); ok && len(msgs) > 0 {
|
||||
parts := make([]string, 0, len(msgs))
|
||||
for _, m := range msgs {
|
||||
if mm, ok := m.(map[string]any); ok {
|
||||
if c, ok := mm["content"].(string); ok {
|
||||
parts = append(parts, truncate(c, 100))
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings.Join(parts, " | ")
|
||||
}
|
||||
if count, ok := raw["_count"].(float64); ok && kind == jsonArray {
|
||||
return fmt.Sprintf("Array with %.0f entries", count)
|
||||
}
|
||||
b, _ := json.Marshal(raw)
|
||||
return truncate(string(b), 200)
|
||||
}
|
||||
|
||||
func guessString(m map[string]any, keys ...string) string {
|
||||
for _, k := range keys {
|
||||
if v, ok := m[k].(string); ok && v != "" {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func kindName(k jsonKind) string {
|
||||
if k == jsonArray {
|
||||
return "array"
|
||||
}
|
||||
return "object"
|
||||
}
|
||||
Reference in New Issue
Block a user