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}