Skip to main content

codemem_core/
traits.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4use crate::{
5    CodememError, Edge, GraphNode, MemoryNode, NodeKind, RawGraphMetrics, RelationshipType,
6    Repository, Session, UnresolvedRefData,
7};
8
9// ── Data types for trait return values ───────────────────────────────────────
10
11/// A pending unresolved reference stored for deferred cross-namespace linking.
12#[derive(Debug, Clone)]
13pub struct PendingUnresolvedRef {
14    /// ID of the unresolved ref record.
15    pub id: String,
16    /// Source symbol qualified name.
17    pub source_node: String,
18    /// The unresolved target name.
19    pub target_name: String,
20    /// Namespace the source belongs to.
21    pub namespace: String,
22    /// File path where the reference occurs.
23    pub file_path: String,
24    /// Line number.
25    pub line: usize,
26    /// Kind of reference: "call", "import", "inherits", etc.
27    pub ref_kind: String,
28    /// Package hint extracted from import context.
29    pub package_hint: Option<String>,
30}
31
32// ── Traits ──────────────────────────────────────────────────────────────────
33
34/// Vector backend trait for HNSW index operations.
35pub trait VectorBackend: Send + Sync {
36    /// Insert a vector with associated ID.
37    fn insert(&mut self, id: &str, embedding: &[f32]) -> Result<(), CodememError>;
38
39    /// Batch insert vectors.
40    fn insert_batch(&mut self, items: &[(String, Vec<f32>)]) -> Result<(), CodememError>;
41
42    /// Search for k nearest neighbors. Returns (id, distance) pairs.
43    fn search(&self, query: &[f32], k: usize) -> Result<Vec<(String, f32)>, CodememError>;
44
45    /// Remove a vector by ID.
46    fn remove(&mut self, id: &str) -> Result<bool, CodememError>;
47
48    /// Save the index to disk.
49    fn save(&self, path: &std::path::Path) -> Result<(), CodememError>;
50
51    /// Load the index from disk.
52    fn load(&mut self, path: &std::path::Path) -> Result<(), CodememError>;
53
54    /// Get index statistics.
55    fn stats(&self) -> VectorStats;
56
57    /// Whether the index has accumulated enough ghost entries to warrant compaction.
58    fn needs_compaction(&self) -> bool {
59        false
60    }
61
62    /// Number of ghost entries left by removals.
63    fn ghost_count(&self) -> usize {
64        0
65    }
66
67    /// Rebuild the index from scratch given all current entries.
68    fn rebuild_from_entries(
69        &mut self,
70        _entries: &[(String, Vec<f32>)],
71    ) -> Result<(), CodememError> {
72        Ok(())
73    }
74}
75
76/// Statistics about the vector index.
77#[derive(Debug, Clone, Default, Serialize, Deserialize)]
78pub struct VectorStats {
79    pub count: usize,
80    pub dimensions: usize,
81    pub metric: String,
82    pub memory_bytes: usize,
83}
84
85/// Graph backend trait for graph operations.
86///
87/// # Default implementations
88///
89/// Extended methods (centrality, community detection, etc.) have no-op defaults
90/// so that the core CRUD + traversal methods are sufficient for a minimal
91/// implementation. Backend authors should override the algorithmic methods
92/// relevant to their storage engine. The in-memory `GraphEngine` overrides all
93/// of them.
94///
95/// # `_ref` methods
96///
97/// `get_node_ref` and `get_edges_ref` return borrowed references into the
98/// backend's in-memory storage. They exist for zero-copy hot-path performance
99/// in the default `GraphEngine`. Database-backed implementations cannot return
100/// references to internal state and should leave the defaults (which return
101/// `None` / empty). Callers that need database compatibility should use the
102/// owned variants `get_node` / `get_edges` instead.
103pub trait GraphBackend: Send + Sync {
104    /// Add a node to the graph.
105    fn add_node(&mut self, node: GraphNode) -> Result<(), CodememError>;
106
107    /// Get a node by ID.
108    fn get_node(&self, id: &str) -> Result<Option<GraphNode>, CodememError>;
109
110    /// Remove a node by ID.
111    fn remove_node(&mut self, id: &str) -> Result<bool, CodememError>;
112
113    /// Add an edge between two nodes.
114    fn add_edge(&mut self, edge: Edge) -> Result<(), CodememError>;
115
116    /// Get edges from a node.
117    fn get_edges(&self, node_id: &str) -> Result<Vec<Edge>, CodememError>;
118
119    /// Remove an edge by ID.
120    fn remove_edge(&mut self, id: &str) -> Result<bool, CodememError>;
121
122    /// BFS traversal from a start node up to max_depth.
123    fn bfs(&self, start_id: &str, max_depth: usize) -> Result<Vec<GraphNode>, CodememError>;
124
125    /// DFS traversal from a start node up to max_depth.
126    fn dfs(&self, start_id: &str, max_depth: usize) -> Result<Vec<GraphNode>, CodememError>;
127
128    /// BFS traversal with filtering: exclude certain node kinds and optionally
129    /// restrict to specific relationship types.
130    fn bfs_filtered(
131        &self,
132        start_id: &str,
133        max_depth: usize,
134        exclude_kinds: &[NodeKind],
135        include_relationships: Option<&[RelationshipType]>,
136    ) -> Result<Vec<GraphNode>, CodememError>;
137
138    /// DFS traversal with filtering: exclude certain node kinds and optionally
139    /// restrict to specific relationship types.
140    fn dfs_filtered(
141        &self,
142        start_id: &str,
143        max_depth: usize,
144        exclude_kinds: &[NodeKind],
145        include_relationships: Option<&[RelationshipType]>,
146    ) -> Result<Vec<GraphNode>, CodememError>;
147
148    /// Shortest path between two nodes.
149    fn shortest_path(&self, from: &str, to: &str) -> Result<Vec<String>, CodememError>;
150
151    /// Get graph statistics.
152    fn stats(&self) -> GraphStats;
153
154    // ── Extended methods (with defaults for backwards compatibility) ──
155
156    /// Get all nodes in the graph.
157    fn get_all_nodes(&self) -> Vec<GraphNode> {
158        Vec::new()
159    }
160
161    /// Zero-copy node lookup. Returns a reference into the backend's internal storage.
162    /// Only meaningful for in-memory backends. Database backends should leave the
163    /// default (returns `None`) and callers should use `get_node()` instead.
164    fn get_node_ref(&self, _id: &str) -> Option<&GraphNode> {
165        None
166    }
167
168    /// Zero-copy edge lookup. Returns references into the backend's internal storage.
169    /// Only meaningful for in-memory backends. Database backends should leave the
170    /// default (returns empty) and callers should use `get_edges()` instead.
171    fn get_edges_ref(&self, _node_id: &str) -> Vec<&Edge> {
172        Vec::new()
173    }
174
175    /// Number of nodes in the graph.
176    fn node_count(&self) -> usize {
177        self.stats().node_count
178    }
179
180    /// Number of edges in the graph.
181    fn edge_count(&self) -> usize {
182        self.stats().edge_count
183    }
184
185    /// Recompute all centrality metrics (PageRank + betweenness).
186    fn recompute_centrality(&mut self) {}
187
188    /// Recompute centrality, optionally including expensive betweenness calculation.
189    fn recompute_centrality_with_options(&mut self, _include_betweenness: bool) {}
190
191    /// Recompute PageRank scoped to a single namespace, updating only that
192    /// namespace's scores in the cache. Nodes from other namespaces are unaffected.
193    fn recompute_centrality_for_namespace(&mut self, _namespace: &str) {}
194
195    /// Lazily compute betweenness centrality if not yet computed.
196    fn ensure_betweenness_computed(&mut self) {}
197
198    /// Compute degree centrality (updates nodes in place).
199    fn compute_centrality(&mut self) {}
200
201    /// Get cached PageRank score for a node.
202    fn get_pagerank(&self, _node_id: &str) -> f64 {
203        0.0
204    }
205
206    /// Get cached betweenness centrality score for a node.
207    fn get_betweenness(&self, _node_id: &str) -> f64 {
208        0.0
209    }
210
211    /// Collect graph metrics for a memory node (used in hybrid scoring).
212    fn raw_graph_metrics_for_memory(&self, _memory_id: &str) -> Option<RawGraphMetrics> {
213        None
214    }
215
216    /// Find connected components (treating graph as undirected).
217    fn connected_components(&self) -> Vec<Vec<String>> {
218        Vec::new()
219    }
220
221    /// Find strongly connected components using Tarjan's algorithm.
222    /// Each SCC is a group where every node can reach every other via directed edges.
223    fn strongly_connected_components(&self) -> Vec<Vec<String>> {
224        Vec::new()
225    }
226
227    /// Compute PageRank scores for all nodes.
228    /// Returns a map from node ID to PageRank score.
229    fn pagerank(&self, _damping: f64, _iterations: usize, _tolerance: f64) -> HashMap<String, f64> {
230        HashMap::new()
231    }
232
233    /// Compute PageRank scores for nodes in a single namespace.
234    /// Only nodes belonging to `namespace` participate; cross-namespace edges are ignored.
235    /// Returns a map from node ID to PageRank score (only for nodes in the namespace).
236    /// Default is abstract; implementers must provide namespace-scoped computation.
237    fn pagerank_for_namespace(
238        &self,
239        _namespace: &str,
240        _damping: f64,
241        _iterations: usize,
242        _tolerance: f64,
243    ) -> HashMap<String, f64> {
244        panic!("pagerank_for_namespace must be implemented; default fallback violates isolation guarantee");
245    }
246
247    /// Run Louvain community detection at the given resolution.
248    /// Returns groups of node IDs, one group per community.
249    fn louvain_communities(&self, _resolution: f64) -> Vec<Vec<String>> {
250        Vec::new()
251    }
252
253    /// Compute topological layers of the graph.
254    /// Returns layers where all nodes in layer i have no dependencies on nodes
255    /// in layer i or later.
256    fn topological_layers(&self) -> Vec<Vec<String>> {
257        Vec::new()
258    }
259
260    /// Return node-to-community-ID mapping for Louvain.
261    fn louvain_with_assignment(&self, resolution: f64) -> HashMap<String, usize> {
262        let communities = self.louvain_communities(resolution);
263        let mut assignment = HashMap::new();
264        for (idx, community) in communities.into_iter().enumerate() {
265            for node_id in community {
266                assignment.insert(node_id, idx);
267            }
268        }
269        assignment
270    }
271
272    /// Return a top-N subgraph: the highest-centrality nodes plus all edges between them.
273    /// Non-structural edges from top-N nodes pull their targets into the result.
274    fn subgraph_top_n(
275        &self,
276        _n: usize,
277        _namespace: Option<&str>,
278        _kinds: Option<&[NodeKind]>,
279    ) -> (Vec<GraphNode>, Vec<Edge>) {
280        (Vec::new(), Vec::new())
281    }
282}
283
284/// Statistics about the graph.
285#[derive(Debug, Clone, Default, Serialize, Deserialize)]
286pub struct GraphStats {
287    pub node_count: usize,
288    pub edge_count: usize,
289    pub node_kind_counts: HashMap<String, usize>,
290    pub relationship_type_counts: HashMap<String, usize>,
291}
292
293// ── Embedding Provider Trait ────────────────────────────────────────────────
294
295/// Trait for pluggable embedding providers.
296pub trait EmbeddingProvider: Send + Sync {
297    /// Embedding vector dimensions.
298    fn dimensions(&self) -> usize;
299
300    /// Embed a single text string.
301    fn embed(&self, text: &str) -> Result<Vec<f32>, crate::CodememError>;
302
303    /// Embed a batch of texts (default: sequential).
304    fn embed_batch(&self, texts: &[&str]) -> Result<Vec<Vec<f32>>, crate::CodememError> {
305        texts.iter().map(|t| self.embed(t)).collect()
306    }
307
308    /// Provider name for display.
309    fn name(&self) -> &str;
310
311    /// Cache statistics: (current_size, capacity). Returns (0, 0) if no cache.
312    fn cache_stats(&self) -> (usize, usize) {
313        (0, 0)
314    }
315}
316
317// ── Storage Stats & Consolidation Types ─────────────────────────────────
318
319/// Database statistics.
320#[derive(Debug, Clone, Serialize, Deserialize)]
321pub struct StorageStats {
322    pub memory_count: usize,
323    pub embedding_count: usize,
324    pub node_count: usize,
325    pub edge_count: usize,
326}
327
328/// A single consolidation log entry.
329#[derive(Debug, Clone)]
330pub struct ConsolidationLogEntry {
331    pub cycle_type: String,
332    pub run_at: i64,
333    pub affected_count: usize,
334}
335
336// ── Storage Backend Trait ───────────────────────────────────────────────
337
338/// Pluggable storage backend trait for all persistence operations.
339///
340/// This trait unifies every persistence concern behind a single interface so
341/// that the engine layer (`CodememEngine`) remains backend-agnostic.
342///
343/// # Method groups
344///
345/// | Group | Methods | Purpose |
346/// |-------|---------|---------|
347/// | **Memory CRUD** | `insert_memory`, `get_memory`, `update_memory`, `delete_memory`, `list_memory_ids`, … | Create, read, update, delete memory nodes |
348/// | **Embedding persistence** | `store_embedding`, `get_embedding`, `delete_embedding`, `list_all_embeddings` | Persist and retrieve embedding vectors |
349/// | **Graph node/edge storage** | `insert_graph_node`, `get_graph_node`, `all_graph_nodes`, `insert_graph_edge`, … | Persist the knowledge graph structure |
350/// | **Sessions** | `start_session`, `end_session`, `list_sessions`, `session_count` | Track interaction sessions |
351/// | **Consolidation** | `insert_consolidation_log`, `last_consolidation_runs` | Record and query memory consolidation runs |
352/// | **Pattern detection** | `get_repeated_searches`, `get_file_hotspots`, `get_tool_usage_stats`, `get_decision_chains` | Cross-session pattern queries |
353/// | **Bulk/batch operations** | `insert_memories_batch`, `store_embeddings_batch`, `insert_graph_nodes_batch`, `insert_graph_edges_batch` | Efficient multi-row inserts |
354/// | **Decay & forgetting** | `decay_stale_memories`, `find_forgettable`, `get_stale_memories_for_decay`, `batch_update_importance` | Power-law decay and garbage collection |
355/// | **Query helpers** | `find_unembedded_memories`, `search_graph_nodes`, `list_memories_filtered`, `find_hash_duplicates` | Filtered searches and dedup |
356/// | **File hash tracking** | `load_file_hashes`, `save_file_hashes` | Incremental indexing support |
357/// | **Session activity** | `record_session_activity`, `get_session_activity_summary`, `get_session_hot_directories`, … | Fine-grained activity tracking |
358/// | **Stats** | `stats` | Database-level statistics |
359///
360/// Implementations include SQLite (default) and can be extended for
361/// SurrealDB, FalkorDB, or other backends.
362pub trait StorageBackend: Send + Sync {
363    // ── Memory CRUD ─────────────────────────────────────────────────
364
365    /// Insert a new memory. Returns Err(Duplicate) if content hash already exists.
366    fn insert_memory(&self, memory: &MemoryNode) -> Result<(), CodememError>;
367
368    /// Get a memory by ID. Updates access_count and last_accessed_at.
369    fn get_memory(&self, id: &str) -> Result<Option<MemoryNode>, CodememError>;
370
371    /// Get a memory by ID without updating access_count or last_accessed_at.
372    /// Use this for internal/system reads (consolidation checks, stats, batch processing).
373    fn get_memory_no_touch(&self, id: &str) -> Result<Option<MemoryNode>, CodememError> {
374        // Default: falls back to get_memory for backwards compatibility.
375        self.get_memory(id)
376    }
377
378    /// Get multiple memories by IDs in a single batch operation.
379    fn get_memories_batch(&self, ids: &[&str]) -> Result<Vec<MemoryNode>, CodememError>;
380
381    /// Update a memory's content and optionally its importance. Re-computes content hash.
382    fn update_memory(
383        &self,
384        id: &str,
385        content: &str,
386        importance: Option<f64>,
387    ) -> Result<(), CodememError>;
388
389    /// Delete a memory by ID. Returns true if a row was deleted.
390    fn delete_memory(&self, id: &str) -> Result<bool, CodememError>;
391
392    /// Delete a memory and all related data (graph nodes/edges, embeddings) atomically.
393    /// Returns true if the memory existed and was deleted.
394    /// Default falls back to individual deletes (non-transactional) for backwards compatibility.
395    fn delete_memory_cascade(&self, id: &str) -> Result<bool, CodememError> {
396        let deleted = self.delete_memory(id)?;
397        if deleted {
398            let _ = self.delete_graph_edges_for_node(id);
399            let _ = self.delete_graph_node(id);
400            let _ = self.delete_embedding(id);
401        }
402        Ok(deleted)
403    }
404
405    /// Delete multiple memories and all related data (graph nodes/edges, embeddings) atomically.
406    /// Returns the number of memories that were actually deleted.
407    /// Default falls back to calling `delete_memory_cascade` per ID for backwards compatibility.
408    fn delete_memories_batch_cascade(&self, ids: &[&str]) -> Result<usize, CodememError> {
409        let mut count = 0;
410        for id in ids {
411            if self.delete_memory_cascade(id)? {
412                count += 1;
413            }
414        }
415        Ok(count)
416    }
417
418    /// Delete all memories whose `expires_at` timestamp is in the past.
419    /// Returns the number of memories deleted.
420    fn delete_expired_memories(&self) -> Result<usize, CodememError>;
421
422    /// Expire (set `expires_at` to now) all `static-analysis` memories
423    /// linked to symbols in the given file path.
424    fn expire_memories_for_file(&self, file_path: &str) -> Result<usize, CodememError>;
425
426    /// List all memory IDs, ordered by created_at descending.
427    fn list_memory_ids(&self) -> Result<Vec<String>, CodememError>;
428
429    /// List memory IDs scoped to a specific namespace.
430    fn list_memory_ids_for_namespace(&self, namespace: &str) -> Result<Vec<String>, CodememError>;
431
432    /// Find memory IDs whose tags contain the given tag value.
433    /// Optionally scoped to a namespace. Excludes `exclude_id`.
434    fn find_memory_ids_by_tag(
435        &self,
436        tag: &str,
437        namespace: Option<&str>,
438        exclude_id: &str,
439    ) -> Result<Vec<String>, CodememError>;
440
441    /// List all distinct namespaces.
442    fn list_namespaces(&self) -> Result<Vec<String>, CodememError>;
443
444    /// Get total memory count.
445    fn memory_count(&self) -> Result<usize, CodememError>;
446
447    // ── Embedding Persistence ───────────────────────────────────────
448
449    /// Store an embedding vector for a memory.
450    fn store_embedding(&self, memory_id: &str, embedding: &[f32]) -> Result<(), CodememError>;
451
452    /// Get an embedding by memory ID.
453    fn get_embedding(&self, memory_id: &str) -> Result<Option<Vec<f32>>, CodememError>;
454
455    /// Delete an embedding by memory ID. Returns true if a row was deleted.
456    fn delete_embedding(&self, memory_id: &str) -> Result<bool, CodememError>;
457
458    /// List all stored embeddings as (memory_id, embedding_vector) pairs.
459    fn list_all_embeddings(&self) -> Result<Vec<(String, Vec<f32>)>, CodememError>;
460
461    // ── Graph Node/Edge Persistence ─────────────────────────────────
462
463    /// Insert or replace a graph node.
464    fn insert_graph_node(&self, node: &GraphNode) -> Result<(), CodememError>;
465
466    /// Get a graph node by ID.
467    fn get_graph_node(&self, id: &str) -> Result<Option<GraphNode>, CodememError>;
468
469    /// Delete a graph node by ID. Returns true if a row was deleted.
470    fn delete_graph_node(&self, id: &str) -> Result<bool, CodememError>;
471
472    /// Get all graph nodes.
473    fn all_graph_nodes(&self) -> Result<Vec<GraphNode>, CodememError>;
474
475    /// Insert or replace a graph edge.
476    fn insert_graph_edge(&self, edge: &Edge) -> Result<(), CodememError>;
477
478    /// Get all edges from or to a node.
479    fn get_edges_for_node(&self, node_id: &str) -> Result<Vec<Edge>, CodememError>;
480
481    /// Get all graph edges.
482    fn all_graph_edges(&self) -> Result<Vec<Edge>, CodememError>;
483
484    /// Delete a single graph edge by ID. Returns true if a row was deleted.
485    fn delete_graph_edge(&self, edge_id: &str) -> Result<bool, CodememError> {
486        // Default: fall back to querying all edges and deleting via for_node.
487        // Backends should override with a direct DELETE WHERE id = ?1.
488        let _ = edge_id;
489        Ok(false)
490    }
491
492    /// Delete all graph edges connected to a node. Returns count deleted.
493    fn delete_graph_edges_for_node(&self, node_id: &str) -> Result<usize, CodememError>;
494
495    /// Delete all graph nodes, edges, and embeddings whose node ID starts with the given prefix.
496    /// Returns count of nodes deleted.
497    fn delete_graph_nodes_by_prefix(&self, prefix: &str) -> Result<usize, CodememError>;
498
499    // ── Sessions ────────────────────────────────────────────────────
500
501    /// Start a new session.
502    fn start_session(&self, id: &str, namespace: Option<&str>) -> Result<(), CodememError>;
503
504    /// End a session with optional summary.
505    fn end_session(&self, id: &str, summary: Option<&str>) -> Result<(), CodememError>;
506
507    /// List sessions, optionally filtered by namespace, up to limit.
508    fn list_sessions(
509        &self,
510        namespace: Option<&str>,
511        limit: usize,
512    ) -> Result<Vec<Session>, CodememError>;
513
514    // ── Consolidation ───────────────────────────────────────────────
515
516    /// Record a consolidation run.
517    fn insert_consolidation_log(
518        &self,
519        cycle_type: &str,
520        affected_count: usize,
521    ) -> Result<(), CodememError>;
522
523    /// Get the last consolidation run for each cycle type.
524    fn last_consolidation_runs(&self) -> Result<Vec<ConsolidationLogEntry>, CodememError>;
525
526    // ── Pattern Detection Queries ───────────────────────────────────
527
528    /// Find repeated search patterns. Returns (pattern, count, memory_ids).
529    fn get_repeated_searches(
530        &self,
531        min_count: usize,
532        namespace: Option<&str>,
533    ) -> Result<Vec<(String, usize, Vec<String>)>, CodememError>;
534
535    /// Find file hotspots. Returns (file_path, count, memory_ids).
536    fn get_file_hotspots(
537        &self,
538        min_count: usize,
539        namespace: Option<&str>,
540    ) -> Result<Vec<(String, usize, Vec<String>)>, CodememError>;
541
542    /// Get tool usage statistics. Returns (tool_name, count) pairs.
543    fn get_tool_usage_stats(
544        &self,
545        namespace: Option<&str>,
546    ) -> Result<Vec<(String, usize)>, CodememError>;
547
548    /// Find decision chains. Returns (file_path, count, memory_ids).
549    fn get_decision_chains(
550        &self,
551        min_count: usize,
552        namespace: Option<&str>,
553    ) -> Result<Vec<(String, usize, Vec<String>)>, CodememError>;
554
555    // ── Bulk Operations ─────────────────────────────────────────────
556
557    /// Decay importance of stale memories older than threshold_ts by decay_factor.
558    /// Returns count of affected memories.
559    fn decay_stale_memories(
560        &self,
561        threshold_ts: i64,
562        decay_factor: f64,
563    ) -> Result<usize, CodememError>;
564
565    /// List memories for creative consolidation: (id, memory_type, tags).
566    fn list_memories_for_creative(
567        &self,
568    ) -> Result<Vec<(String, String, Vec<String>)>, CodememError>;
569
570    /// Find near-duplicate memories by content hash prefix matching.
571    /// Returns (id1, id2, similarity) pairs. Only catches exact content matches
572    /// (hash prefix), not semantic near-duplicates.
573    fn find_hash_duplicates(&self) -> Result<Vec<(String, String, f64)>, CodememError>;
574
575    /// Find memories eligible for forgetting (low importance).
576    /// Returns list of memory IDs.
577    fn find_forgettable(&self, importance_threshold: f64) -> Result<Vec<String>, CodememError>;
578
579    // ── Batch Operations ────────────────────────────────────────────
580
581    /// Insert multiple memories in a single batch. Default impl calls insert_memory in a loop.
582    fn insert_memories_batch(&self, memories: &[MemoryNode]) -> Result<(), CodememError> {
583        for memory in memories {
584            self.insert_memory(memory)?;
585        }
586        Ok(())
587    }
588
589    /// Store multiple embeddings in a single batch. Default impl calls store_embedding in a loop.
590    fn store_embeddings_batch(&self, items: &[(&str, &[f32])]) -> Result<(), CodememError> {
591        for (id, embedding) in items {
592            self.store_embedding(id, embedding)?;
593        }
594        Ok(())
595    }
596
597    /// Insert multiple graph nodes in a single batch. Default impl calls insert_graph_node in a loop.
598    fn insert_graph_nodes_batch(&self, nodes: &[GraphNode]) -> Result<(), CodememError> {
599        for node in nodes {
600            self.insert_graph_node(node)?;
601        }
602        Ok(())
603    }
604
605    /// Insert multiple graph edges in a single batch. Default impl calls insert_graph_edge in a loop.
606    fn insert_graph_edges_batch(&self, edges: &[Edge]) -> Result<(), CodememError> {
607        for edge in edges {
608            self.insert_graph_edge(edge)?;
609        }
610        Ok(())
611    }
612
613    // ── Query Helpers ───────────────────────────────────────────────
614
615    /// Find memories that have no embeddings yet. Returns (id, content) pairs.
616    fn find_unembedded_memories(&self) -> Result<Vec<(String, String)>, CodememError>;
617
618    /// Search graph nodes by label (case-insensitive LIKE). Returns matching nodes
619    /// sorted by centrality descending, limited to `limit` results.
620    fn search_graph_nodes(
621        &self,
622        query: &str,
623        namespace: Option<&str>,
624        limit: usize,
625    ) -> Result<Vec<GraphNode>, CodememError>;
626
627    /// List memories matching a specific tag, with optional namespace filter.
628    fn list_memories_by_tag(
629        &self,
630        tag: &str,
631        namespace: Option<&str>,
632        limit: usize,
633    ) -> Result<Vec<MemoryNode>, CodememError>;
634
635    /// List memories with optional namespace and memory_type filters.
636    fn list_memories_filtered(
637        &self,
638        namespace: Option<&str>,
639        memory_type: Option<&str>,
640    ) -> Result<Vec<MemoryNode>, CodememError>;
641
642    /// Fetch stale memories with access metadata for power-law decay.
643    /// Returns (id, importance, access_count, last_accessed_at).
644    fn get_stale_memories_for_decay(
645        &self,
646        threshold_ts: i64,
647    ) -> Result<Vec<(String, f64, u32, i64)>, CodememError>;
648
649    /// Batch-update importance values. Returns count of updated rows.
650    fn batch_update_importance(&self, updates: &[(String, f64)]) -> Result<usize, CodememError>;
651
652    /// Total session count, optionally filtered by namespace.
653    fn session_count(&self, namespace: Option<&str>) -> Result<usize, CodememError>;
654
655    // ── File Hash Tracking ──────────────────────────────────────────
656
657    /// Load file hashes for incremental indexing, scoped to a namespace.
658    /// Returns path -> hash map.
659    fn load_file_hashes(&self, namespace: &str) -> Result<HashMap<String, String>, CodememError>;
660
661    /// Save file hashes for incremental indexing, scoped to a namespace.
662    fn save_file_hashes(
663        &self,
664        namespace: &str,
665        hashes: &HashMap<String, String>,
666    ) -> Result<(), CodememError>;
667
668    // ── Session Activity Tracking ─────────────────────────────────
669
670    /// Record a session activity event (tool use with context).
671    fn record_session_activity(
672        &self,
673        session_id: &str,
674        tool_name: &str,
675        file_path: Option<&str>,
676        directory: Option<&str>,
677        pattern: Option<&str>,
678    ) -> Result<(), CodememError>;
679
680    /// Get a summary of session activity counts.
681    fn get_session_activity_summary(
682        &self,
683        session_id: &str,
684    ) -> Result<crate::SessionActivitySummary, CodememError>;
685
686    /// Get the most active directories in a session. Returns (directory, count) pairs.
687    fn get_session_hot_directories(
688        &self,
689        session_id: &str,
690        limit: usize,
691    ) -> Result<Vec<(String, usize)>, CodememError>;
692
693    /// Check whether a particular auto-insight dedup tag already exists for a session.
694    fn has_auto_insight(&self, session_id: &str, dedup_tag: &str) -> Result<bool, CodememError>;
695
696    /// Count how many Read events occurred in a directory during a session.
697    fn count_directory_reads(
698        &self,
699        session_id: &str,
700        directory: &str,
701    ) -> Result<usize, CodememError>;
702
703    /// Check if a file was read in the current session.
704    fn was_file_read_in_session(
705        &self,
706        session_id: &str,
707        file_path: &str,
708    ) -> Result<bool, CodememError>;
709
710    /// Count how many times a search pattern was used in a session.
711    fn count_search_pattern_in_session(
712        &self,
713        session_id: &str,
714        pattern: &str,
715    ) -> Result<usize, CodememError>;
716
717    // ── Repository Management ────────────────────────────────────────
718
719    /// List all registered repositories.
720    fn list_repos(&self) -> Result<Vec<Repository>, CodememError>;
721
722    /// Add a new repository.
723    fn add_repo(&self, repo: &Repository) -> Result<(), CodememError>;
724
725    /// Get a repository by ID.
726    fn get_repo(&self, id: &str) -> Result<Option<Repository>, CodememError>;
727
728    /// Remove a repository by ID. Returns true if it existed.
729    fn remove_repo(&self, id: &str) -> Result<bool, CodememError>;
730
731    /// Update a repository's status and optionally its last-indexed timestamp.
732    fn update_repo_status(
733        &self,
734        id: &str,
735        status: &str,
736        indexed_at: Option<&str>,
737    ) -> Result<(), CodememError>;
738
739    // ── Stats ───────────────────────────────────────────────────────
740
741    /// Get database statistics.
742    fn stats(&self) -> Result<StorageStats, CodememError>;
743
744    // ── Transaction Control ────────────────────────────────────────
745
746    /// Begin an explicit transaction.
747    ///
748    /// While a transaction is active, individual storage methods (e.g.
749    /// `insert_memory`, `insert_graph_node`) participate in it instead of
750    /// starting their own. Call `commit_transaction` to persist or
751    /// `rollback_transaction` to discard.
752    ///
753    /// Default implementation is a no-op for backends that don't support
754    /// explicit transaction control.
755    fn begin_transaction(&self) -> Result<(), CodememError> {
756        Ok(())
757    }
758
759    /// Commit the active transaction started by `begin_transaction`.
760    ///
761    /// Default implementation is a no-op.
762    fn commit_transaction(&self) -> Result<(), CodememError> {
763        Ok(())
764    }
765
766    /// Roll back the active transaction started by `begin_transaction`.
767    ///
768    /// Default implementation is a no-op.
769    fn rollback_transaction(&self) -> Result<(), CodememError> {
770        Ok(())
771    }
772
773    // ── Cross-Repo Persistence ────────────────────────────────────────
774
775    /// Get graph edges where either the source or destination node belongs to the
776    /// given namespace (cross-namespace query). When `include_cross_namespace` is false,
777    /// this behaves like `graph_edges_for_namespace` (both endpoints in namespace).
778    fn graph_edges_for_namespace_with_cross(
779        &self,
780        _namespace: &str,
781        _include_cross_namespace: bool,
782    ) -> Result<Vec<Edge>, CodememError> {
783        Ok(Vec::new())
784    }
785
786    /// Upsert a package into the cross-repo package registry.
787    fn upsert_package_registry(
788        &self,
789        _package_name: &str,
790        _namespace: &str,
791        _version: &str,
792        _manifest: &str,
793    ) -> Result<(), CodememError> {
794        Ok(())
795    }
796
797    /// Store an unresolved reference for future cross-namespace linking.
798    #[allow(clippy::too_many_arguments)]
799    fn store_unresolved_ref(
800        &self,
801        _source_qualified_name: &str,
802        _target_name: &str,
803        _source_namespace: &str,
804        _file_path: &str,
805        _line: usize,
806        _ref_kind: &str,
807        _package_hint: Option<&str>,
808    ) -> Result<(), CodememError> {
809        Ok(())
810    }
811
812    /// Batch store unresolved references. Default falls back to per-ref calls.
813    fn store_unresolved_refs_batch(
814        &self,
815        refs: &[UnresolvedRefData],
816    ) -> Result<usize, CodememError> {
817        let mut count = 0;
818        for r in refs {
819            self.store_unresolved_ref(
820                &r.source_qualified_name,
821                &r.target_name,
822                &r.namespace,
823                &r.file_path,
824                r.line,
825                &r.ref_kind,
826                r.package_hint.as_deref(),
827            )?;
828            count += 1;
829        }
830        Ok(count)
831    }
832
833    /// List all registered packages. Returns (name, namespace, manifest_path) tuples.
834    fn list_registered_packages(&self) -> Result<Vec<(String, String, String)>, CodememError> {
835        Ok(Vec::new())
836    }
837
838    /// List all pending unresolved refs with full context.
839    fn list_pending_unresolved_refs(&self) -> Result<Vec<PendingUnresolvedRef>, CodememError> {
840        Ok(Vec::new())
841    }
842
843    /// Delete a resolved unresolved ref by ID.
844    fn delete_unresolved_ref(&self, _id: &str) -> Result<(), CodememError> {
845        Ok(())
846    }
847
848    /// Count unresolved refs for a given namespace.
849    fn count_unresolved_refs(&self, _namespace: &str) -> Result<usize, CodememError> {
850        Ok(0)
851    }
852
853    /// List registered packages for a given namespace.
854    /// Returns (name, namespace, manifest_path) tuples.
855    fn list_registered_packages_for_namespace(
856        &self,
857        _namespace: &str,
858    ) -> Result<Vec<(String, String, String)>, CodememError> {
859        Ok(Vec::new())
860    }
861
862    /// Store a detected API endpoint.
863    fn store_api_endpoint(
864        &self,
865        _method: &str,
866        _path: &str,
867        _handler_symbol: &str,
868        _namespace: &str,
869    ) -> Result<(), CodememError> {
870        Ok(())
871    }
872
873    /// Store a detected client call.
874    fn store_api_client_call(
875        &self,
876        _library: &str,
877        _method: Option<&str>,
878        _caller_symbol: &str,
879        _namespace: &str,
880    ) -> Result<(), CodememError> {
881        Ok(())
882    }
883
884    /// List detected API endpoints for a namespace.
885    /// Returns (method, path, handler_symbol, namespace) tuples.
886    fn list_api_endpoints(
887        &self,
888        _namespace: &str,
889    ) -> Result<Vec<(String, String, String, String)>, CodememError> {
890        Ok(Vec::new())
891    }
892
893    /// Store a detected event channel.
894    fn store_event_channel(
895        &self,
896        channel: &str,
897        direction: &str,
898        protocol: &str,
899        handler: &str,
900        namespace: &str,
901        description: &str,
902    ) -> Result<(), CodememError> {
903        let _ = (
904            channel,
905            direction,
906            protocol,
907            handler,
908            namespace,
909            description,
910        );
911        Ok(())
912    }
913
914    /// List event channels for a namespace.
915    /// Returns (channel, direction, protocol, handler, description) tuples.
916    #[allow(clippy::type_complexity)]
917    fn list_event_channels(
918        &self,
919        namespace: &str,
920    ) -> Result<Vec<(String, String, String, String, String)>, CodememError> {
921        let _ = namespace;
922        Ok(Vec::new())
923    }
924
925    /// List all event channels across all namespaces.
926    /// Returns (channel, direction, protocol, handler, namespace, description) tuples.
927    #[allow(clippy::type_complexity)]
928    fn list_all_event_channels(
929        &self,
930    ) -> Result<Vec<(String, String, String, String, String, String)>, CodememError> {
931        Ok(Vec::new())
932    }
933}