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}