// Package sqlitevec provides sqlite-vec based vector database integration for claude-mnemonic. package sqlitevec // DocType represents the type of document stored in the vector table. type DocType string const ( DocTypeObservation DocType = "observation" DocTypeSessionSummary DocType = "session_summary" DocTypeUserPrompt DocType = "user_prompt" ) // Document represents a document to store with vector embedding. type Document struct { Metadata map[string]any ID string Content string } // QueryResult represents a search result from vector search. type QueryResult struct { Metadata map[string]any ID string Distance float64 Similarity float64 } // DistanceToSimilarity converts sqlite-vec cosine distance to similarity score. // Cosine distance: 0 = identical, 2 = opposite // Similarity: 1.0 = identical, 0.0 = opposite func DistanceToSimilarity(distance float64) float64 { return 1.0 - (distance / 2.0) } // FilterByThreshold filters results to only include those above the similarity threshold. // If maxResults > 0, also caps the number of results. func FilterByThreshold(results []QueryResult, threshold float64, maxResults int) []QueryResult { var filtered []QueryResult for _, r := range results { if r.Similarity >= threshold { filtered = append(filtered, r) if maxResults > 0 && len(filtered) >= maxResults { break } } } return filtered } // ExtractedIDs contains SQLite IDs extracted from query results, grouped by document type. type ExtractedIDs struct { ObservationIDs []int64 SummaryIDs []int64 PromptIDs []int64 } // BuildWhereFilter creates a where filter map for vector queries. // If docType is empty, no doc_type filter is added. func BuildWhereFilter(docType DocType, project string) map[string]interface{} { where := make(map[string]interface{}) if docType != "" { where["doc_type"] = string(docType) } if project != "" { where["project"] = project } return where } // ExtractIDsByDocType extracts SQLite IDs from query results, // grouped by document type and deduplicated. func ExtractIDsByDocType(results []QueryResult) *ExtractedIDs { ids := &ExtractedIDs{} seenObs := make(map[int64]bool) seenSummary := make(map[int64]bool) seenPrompt := make(map[int64]bool) for _, result := range results { sqliteID, ok := result.Metadata["sqlite_id"].(float64) if !ok { // Try int64 directly if id, ok := result.Metadata["sqlite_id"].(int64); ok { sqliteID = float64(id) } else { continue } } id := int64(sqliteID) docType, _ := result.Metadata["doc_type"].(string) switch docType { case string(DocTypeObservation): if !seenObs[id] { seenObs[id] = true ids.ObservationIDs = append(ids.ObservationIDs, id) } case string(DocTypeSessionSummary): if !seenSummary[id] { seenSummary[id] = true ids.SummaryIDs = append(ids.SummaryIDs, id) } case string(DocTypeUserPrompt): if !seenPrompt[id] { seenPrompt[id] = true ids.PromptIDs = append(ids.PromptIDs, id) } } } return ids } // ExtractObservationIDs extracts observation SQLite IDs from query results, // optionally filtering by project or including global scope. func ExtractObservationIDs(results []QueryResult, project string) []int64 { var ids []int64 seen := make(map[int64]bool) for _, result := range results { sqliteID, ok := result.Metadata["sqlite_id"].(float64) if !ok { if id, ok := result.Metadata["sqlite_id"].(int64); ok { sqliteID = float64(id) } else { continue } } id := int64(sqliteID) docType, _ := result.Metadata["doc_type"].(string) if docType != string(DocTypeObservation) { continue } if project != "" { proj, _ := result.Metadata["project"].(string) scope, _ := result.Metadata["scope"].(string) if proj != project && scope != "global" { continue } } if !seen[id] { seen[id] = true ids = append(ids, id) } } return ids } // ExtractSummaryIDs extracts session summary SQLite IDs from query results. func ExtractSummaryIDs(results []QueryResult, project string) []int64 { var ids []int64 seen := make(map[int64]bool) for _, result := range results { sqliteID, ok := result.Metadata["sqlite_id"].(float64) if !ok { if id, ok := result.Metadata["sqlite_id"].(int64); ok { sqliteID = float64(id) } else { continue } } id := int64(sqliteID) docType, _ := result.Metadata["doc_type"].(string) if docType != string(DocTypeSessionSummary) { continue } if project != "" { proj, _ := result.Metadata["project"].(string) if proj != project { continue } } if !seen[id] { seen[id] = true ids = append(ids, id) } } return ids } // ExtractPromptIDs extracts user prompt SQLite IDs from query results. func ExtractPromptIDs(results []QueryResult, project string) []int64 { var ids []int64 seen := make(map[int64]bool) for _, result := range results { sqliteID, ok := result.Metadata["sqlite_id"].(float64) if !ok { if id, ok := result.Metadata["sqlite_id"].(int64); ok { sqliteID = float64(id) } else { continue } } id := int64(sqliteID) docType, _ := result.Metadata["doc_type"].(string) if docType != string(DocTypeUserPrompt) { continue } if project != "" { proj, _ := result.Metadata["project"].(string) if proj != project { continue } } if !seen[id] { seen[id] = true ids = append(ids, id) } } return ids } // Helper functions for metadata manipulation func copyMetadata(base map[string]any, key string, value any) map[string]any { result := make(map[string]any, len(base)+1) for k, v := range base { result[k] = v } result[key] = value return result } func copyMetadataMulti(base map[string]any, extra map[string]any) map[string]any { result := make(map[string]any, len(base)+len(extra)) for k, v := range base { result[k] = v } for k, v := range extra { result[k] = v } return result } func joinStrings(strs []string, sep string) string { if len(strs) == 0 { return "" } result := strs[0] for i := 1; i < len(strs); i++ { result += sep + strs[i] } return result }