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, RelationshipType, Repository, Session,
6    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
58/// Statistics about the vector index.
59#[derive(Debug, Clone, Default, Serialize, Deserialize)]
60pub struct VectorStats {
61    pub count: usize,
62    pub dimensions: usize,
63    pub metric: String,
64    pub memory_bytes: usize,
65}
66
67/// Graph backend trait for graph operations.
68pub trait GraphBackend: Send + Sync {
69    /// Add a node to the graph.
70    fn add_node(&mut self, node: GraphNode) -> Result<(), CodememError>;
71
72    /// Get a node by ID.
73    fn get_node(&self, id: &str) -> Result<Option<GraphNode>, CodememError>;
74
75    /// Remove a node by ID.
76    fn remove_node(&mut self, id: &str) -> Result<bool, CodememError>;
77
78    /// Add an edge between two nodes.
79    fn add_edge(&mut self, edge: Edge) -> Result<(), CodememError>;
80
81    /// Get edges from a node.
82    fn get_edges(&self, node_id: &str) -> Result<Vec<Edge>, CodememError>;
83
84    /// Remove an edge by ID.
85    fn remove_edge(&mut self, id: &str) -> Result<bool, CodememError>;
86
87    /// BFS traversal from a start node up to max_depth.
88    fn bfs(&self, start_id: &str, max_depth: usize) -> Result<Vec<GraphNode>, CodememError>;
89
90    /// DFS traversal from a start node up to max_depth.
91    fn dfs(&self, start_id: &str, max_depth: usize) -> Result<Vec<GraphNode>, CodememError>;
92
93    /// BFS traversal with filtering: exclude certain node kinds and optionally
94    /// restrict to specific relationship types.
95    fn bfs_filtered(
96        &self,
97        start_id: &str,
98        max_depth: usize,
99        exclude_kinds: &[NodeKind],
100        include_relationships: Option<&[RelationshipType]>,
101    ) -> Result<Vec<GraphNode>, CodememError>;
102
103    /// DFS traversal with filtering: exclude certain node kinds and optionally
104    /// restrict to specific relationship types.
105    fn dfs_filtered(
106        &self,
107        start_id: &str,
108        max_depth: usize,
109        exclude_kinds: &[NodeKind],
110        include_relationships: Option<&[RelationshipType]>,
111    ) -> Result<Vec<GraphNode>, CodememError>;
112
113    /// Shortest path between two nodes.
114    fn shortest_path(&self, from: &str, to: &str) -> Result<Vec<String>, CodememError>;
115
116    /// Get graph statistics.
117    fn stats(&self) -> GraphStats;
118}
119
120/// Statistics about the graph.
121#[derive(Debug, Clone, Default, Serialize, Deserialize)]
122pub struct GraphStats {
123    pub node_count: usize,
124    pub edge_count: usize,
125    pub node_kind_counts: HashMap<String, usize>,
126    pub relationship_type_counts: HashMap<String, usize>,
127}
128
129// ── Embedding Provider Trait ────────────────────────────────────────────────
130
131/// Trait for pluggable embedding providers.
132pub trait EmbeddingProvider: Send + Sync {
133    /// Embedding vector dimensions.
134    fn dimensions(&self) -> usize;
135
136    /// Embed a single text string.
137    fn embed(&self, text: &str) -> Result<Vec<f32>, crate::CodememError>;
138
139    /// Embed a batch of texts (default: sequential).
140    fn embed_batch(&self, texts: &[&str]) -> Result<Vec<Vec<f32>>, crate::CodememError> {
141        texts.iter().map(|t| self.embed(t)).collect()
142    }
143
144    /// Provider name for display.
145    fn name(&self) -> &str;
146
147    /// Cache statistics: (current_size, capacity). Returns (0, 0) if no cache.
148    fn cache_stats(&self) -> (usize, usize) {
149        (0, 0)
150    }
151}
152
153// ── Storage Stats & Consolidation Types ─────────────────────────────────
154
155/// Database statistics.
156#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct StorageStats {
158    pub memory_count: usize,
159    pub embedding_count: usize,
160    pub node_count: usize,
161    pub edge_count: usize,
162}
163
164/// A single consolidation log entry.
165#[derive(Debug, Clone)]
166pub struct ConsolidationLogEntry {
167    pub cycle_type: String,
168    pub run_at: i64,
169    pub affected_count: usize,
170}
171
172// ── Storage Backend Trait ───────────────────────────────────────────────
173
174/// Pluggable storage backend trait for all persistence operations.
175///
176/// This trait unifies every persistence concern behind a single interface so
177/// that the engine layer (`CodememEngine`) remains backend-agnostic.
178///
179/// # Method groups
180///
181/// | Group | Methods | Purpose |
182/// |-------|---------|---------|
183/// | **Memory CRUD** | `insert_memory`, `get_memory`, `update_memory`, `delete_memory`, `list_memory_ids`, … | Create, read, update, delete memory nodes |
184/// | **Embedding persistence** | `store_embedding`, `get_embedding`, `delete_embedding`, `list_all_embeddings` | Persist and retrieve embedding vectors |
185/// | **Graph node/edge storage** | `insert_graph_node`, `get_graph_node`, `all_graph_nodes`, `insert_graph_edge`, … | Persist the knowledge graph structure |
186/// | **Sessions** | `start_session`, `end_session`, `list_sessions`, `session_count` | Track interaction sessions |
187/// | **Consolidation** | `insert_consolidation_log`, `last_consolidation_runs` | Record and query memory consolidation runs |
188/// | **Pattern detection** | `get_repeated_searches`, `get_file_hotspots`, `get_tool_usage_stats`, `get_decision_chains` | Cross-session pattern queries |
189/// | **Bulk/batch operations** | `insert_memories_batch`, `store_embeddings_batch`, `insert_graph_nodes_batch`, `insert_graph_edges_batch` | Efficient multi-row inserts |
190/// | **Decay & forgetting** | `decay_stale_memories`, `find_forgettable`, `get_stale_memories_for_decay`, `batch_update_importance` | Power-law decay and garbage collection |
191/// | **Query helpers** | `find_unembedded_memories`, `search_graph_nodes`, `list_memories_filtered`, `find_hash_duplicates` | Filtered searches and dedup |
192/// | **File hash tracking** | `load_file_hashes`, `save_file_hashes` | Incremental indexing support |
193/// | **Session activity** | `record_session_activity`, `get_session_activity_summary`, `get_session_hot_directories`, … | Fine-grained activity tracking |
194/// | **Stats** | `stats` | Database-level statistics |
195///
196/// Implementations include SQLite (default) and can be extended for
197/// SurrealDB, FalkorDB, or other backends.
198pub trait StorageBackend: Send + Sync {
199    // ── Memory CRUD ─────────────────────────────────────────────────
200
201    /// Insert a new memory. Returns Err(Duplicate) if content hash already exists.
202    fn insert_memory(&self, memory: &MemoryNode) -> Result<(), CodememError>;
203
204    /// Get a memory by ID. Updates access_count and last_accessed_at.
205    fn get_memory(&self, id: &str) -> Result<Option<MemoryNode>, CodememError>;
206
207    /// Get a memory by ID without updating access_count or last_accessed_at.
208    /// Use this for internal/system reads (consolidation checks, stats, batch processing).
209    fn get_memory_no_touch(&self, id: &str) -> Result<Option<MemoryNode>, CodememError> {
210        // Default: falls back to get_memory for backwards compatibility.
211        self.get_memory(id)
212    }
213
214    /// Get multiple memories by IDs in a single batch operation.
215    fn get_memories_batch(&self, ids: &[&str]) -> Result<Vec<MemoryNode>, CodememError>;
216
217    /// Update a memory's content and optionally its importance. Re-computes content hash.
218    fn update_memory(
219        &self,
220        id: &str,
221        content: &str,
222        importance: Option<f64>,
223    ) -> Result<(), CodememError>;
224
225    /// Delete a memory by ID. Returns true if a row was deleted.
226    fn delete_memory(&self, id: &str) -> Result<bool, CodememError>;
227
228    /// Delete a memory and all related data (graph nodes/edges, embeddings) atomically.
229    /// Returns true if the memory existed and was deleted.
230    /// Default falls back to individual deletes (non-transactional) for backwards compatibility.
231    fn delete_memory_cascade(&self, id: &str) -> Result<bool, CodememError> {
232        let deleted = self.delete_memory(id)?;
233        if deleted {
234            let _ = self.delete_graph_edges_for_node(id);
235            let _ = self.delete_graph_node(id);
236            let _ = self.delete_embedding(id);
237        }
238        Ok(deleted)
239    }
240
241    /// Delete multiple memories and all related data (graph nodes/edges, embeddings) atomically.
242    /// Returns the number of memories that were actually deleted.
243    /// Default falls back to calling `delete_memory_cascade` per ID for backwards compatibility.
244    fn delete_memories_batch_cascade(&self, ids: &[&str]) -> Result<usize, CodememError> {
245        let mut count = 0;
246        for id in ids {
247            if self.delete_memory_cascade(id)? {
248                count += 1;
249            }
250        }
251        Ok(count)
252    }
253
254    /// List all memory IDs, ordered by created_at descending.
255    fn list_memory_ids(&self) -> Result<Vec<String>, CodememError>;
256
257    /// List memory IDs scoped to a specific namespace.
258    fn list_memory_ids_for_namespace(&self, namespace: &str) -> Result<Vec<String>, CodememError>;
259
260    /// Find memory IDs whose tags contain the given tag value.
261    /// Optionally scoped to a namespace. Excludes `exclude_id`.
262    fn find_memory_ids_by_tag(
263        &self,
264        tag: &str,
265        namespace: Option<&str>,
266        exclude_id: &str,
267    ) -> Result<Vec<String>, CodememError>;
268
269    /// List all distinct namespaces.
270    fn list_namespaces(&self) -> Result<Vec<String>, CodememError>;
271
272    /// Get total memory count.
273    fn memory_count(&self) -> Result<usize, CodememError>;
274
275    // ── Embedding Persistence ───────────────────────────────────────
276
277    /// Store an embedding vector for a memory.
278    fn store_embedding(&self, memory_id: &str, embedding: &[f32]) -> Result<(), CodememError>;
279
280    /// Get an embedding by memory ID.
281    fn get_embedding(&self, memory_id: &str) -> Result<Option<Vec<f32>>, CodememError>;
282
283    /// Delete an embedding by memory ID. Returns true if a row was deleted.
284    fn delete_embedding(&self, memory_id: &str) -> Result<bool, CodememError>;
285
286    /// List all stored embeddings as (memory_id, embedding_vector) pairs.
287    fn list_all_embeddings(&self) -> Result<Vec<(String, Vec<f32>)>, CodememError>;
288
289    // ── Graph Node/Edge Persistence ─────────────────────────────────
290
291    /// Insert or replace a graph node.
292    fn insert_graph_node(&self, node: &GraphNode) -> Result<(), CodememError>;
293
294    /// Get a graph node by ID.
295    fn get_graph_node(&self, id: &str) -> Result<Option<GraphNode>, CodememError>;
296
297    /// Delete a graph node by ID. Returns true if a row was deleted.
298    fn delete_graph_node(&self, id: &str) -> Result<bool, CodememError>;
299
300    /// Get all graph nodes.
301    fn all_graph_nodes(&self) -> Result<Vec<GraphNode>, CodememError>;
302
303    /// Insert or replace a graph edge.
304    fn insert_graph_edge(&self, edge: &Edge) -> Result<(), CodememError>;
305
306    /// Get all edges from or to a node.
307    fn get_edges_for_node(&self, node_id: &str) -> Result<Vec<Edge>, CodememError>;
308
309    /// Get all graph edges.
310    fn all_graph_edges(&self) -> Result<Vec<Edge>, CodememError>;
311
312    /// Delete a single graph edge by ID. Returns true if a row was deleted.
313    fn delete_graph_edge(&self, edge_id: &str) -> Result<bool, CodememError> {
314        // Default: fall back to querying all edges and deleting via for_node.
315        // Backends should override with a direct DELETE WHERE id = ?1.
316        let _ = edge_id;
317        Ok(false)
318    }
319
320    /// Delete all graph edges connected to a node. Returns count deleted.
321    fn delete_graph_edges_for_node(&self, node_id: &str) -> Result<usize, CodememError>;
322
323    /// Delete all graph nodes, edges, and embeddings whose node ID starts with the given prefix.
324    /// Returns count of nodes deleted.
325    fn delete_graph_nodes_by_prefix(&self, prefix: &str) -> Result<usize, CodememError>;
326
327    // ── Sessions ────────────────────────────────────────────────────
328
329    /// Start a new session.
330    fn start_session(&self, id: &str, namespace: Option<&str>) -> Result<(), CodememError>;
331
332    /// End a session with optional summary.
333    fn end_session(&self, id: &str, summary: Option<&str>) -> Result<(), CodememError>;
334
335    /// List sessions, optionally filtered by namespace, up to limit.
336    fn list_sessions(
337        &self,
338        namespace: Option<&str>,
339        limit: usize,
340    ) -> Result<Vec<Session>, CodememError>;
341
342    // ── Consolidation ───────────────────────────────────────────────
343
344    /// Record a consolidation run.
345    fn insert_consolidation_log(
346        &self,
347        cycle_type: &str,
348        affected_count: usize,
349    ) -> Result<(), CodememError>;
350
351    /// Get the last consolidation run for each cycle type.
352    fn last_consolidation_runs(&self) -> Result<Vec<ConsolidationLogEntry>, CodememError>;
353
354    // ── Pattern Detection Queries ───────────────────────────────────
355
356    /// Find repeated search patterns. Returns (pattern, count, memory_ids).
357    fn get_repeated_searches(
358        &self,
359        min_count: usize,
360        namespace: Option<&str>,
361    ) -> Result<Vec<(String, usize, Vec<String>)>, CodememError>;
362
363    /// Find file hotspots. Returns (file_path, count, memory_ids).
364    fn get_file_hotspots(
365        &self,
366        min_count: usize,
367        namespace: Option<&str>,
368    ) -> Result<Vec<(String, usize, Vec<String>)>, CodememError>;
369
370    /// Get tool usage statistics. Returns (tool_name, count) pairs.
371    fn get_tool_usage_stats(
372        &self,
373        namespace: Option<&str>,
374    ) -> Result<Vec<(String, usize)>, CodememError>;
375
376    /// Find decision chains. Returns (file_path, count, memory_ids).
377    fn get_decision_chains(
378        &self,
379        min_count: usize,
380        namespace: Option<&str>,
381    ) -> Result<Vec<(String, usize, Vec<String>)>, CodememError>;
382
383    // ── Bulk Operations ─────────────────────────────────────────────
384
385    /// Decay importance of stale memories older than threshold_ts by decay_factor.
386    /// Returns count of affected memories.
387    fn decay_stale_memories(
388        &self,
389        threshold_ts: i64,
390        decay_factor: f64,
391    ) -> Result<usize, CodememError>;
392
393    /// List memories for creative consolidation: (id, memory_type, tags).
394    fn list_memories_for_creative(
395        &self,
396    ) -> Result<Vec<(String, String, Vec<String>)>, CodememError>;
397
398    /// Find near-duplicate memories by content hash prefix matching.
399    /// Returns (id1, id2, similarity) pairs. Only catches exact content matches
400    /// (hash prefix), not semantic near-duplicates.
401    fn find_hash_duplicates(&self) -> Result<Vec<(String, String, f64)>, CodememError>;
402
403    /// Find memories eligible for forgetting (low importance).
404    /// Returns list of memory IDs.
405    fn find_forgettable(&self, importance_threshold: f64) -> Result<Vec<String>, CodememError>;
406
407    // ── Batch Operations ────────────────────────────────────────────
408
409    /// Insert multiple memories in a single batch. Default impl calls insert_memory in a loop.
410    fn insert_memories_batch(&self, memories: &[MemoryNode]) -> Result<(), CodememError> {
411        for memory in memories {
412            self.insert_memory(memory)?;
413        }
414        Ok(())
415    }
416
417    /// Store multiple embeddings in a single batch. Default impl calls store_embedding in a loop.
418    fn store_embeddings_batch(&self, items: &[(&str, &[f32])]) -> Result<(), CodememError> {
419        for (id, embedding) in items {
420            self.store_embedding(id, embedding)?;
421        }
422        Ok(())
423    }
424
425    /// Insert multiple graph nodes in a single batch. Default impl calls insert_graph_node in a loop.
426    fn insert_graph_nodes_batch(&self, nodes: &[GraphNode]) -> Result<(), CodememError> {
427        for node in nodes {
428            self.insert_graph_node(node)?;
429        }
430        Ok(())
431    }
432
433    /// Insert multiple graph edges in a single batch. Default impl calls insert_graph_edge in a loop.
434    fn insert_graph_edges_batch(&self, edges: &[Edge]) -> Result<(), CodememError> {
435        for edge in edges {
436            self.insert_graph_edge(edge)?;
437        }
438        Ok(())
439    }
440
441    // ── Query Helpers ───────────────────────────────────────────────
442
443    /// Find memories that have no embeddings yet. Returns (id, content) pairs.
444    fn find_unembedded_memories(&self) -> Result<Vec<(String, String)>, CodememError>;
445
446    /// Search graph nodes by label (case-insensitive LIKE). Returns matching nodes
447    /// sorted by centrality descending, limited to `limit` results.
448    fn search_graph_nodes(
449        &self,
450        query: &str,
451        namespace: Option<&str>,
452        limit: usize,
453    ) -> Result<Vec<GraphNode>, CodememError>;
454
455    /// List memories matching a specific tag, with optional namespace filter.
456    fn list_memories_by_tag(
457        &self,
458        tag: &str,
459        namespace: Option<&str>,
460        limit: usize,
461    ) -> Result<Vec<MemoryNode>, CodememError>;
462
463    /// List memories with optional namespace and memory_type filters.
464    fn list_memories_filtered(
465        &self,
466        namespace: Option<&str>,
467        memory_type: Option<&str>,
468    ) -> Result<Vec<MemoryNode>, CodememError>;
469
470    /// Fetch stale memories with access metadata for power-law decay.
471    /// Returns (id, importance, access_count, last_accessed_at).
472    fn get_stale_memories_for_decay(
473        &self,
474        threshold_ts: i64,
475    ) -> Result<Vec<(String, f64, u32, i64)>, CodememError>;
476
477    /// Batch-update importance values. Returns count of updated rows.
478    fn batch_update_importance(&self, updates: &[(String, f64)]) -> Result<usize, CodememError>;
479
480    /// Total session count, optionally filtered by namespace.
481    fn session_count(&self, namespace: Option<&str>) -> Result<usize, CodememError>;
482
483    // ── File Hash Tracking ──────────────────────────────────────────
484
485    /// Load file hashes for incremental indexing, scoped to a namespace.
486    /// Returns path -> hash map.
487    fn load_file_hashes(&self, namespace: &str) -> Result<HashMap<String, String>, CodememError>;
488
489    /// Save file hashes for incremental indexing, scoped to a namespace.
490    fn save_file_hashes(
491        &self,
492        namespace: &str,
493        hashes: &HashMap<String, String>,
494    ) -> Result<(), CodememError>;
495
496    // ── Session Activity Tracking ─────────────────────────────────
497
498    /// Record a session activity event (tool use with context).
499    fn record_session_activity(
500        &self,
501        session_id: &str,
502        tool_name: &str,
503        file_path: Option<&str>,
504        directory: Option<&str>,
505        pattern: Option<&str>,
506    ) -> Result<(), CodememError>;
507
508    /// Get a summary of session activity counts.
509    fn get_session_activity_summary(
510        &self,
511        session_id: &str,
512    ) -> Result<crate::SessionActivitySummary, CodememError>;
513
514    /// Get the most active directories in a session. Returns (directory, count) pairs.
515    fn get_session_hot_directories(
516        &self,
517        session_id: &str,
518        limit: usize,
519    ) -> Result<Vec<(String, usize)>, CodememError>;
520
521    /// Check whether a particular auto-insight dedup tag already exists for a session.
522    fn has_auto_insight(&self, session_id: &str, dedup_tag: &str) -> Result<bool, CodememError>;
523
524    /// Count how many Read events occurred in a directory during a session.
525    fn count_directory_reads(
526        &self,
527        session_id: &str,
528        directory: &str,
529    ) -> Result<usize, CodememError>;
530
531    /// Check if a file was read in the current session.
532    fn was_file_read_in_session(
533        &self,
534        session_id: &str,
535        file_path: &str,
536    ) -> Result<bool, CodememError>;
537
538    /// Count how many times a search pattern was used in a session.
539    fn count_search_pattern_in_session(
540        &self,
541        session_id: &str,
542        pattern: &str,
543    ) -> Result<usize, CodememError>;
544
545    // ── Repository Management ────────────────────────────────────────
546
547    /// List all registered repositories.
548    fn list_repos(&self) -> Result<Vec<Repository>, CodememError>;
549
550    /// Add a new repository.
551    fn add_repo(&self, repo: &Repository) -> Result<(), CodememError>;
552
553    /// Get a repository by ID.
554    fn get_repo(&self, id: &str) -> Result<Option<Repository>, CodememError>;
555
556    /// Remove a repository by ID. Returns true if it existed.
557    fn remove_repo(&self, id: &str) -> Result<bool, CodememError>;
558
559    /// Update a repository's status and optionally its last-indexed timestamp.
560    fn update_repo_status(
561        &self,
562        id: &str,
563        status: &str,
564        indexed_at: Option<&str>,
565    ) -> Result<(), CodememError>;
566
567    // ── Stats ───────────────────────────────────────────────────────
568
569    /// Get database statistics.
570    fn stats(&self) -> Result<StorageStats, CodememError>;
571
572    // ── Transaction Control ────────────────────────────────────────
573
574    /// Begin an explicit transaction.
575    ///
576    /// While a transaction is active, individual storage methods (e.g.
577    /// `insert_memory`, `insert_graph_node`) participate in it instead of
578    /// starting their own. Call `commit_transaction` to persist or
579    /// `rollback_transaction` to discard.
580    ///
581    /// Default implementation is a no-op for backends that don't support
582    /// explicit transaction control.
583    fn begin_transaction(&self) -> Result<(), CodememError> {
584        Ok(())
585    }
586
587    /// Commit the active transaction started by `begin_transaction`.
588    ///
589    /// Default implementation is a no-op.
590    fn commit_transaction(&self) -> Result<(), CodememError> {
591        Ok(())
592    }
593
594    /// Roll back the active transaction started by `begin_transaction`.
595    ///
596    /// Default implementation is a no-op.
597    fn rollback_transaction(&self) -> Result<(), CodememError> {
598        Ok(())
599    }
600
601    // ── Cross-Repo Persistence ────────────────────────────────────────
602
603    /// Get graph edges where either the source or destination node belongs to the
604    /// given namespace (cross-namespace query). When `include_cross_namespace` is false,
605    /// this behaves like `graph_edges_for_namespace` (both endpoints in namespace).
606    fn graph_edges_for_namespace_with_cross(
607        &self,
608        _namespace: &str,
609        _include_cross_namespace: bool,
610    ) -> Result<Vec<Edge>, CodememError> {
611        Ok(Vec::new())
612    }
613
614    /// Upsert a package into the cross-repo package registry.
615    fn upsert_package_registry(
616        &self,
617        _package_name: &str,
618        _namespace: &str,
619        _version: &str,
620        _manifest: &str,
621    ) -> Result<(), CodememError> {
622        Ok(())
623    }
624
625    /// Store an unresolved reference for future cross-namespace linking.
626    #[allow(clippy::too_many_arguments)]
627    fn store_unresolved_ref(
628        &self,
629        _source_qualified_name: &str,
630        _target_name: &str,
631        _source_namespace: &str,
632        _file_path: &str,
633        _line: usize,
634        _ref_kind: &str,
635        _package_hint: Option<&str>,
636    ) -> Result<(), CodememError> {
637        Ok(())
638    }
639
640    /// Batch store unresolved references. Default falls back to per-ref calls.
641    fn store_unresolved_refs_batch(
642        &self,
643        refs: &[UnresolvedRefData],
644    ) -> Result<usize, CodememError> {
645        let mut count = 0;
646        for r in refs {
647            self.store_unresolved_ref(
648                &r.source_qualified_name,
649                &r.target_name,
650                &r.namespace,
651                &r.file_path,
652                r.line,
653                &r.ref_kind,
654                r.package_hint.as_deref(),
655            )?;
656            count += 1;
657        }
658        Ok(count)
659    }
660
661    /// List all registered packages. Returns (name, namespace, manifest_path) tuples.
662    fn list_registered_packages(&self) -> Result<Vec<(String, String, String)>, CodememError> {
663        Ok(Vec::new())
664    }
665
666    /// List all pending unresolved refs with full context.
667    fn list_pending_unresolved_refs(&self) -> Result<Vec<PendingUnresolvedRef>, CodememError> {
668        Ok(Vec::new())
669    }
670
671    /// Delete a resolved unresolved ref by ID.
672    fn delete_unresolved_ref(&self, _id: &str) -> Result<(), CodememError> {
673        Ok(())
674    }
675
676    /// Count unresolved refs for a given namespace.
677    fn count_unresolved_refs(&self, _namespace: &str) -> Result<usize, CodememError> {
678        Ok(0)
679    }
680
681    /// List registered packages for a given namespace.
682    /// Returns (name, namespace, manifest_path) tuples.
683    fn list_registered_packages_for_namespace(
684        &self,
685        _namespace: &str,
686    ) -> Result<Vec<(String, String, String)>, CodememError> {
687        Ok(Vec::new())
688    }
689
690    /// Store a detected API endpoint.
691    fn store_api_endpoint(
692        &self,
693        _method: &str,
694        _path: &str,
695        _handler_symbol: &str,
696        _namespace: &str,
697    ) -> Result<(), CodememError> {
698        Ok(())
699    }
700
701    /// Store a detected client call.
702    fn store_api_client_call(
703        &self,
704        _library: &str,
705        _method: Option<&str>,
706        _caller_symbol: &str,
707        _namespace: &str,
708    ) -> Result<(), CodememError> {
709        Ok(())
710    }
711
712    /// List detected API endpoints for a namespace.
713    /// Returns (method, path, handler_symbol, namespace) tuples.
714    fn list_api_endpoints(
715        &self,
716        _namespace: &str,
717    ) -> Result<Vec<(String, String, String, String)>, CodememError> {
718        Ok(Vec::new())
719    }
720}