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    /// Lazily compute betweenness centrality if not yet computed.
192    fn ensure_betweenness_computed(&mut self) {}
193
194    /// Compute degree centrality (updates nodes in place).
195    fn compute_centrality(&mut self) {}
196
197    /// Get cached PageRank score for a node.
198    fn get_pagerank(&self, _node_id: &str) -> f64 {
199        0.0
200    }
201
202    /// Get cached betweenness centrality score for a node.
203    fn get_betweenness(&self, _node_id: &str) -> f64 {
204        0.0
205    }
206
207    /// Collect graph metrics for a memory node (used in hybrid scoring).
208    fn raw_graph_metrics_for_memory(&self, _memory_id: &str) -> Option<RawGraphMetrics> {
209        None
210    }
211
212    /// Find connected components (treating graph as undirected).
213    fn connected_components(&self) -> Vec<Vec<String>> {
214        Vec::new()
215    }
216
217    /// Compute PageRank scores for all nodes.
218    /// Returns a map from node ID to PageRank score.
219    fn pagerank(&self, _damping: f64, _iterations: usize, _tolerance: f64) -> HashMap<String, f64> {
220        HashMap::new()
221    }
222
223    /// Run Louvain community detection at the given resolution.
224    /// Returns groups of node IDs, one group per community.
225    fn louvain_communities(&self, _resolution: f64) -> Vec<Vec<String>> {
226        Vec::new()
227    }
228
229    /// Compute topological layers of the graph.
230    /// Returns layers where all nodes in layer i have no dependencies on nodes
231    /// in layer i or later.
232    fn topological_layers(&self) -> Vec<Vec<String>> {
233        Vec::new()
234    }
235
236    /// Return node-to-community-ID mapping for Louvain.
237    fn louvain_with_assignment(&self, resolution: f64) -> HashMap<String, usize> {
238        let communities = self.louvain_communities(resolution);
239        let mut assignment = HashMap::new();
240        for (idx, community) in communities.into_iter().enumerate() {
241            for node_id in community {
242                assignment.insert(node_id, idx);
243            }
244        }
245        assignment
246    }
247
248    /// Return a top-N subgraph: the highest-centrality nodes plus all edges between them.
249    /// Non-structural edges from top-N nodes pull their targets into the result.
250    fn subgraph_top_n(
251        &self,
252        _n: usize,
253        _namespace: Option<&str>,
254        _kinds: Option<&[NodeKind]>,
255    ) -> (Vec<GraphNode>, Vec<Edge>) {
256        (Vec::new(), Vec::new())
257    }
258}
259
260/// Statistics about the graph.
261#[derive(Debug, Clone, Default, Serialize, Deserialize)]
262pub struct GraphStats {
263    pub node_count: usize,
264    pub edge_count: usize,
265    pub node_kind_counts: HashMap<String, usize>,
266    pub relationship_type_counts: HashMap<String, usize>,
267}
268
269// ── Embedding Provider Trait ────────────────────────────────────────────────
270
271/// Trait for pluggable embedding providers.
272pub trait EmbeddingProvider: Send + Sync {
273    /// Embedding vector dimensions.
274    fn dimensions(&self) -> usize;
275
276    /// Embed a single text string.
277    fn embed(&self, text: &str) -> Result<Vec<f32>, crate::CodememError>;
278
279    /// Embed a batch of texts (default: sequential).
280    fn embed_batch(&self, texts: &[&str]) -> Result<Vec<Vec<f32>>, crate::CodememError> {
281        texts.iter().map(|t| self.embed(t)).collect()
282    }
283
284    /// Provider name for display.
285    fn name(&self) -> &str;
286
287    /// Cache statistics: (current_size, capacity). Returns (0, 0) if no cache.
288    fn cache_stats(&self) -> (usize, usize) {
289        (0, 0)
290    }
291}
292
293// ── Storage Stats & Consolidation Types ─────────────────────────────────
294
295/// Database statistics.
296#[derive(Debug, Clone, Serialize, Deserialize)]
297pub struct StorageStats {
298    pub memory_count: usize,
299    pub embedding_count: usize,
300    pub node_count: usize,
301    pub edge_count: usize,
302}
303
304/// A single consolidation log entry.
305#[derive(Debug, Clone)]
306pub struct ConsolidationLogEntry {
307    pub cycle_type: String,
308    pub run_at: i64,
309    pub affected_count: usize,
310}
311
312// ── Storage Backend Trait ───────────────────────────────────────────────
313
314/// Pluggable storage backend trait for all persistence operations.
315///
316/// This trait unifies every persistence concern behind a single interface so
317/// that the engine layer (`CodememEngine`) remains backend-agnostic.
318///
319/// # Method groups
320///
321/// | Group | Methods | Purpose |
322/// |-------|---------|---------|
323/// | **Memory CRUD** | `insert_memory`, `get_memory`, `update_memory`, `delete_memory`, `list_memory_ids`, … | Create, read, update, delete memory nodes |
324/// | **Embedding persistence** | `store_embedding`, `get_embedding`, `delete_embedding`, `list_all_embeddings` | Persist and retrieve embedding vectors |
325/// | **Graph node/edge storage** | `insert_graph_node`, `get_graph_node`, `all_graph_nodes`, `insert_graph_edge`, … | Persist the knowledge graph structure |
326/// | **Sessions** | `start_session`, `end_session`, `list_sessions`, `session_count` | Track interaction sessions |
327/// | **Consolidation** | `insert_consolidation_log`, `last_consolidation_runs` | Record and query memory consolidation runs |
328/// | **Pattern detection** | `get_repeated_searches`, `get_file_hotspots`, `get_tool_usage_stats`, `get_decision_chains` | Cross-session pattern queries |
329/// | **Bulk/batch operations** | `insert_memories_batch`, `store_embeddings_batch`, `insert_graph_nodes_batch`, `insert_graph_edges_batch` | Efficient multi-row inserts |
330/// | **Decay & forgetting** | `decay_stale_memories`, `find_forgettable`, `get_stale_memories_for_decay`, `batch_update_importance` | Power-law decay and garbage collection |
331/// | **Query helpers** | `find_unembedded_memories`, `search_graph_nodes`, `list_memories_filtered`, `find_hash_duplicates` | Filtered searches and dedup |
332/// | **File hash tracking** | `load_file_hashes`, `save_file_hashes` | Incremental indexing support |
333/// | **Session activity** | `record_session_activity`, `get_session_activity_summary`, `get_session_hot_directories`, … | Fine-grained activity tracking |
334/// | **Stats** | `stats` | Database-level statistics |
335///
336/// Implementations include SQLite (default) and can be extended for
337/// SurrealDB, FalkorDB, or other backends.
338pub trait StorageBackend: Send + Sync {
339    // ── Memory CRUD ─────────────────────────────────────────────────
340
341    /// Insert a new memory. Returns Err(Duplicate) if content hash already exists.
342    fn insert_memory(&self, memory: &MemoryNode) -> Result<(), CodememError>;
343
344    /// Get a memory by ID. Updates access_count and last_accessed_at.
345    fn get_memory(&self, id: &str) -> Result<Option<MemoryNode>, CodememError>;
346
347    /// Get a memory by ID without updating access_count or last_accessed_at.
348    /// Use this for internal/system reads (consolidation checks, stats, batch processing).
349    fn get_memory_no_touch(&self, id: &str) -> Result<Option<MemoryNode>, CodememError> {
350        // Default: falls back to get_memory for backwards compatibility.
351        self.get_memory(id)
352    }
353
354    /// Get multiple memories by IDs in a single batch operation.
355    fn get_memories_batch(&self, ids: &[&str]) -> Result<Vec<MemoryNode>, CodememError>;
356
357    /// Update a memory's content and optionally its importance. Re-computes content hash.
358    fn update_memory(
359        &self,
360        id: &str,
361        content: &str,
362        importance: Option<f64>,
363    ) -> Result<(), CodememError>;
364
365    /// Delete a memory by ID. Returns true if a row was deleted.
366    fn delete_memory(&self, id: &str) -> Result<bool, CodememError>;
367
368    /// Delete a memory and all related data (graph nodes/edges, embeddings) atomically.
369    /// Returns true if the memory existed and was deleted.
370    /// Default falls back to individual deletes (non-transactional) for backwards compatibility.
371    fn delete_memory_cascade(&self, id: &str) -> Result<bool, CodememError> {
372        let deleted = self.delete_memory(id)?;
373        if deleted {
374            let _ = self.delete_graph_edges_for_node(id);
375            let _ = self.delete_graph_node(id);
376            let _ = self.delete_embedding(id);
377        }
378        Ok(deleted)
379    }
380
381    /// Delete multiple memories and all related data (graph nodes/edges, embeddings) atomically.
382    /// Returns the number of memories that were actually deleted.
383    /// Default falls back to calling `delete_memory_cascade` per ID for backwards compatibility.
384    fn delete_memories_batch_cascade(&self, ids: &[&str]) -> Result<usize, CodememError> {
385        let mut count = 0;
386        for id in ids {
387            if self.delete_memory_cascade(id)? {
388                count += 1;
389            }
390        }
391        Ok(count)
392    }
393
394    /// Delete all memories whose `expires_at` timestamp is in the past.
395    /// Returns the number of memories deleted.
396    fn delete_expired_memories(&self) -> Result<usize, CodememError>;
397
398    /// Expire (set `expires_at` to now) all `static-analysis` memories
399    /// linked to symbols in the given file path.
400    fn expire_memories_for_file(&self, file_path: &str) -> Result<usize, CodememError>;
401
402    /// List all memory IDs, ordered by created_at descending.
403    fn list_memory_ids(&self) -> Result<Vec<String>, CodememError>;
404
405    /// List memory IDs scoped to a specific namespace.
406    fn list_memory_ids_for_namespace(&self, namespace: &str) -> Result<Vec<String>, CodememError>;
407
408    /// Find memory IDs whose tags contain the given tag value.
409    /// Optionally scoped to a namespace. Excludes `exclude_id`.
410    fn find_memory_ids_by_tag(
411        &self,
412        tag: &str,
413        namespace: Option<&str>,
414        exclude_id: &str,
415    ) -> Result<Vec<String>, CodememError>;
416
417    /// List all distinct namespaces.
418    fn list_namespaces(&self) -> Result<Vec<String>, CodememError>;
419
420    /// Get total memory count.
421    fn memory_count(&self) -> Result<usize, CodememError>;
422
423    // ── Embedding Persistence ───────────────────────────────────────
424
425    /// Store an embedding vector for a memory.
426    fn store_embedding(&self, memory_id: &str, embedding: &[f32]) -> Result<(), CodememError>;
427
428    /// Get an embedding by memory ID.
429    fn get_embedding(&self, memory_id: &str) -> Result<Option<Vec<f32>>, CodememError>;
430
431    /// Delete an embedding by memory ID. Returns true if a row was deleted.
432    fn delete_embedding(&self, memory_id: &str) -> Result<bool, CodememError>;
433
434    /// List all stored embeddings as (memory_id, embedding_vector) pairs.
435    fn list_all_embeddings(&self) -> Result<Vec<(String, Vec<f32>)>, CodememError>;
436
437    // ── Graph Node/Edge Persistence ─────────────────────────────────
438
439    /// Insert or replace a graph node.
440    fn insert_graph_node(&self, node: &GraphNode) -> Result<(), CodememError>;
441
442    /// Get a graph node by ID.
443    fn get_graph_node(&self, id: &str) -> Result<Option<GraphNode>, CodememError>;
444
445    /// Delete a graph node by ID. Returns true if a row was deleted.
446    fn delete_graph_node(&self, id: &str) -> Result<bool, CodememError>;
447
448    /// Get all graph nodes.
449    fn all_graph_nodes(&self) -> Result<Vec<GraphNode>, CodememError>;
450
451    /// Insert or replace a graph edge.
452    fn insert_graph_edge(&self, edge: &Edge) -> Result<(), CodememError>;
453
454    /// Get all edges from or to a node.
455    fn get_edges_for_node(&self, node_id: &str) -> Result<Vec<Edge>, CodememError>;
456
457    /// Get all graph edges.
458    fn all_graph_edges(&self) -> Result<Vec<Edge>, CodememError>;
459
460    /// Delete a single graph edge by ID. Returns true if a row was deleted.
461    fn delete_graph_edge(&self, edge_id: &str) -> Result<bool, CodememError> {
462        // Default: fall back to querying all edges and deleting via for_node.
463        // Backends should override with a direct DELETE WHERE id = ?1.
464        let _ = edge_id;
465        Ok(false)
466    }
467
468    /// Delete all graph edges connected to a node. Returns count deleted.
469    fn delete_graph_edges_for_node(&self, node_id: &str) -> Result<usize, CodememError>;
470
471    /// Delete all graph nodes, edges, and embeddings whose node ID starts with the given prefix.
472    /// Returns count of nodes deleted.
473    fn delete_graph_nodes_by_prefix(&self, prefix: &str) -> Result<usize, CodememError>;
474
475    // ── Sessions ────────────────────────────────────────────────────
476
477    /// Start a new session.
478    fn start_session(&self, id: &str, namespace: Option<&str>) -> Result<(), CodememError>;
479
480    /// End a session with optional summary.
481    fn end_session(&self, id: &str, summary: Option<&str>) -> Result<(), CodememError>;
482
483    /// List sessions, optionally filtered by namespace, up to limit.
484    fn list_sessions(
485        &self,
486        namespace: Option<&str>,
487        limit: usize,
488    ) -> Result<Vec<Session>, CodememError>;
489
490    // ── Consolidation ───────────────────────────────────────────────
491
492    /// Record a consolidation run.
493    fn insert_consolidation_log(
494        &self,
495        cycle_type: &str,
496        affected_count: usize,
497    ) -> Result<(), CodememError>;
498
499    /// Get the last consolidation run for each cycle type.
500    fn last_consolidation_runs(&self) -> Result<Vec<ConsolidationLogEntry>, CodememError>;
501
502    // ── Pattern Detection Queries ───────────────────────────────────
503
504    /// Find repeated search patterns. Returns (pattern, count, memory_ids).
505    fn get_repeated_searches(
506        &self,
507        min_count: usize,
508        namespace: Option<&str>,
509    ) -> Result<Vec<(String, usize, Vec<String>)>, CodememError>;
510
511    /// Find file hotspots. Returns (file_path, count, memory_ids).
512    fn get_file_hotspots(
513        &self,
514        min_count: usize,
515        namespace: Option<&str>,
516    ) -> Result<Vec<(String, usize, Vec<String>)>, CodememError>;
517
518    /// Get tool usage statistics. Returns (tool_name, count) pairs.
519    fn get_tool_usage_stats(
520        &self,
521        namespace: Option<&str>,
522    ) -> Result<Vec<(String, usize)>, CodememError>;
523
524    /// Find decision chains. Returns (file_path, count, memory_ids).
525    fn get_decision_chains(
526        &self,
527        min_count: usize,
528        namespace: Option<&str>,
529    ) -> Result<Vec<(String, usize, Vec<String>)>, CodememError>;
530
531    // ── Bulk Operations ─────────────────────────────────────────────
532
533    /// Decay importance of stale memories older than threshold_ts by decay_factor.
534    /// Returns count of affected memories.
535    fn decay_stale_memories(
536        &self,
537        threshold_ts: i64,
538        decay_factor: f64,
539    ) -> Result<usize, CodememError>;
540
541    /// List memories for creative consolidation: (id, memory_type, tags).
542    fn list_memories_for_creative(
543        &self,
544    ) -> Result<Vec<(String, String, Vec<String>)>, CodememError>;
545
546    /// Find near-duplicate memories by content hash prefix matching.
547    /// Returns (id1, id2, similarity) pairs. Only catches exact content matches
548    /// (hash prefix), not semantic near-duplicates.
549    fn find_hash_duplicates(&self) -> Result<Vec<(String, String, f64)>, CodememError>;
550
551    /// Find memories eligible for forgetting (low importance).
552    /// Returns list of memory IDs.
553    fn find_forgettable(&self, importance_threshold: f64) -> Result<Vec<String>, CodememError>;
554
555    // ── Batch Operations ────────────────────────────────────────────
556
557    /// Insert multiple memories in a single batch. Default impl calls insert_memory in a loop.
558    fn insert_memories_batch(&self, memories: &[MemoryNode]) -> Result<(), CodememError> {
559        for memory in memories {
560            self.insert_memory(memory)?;
561        }
562        Ok(())
563    }
564
565    /// Store multiple embeddings in a single batch. Default impl calls store_embedding in a loop.
566    fn store_embeddings_batch(&self, items: &[(&str, &[f32])]) -> Result<(), CodememError> {
567        for (id, embedding) in items {
568            self.store_embedding(id, embedding)?;
569        }
570        Ok(())
571    }
572
573    /// Insert multiple graph nodes in a single batch. Default impl calls insert_graph_node in a loop.
574    fn insert_graph_nodes_batch(&self, nodes: &[GraphNode]) -> Result<(), CodememError> {
575        for node in nodes {
576            self.insert_graph_node(node)?;
577        }
578        Ok(())
579    }
580
581    /// Insert multiple graph edges in a single batch. Default impl calls insert_graph_edge in a loop.
582    fn insert_graph_edges_batch(&self, edges: &[Edge]) -> Result<(), CodememError> {
583        for edge in edges {
584            self.insert_graph_edge(edge)?;
585        }
586        Ok(())
587    }
588
589    // ── Query Helpers ───────────────────────────────────────────────
590
591    /// Find memories that have no embeddings yet. Returns (id, content) pairs.
592    fn find_unembedded_memories(&self) -> Result<Vec<(String, String)>, CodememError>;
593
594    /// Search graph nodes by label (case-insensitive LIKE). Returns matching nodes
595    /// sorted by centrality descending, limited to `limit` results.
596    fn search_graph_nodes(
597        &self,
598        query: &str,
599        namespace: Option<&str>,
600        limit: usize,
601    ) -> Result<Vec<GraphNode>, CodememError>;
602
603    /// List memories matching a specific tag, with optional namespace filter.
604    fn list_memories_by_tag(
605        &self,
606        tag: &str,
607        namespace: Option<&str>,
608        limit: usize,
609    ) -> Result<Vec<MemoryNode>, CodememError>;
610
611    /// List memories with optional namespace and memory_type filters.
612    fn list_memories_filtered(
613        &self,
614        namespace: Option<&str>,
615        memory_type: Option<&str>,
616    ) -> Result<Vec<MemoryNode>, CodememError>;
617
618    /// Fetch stale memories with access metadata for power-law decay.
619    /// Returns (id, importance, access_count, last_accessed_at).
620    fn get_stale_memories_for_decay(
621        &self,
622        threshold_ts: i64,
623    ) -> Result<Vec<(String, f64, u32, i64)>, CodememError>;
624
625    /// Batch-update importance values. Returns count of updated rows.
626    fn batch_update_importance(&self, updates: &[(String, f64)]) -> Result<usize, CodememError>;
627
628    /// Total session count, optionally filtered by namespace.
629    fn session_count(&self, namespace: Option<&str>) -> Result<usize, CodememError>;
630
631    // ── File Hash Tracking ──────────────────────────────────────────
632
633    /// Load file hashes for incremental indexing, scoped to a namespace.
634    /// Returns path -> hash map.
635    fn load_file_hashes(&self, namespace: &str) -> Result<HashMap<String, String>, CodememError>;
636
637    /// Save file hashes for incremental indexing, scoped to a namespace.
638    fn save_file_hashes(
639        &self,
640        namespace: &str,
641        hashes: &HashMap<String, String>,
642    ) -> Result<(), CodememError>;
643
644    // ── Session Activity Tracking ─────────────────────────────────
645
646    /// Record a session activity event (tool use with context).
647    fn record_session_activity(
648        &self,
649        session_id: &str,
650        tool_name: &str,
651        file_path: Option<&str>,
652        directory: Option<&str>,
653        pattern: Option<&str>,
654    ) -> Result<(), CodememError>;
655
656    /// Get a summary of session activity counts.
657    fn get_session_activity_summary(
658        &self,
659        session_id: &str,
660    ) -> Result<crate::SessionActivitySummary, CodememError>;
661
662    /// Get the most active directories in a session. Returns (directory, count) pairs.
663    fn get_session_hot_directories(
664        &self,
665        session_id: &str,
666        limit: usize,
667    ) -> Result<Vec<(String, usize)>, CodememError>;
668
669    /// Check whether a particular auto-insight dedup tag already exists for a session.
670    fn has_auto_insight(&self, session_id: &str, dedup_tag: &str) -> Result<bool, CodememError>;
671
672    /// Count how many Read events occurred in a directory during a session.
673    fn count_directory_reads(
674        &self,
675        session_id: &str,
676        directory: &str,
677    ) -> Result<usize, CodememError>;
678
679    /// Check if a file was read in the current session.
680    fn was_file_read_in_session(
681        &self,
682        session_id: &str,
683        file_path: &str,
684    ) -> Result<bool, CodememError>;
685
686    /// Count how many times a search pattern was used in a session.
687    fn count_search_pattern_in_session(
688        &self,
689        session_id: &str,
690        pattern: &str,
691    ) -> Result<usize, CodememError>;
692
693    // ── Repository Management ────────────────────────────────────────
694
695    /// List all registered repositories.
696    fn list_repos(&self) -> Result<Vec<Repository>, CodememError>;
697
698    /// Add a new repository.
699    fn add_repo(&self, repo: &Repository) -> Result<(), CodememError>;
700
701    /// Get a repository by ID.
702    fn get_repo(&self, id: &str) -> Result<Option<Repository>, CodememError>;
703
704    /// Remove a repository by ID. Returns true if it existed.
705    fn remove_repo(&self, id: &str) -> Result<bool, CodememError>;
706
707    /// Update a repository's status and optionally its last-indexed timestamp.
708    fn update_repo_status(
709        &self,
710        id: &str,
711        status: &str,
712        indexed_at: Option<&str>,
713    ) -> Result<(), CodememError>;
714
715    // ── Stats ───────────────────────────────────────────────────────
716
717    /// Get database statistics.
718    fn stats(&self) -> Result<StorageStats, CodememError>;
719
720    // ── Transaction Control ────────────────────────────────────────
721
722    /// Begin an explicit transaction.
723    ///
724    /// While a transaction is active, individual storage methods (e.g.
725    /// `insert_memory`, `insert_graph_node`) participate in it instead of
726    /// starting their own. Call `commit_transaction` to persist or
727    /// `rollback_transaction` to discard.
728    ///
729    /// Default implementation is a no-op for backends that don't support
730    /// explicit transaction control.
731    fn begin_transaction(&self) -> Result<(), CodememError> {
732        Ok(())
733    }
734
735    /// Commit the active transaction started by `begin_transaction`.
736    ///
737    /// Default implementation is a no-op.
738    fn commit_transaction(&self) -> Result<(), CodememError> {
739        Ok(())
740    }
741
742    /// Roll back the active transaction started by `begin_transaction`.
743    ///
744    /// Default implementation is a no-op.
745    fn rollback_transaction(&self) -> Result<(), CodememError> {
746        Ok(())
747    }
748
749    // ── Cross-Repo Persistence ────────────────────────────────────────
750
751    /// Get graph edges where either the source or destination node belongs to the
752    /// given namespace (cross-namespace query). When `include_cross_namespace` is false,
753    /// this behaves like `graph_edges_for_namespace` (both endpoints in namespace).
754    fn graph_edges_for_namespace_with_cross(
755        &self,
756        _namespace: &str,
757        _include_cross_namespace: bool,
758    ) -> Result<Vec<Edge>, CodememError> {
759        Ok(Vec::new())
760    }
761
762    /// Upsert a package into the cross-repo package registry.
763    fn upsert_package_registry(
764        &self,
765        _package_name: &str,
766        _namespace: &str,
767        _version: &str,
768        _manifest: &str,
769    ) -> Result<(), CodememError> {
770        Ok(())
771    }
772
773    /// Store an unresolved reference for future cross-namespace linking.
774    #[allow(clippy::too_many_arguments)]
775    fn store_unresolved_ref(
776        &self,
777        _source_qualified_name: &str,
778        _target_name: &str,
779        _source_namespace: &str,
780        _file_path: &str,
781        _line: usize,
782        _ref_kind: &str,
783        _package_hint: Option<&str>,
784    ) -> Result<(), CodememError> {
785        Ok(())
786    }
787
788    /// Batch store unresolved references. Default falls back to per-ref calls.
789    fn store_unresolved_refs_batch(
790        &self,
791        refs: &[UnresolvedRefData],
792    ) -> Result<usize, CodememError> {
793        let mut count = 0;
794        for r in refs {
795            self.store_unresolved_ref(
796                &r.source_qualified_name,
797                &r.target_name,
798                &r.namespace,
799                &r.file_path,
800                r.line,
801                &r.ref_kind,
802                r.package_hint.as_deref(),
803            )?;
804            count += 1;
805        }
806        Ok(count)
807    }
808
809    /// List all registered packages. Returns (name, namespace, manifest_path) tuples.
810    fn list_registered_packages(&self) -> Result<Vec<(String, String, String)>, CodememError> {
811        Ok(Vec::new())
812    }
813
814    /// List all pending unresolved refs with full context.
815    fn list_pending_unresolved_refs(&self) -> Result<Vec<PendingUnresolvedRef>, CodememError> {
816        Ok(Vec::new())
817    }
818
819    /// Delete a resolved unresolved ref by ID.
820    fn delete_unresolved_ref(&self, _id: &str) -> Result<(), CodememError> {
821        Ok(())
822    }
823
824    /// Count unresolved refs for a given namespace.
825    fn count_unresolved_refs(&self, _namespace: &str) -> Result<usize, CodememError> {
826        Ok(0)
827    }
828
829    /// List registered packages for a given namespace.
830    /// Returns (name, namespace, manifest_path) tuples.
831    fn list_registered_packages_for_namespace(
832        &self,
833        _namespace: &str,
834    ) -> Result<Vec<(String, String, String)>, CodememError> {
835        Ok(Vec::new())
836    }
837
838    /// Store a detected API endpoint.
839    fn store_api_endpoint(
840        &self,
841        _method: &str,
842        _path: &str,
843        _handler_symbol: &str,
844        _namespace: &str,
845    ) -> Result<(), CodememError> {
846        Ok(())
847    }
848
849    /// Store a detected client call.
850    fn store_api_client_call(
851        &self,
852        _library: &str,
853        _method: Option<&str>,
854        _caller_symbol: &str,
855        _namespace: &str,
856    ) -> Result<(), CodememError> {
857        Ok(())
858    }
859
860    /// List detected API endpoints for a namespace.
861    /// Returns (method, path, handler_symbol, namespace) tuples.
862    fn list_api_endpoints(
863        &self,
864        _namespace: &str,
865    ) -> Result<Vec<(String, String, String, String)>, CodememError> {
866        Ok(Vec::new())
867    }
868
869    /// Store a detected event channel.
870    fn store_event_channel(
871        &self,
872        channel: &str,
873        direction: &str,
874        protocol: &str,
875        handler: &str,
876        namespace: &str,
877        description: &str,
878    ) -> Result<(), CodememError> {
879        let _ = (
880            channel,
881            direction,
882            protocol,
883            handler,
884            namespace,
885            description,
886        );
887        Ok(())
888    }
889
890    /// List event channels for a namespace.
891    /// Returns (channel, direction, protocol, handler, description) tuples.
892    #[allow(clippy::type_complexity)]
893    fn list_event_channels(
894        &self,
895        namespace: &str,
896    ) -> Result<Vec<(String, String, String, String, String)>, CodememError> {
897        let _ = namespace;
898        Ok(Vec::new())
899    }
900
901    /// List all event channels across all namespaces.
902    /// Returns (channel, direction, protocol, handler, namespace, description) tuples.
903    #[allow(clippy::type_complexity)]
904    fn list_all_event_channels(
905        &self,
906    ) -> Result<Vec<(String, String, String, String, String, String)>, CodememError> {
907        Ok(Vec::new())
908    }
909}