1pub mod commands;
2pub mod config;
3pub mod db;
4pub mod falkor;
5pub mod freshness;
6pub mod git;
7pub mod graph;
8pub mod index;
9pub mod models;
10pub mod output;
11pub mod progress;
12pub mod project;
13pub mod projection;
14pub mod savings;
15pub mod schema;
16pub mod search;
17pub mod secrets;
18pub mod setup;
19pub mod skill;
20pub mod utils;
21pub mod vector;
22
23pub use index::api::{IndexDegradation, IndexDurations, IndexOutcome, IndexRequest, index_files};
24
25#[cfg(test)]
26mod tests {
27 use serde::Serialize;
28 use serde::de::DeserializeOwned;
29
30 fn assert_cli_independent_contract<T>()
31 where
32 T: Serialize + DeserializeOwned,
33 {
34 let type_name = std::any::type_name::<T>();
35 assert!(!type_name.contains("commands::"), "{type_name}");
36 assert!(!type_name.contains("output::"), "{type_name}");
37 assert!(!type_name.contains("clap"), "{type_name}");
38 }
39
40 #[test]
41 fn public_projection_api_is_cli_independent() {
42 let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
43 for rel_path in [
44 "src/index/api.rs",
45 "src/graph/typed_query.rs",
46 "src/graph/code_graph.rs",
47 "src/vector/code_symbols.rs",
48 "src/projection/sync.rs",
49 ] {
50 assert!(
51 manifest_dir.join(rel_path).exists(),
52 "missing projection boundary module {rel_path}"
53 );
54 }
55
56 assert_cli_independent_contract::<crate::index::api::CodeFactWriteRequest>();
57 assert_cli_independent_contract::<crate::index::api::CodeFactWriteSummary>();
58 assert_cli_independent_contract::<crate::index::api::IndexRequest>();
59 assert_cli_independent_contract::<crate::index::api::IndexOutcome>();
60 assert_cli_independent_contract::<crate::index::api::IndexDurations>();
61 assert_cli_independent_contract::<crate::index::api::IndexDegradation>();
62 assert_cli_independent_contract::<crate::graph::code_graph::GraphLifecycleRequest>();
63 assert_cli_independent_contract::<crate::graph::code_graph::GraphLifecycleOutput>();
64 assert_cli_independent_contract::<crate::graph::code_graph::GraphReadRequest>();
65 assert_cli_independent_contract::<crate::vector::code_symbols::CodeSymbolVectorSearchRequest>(
66 );
67 assert_cli_independent_contract::<crate::vector::code_symbols::CodeSymbolVectorSearchHit>();
68 assert_cli_independent_contract::<crate::vector::code_symbols::CodeSymbolVectorPayload>();
69 assert_cli_independent_contract::<crate::projection::sync::ProjectionSyncRequest>();
70 assert_cli_independent_contract::<crate::projection::sync::ProjectionSyncStatus>();
71 }
72
73 #[test]
74 fn falkor_facade_is_available() {
75 let _ = std::any::type_name::<crate::falkor::FalkorClient>();
76
77 let ctx = crate::config::Context {
78 database_url: "postgresql://localhost/nonexistent".to_string(),
79 project_root: std::path::PathBuf::from("/nonexistent"),
80 project_id: "project-1".to_string(),
81 quiet: true,
82 falkordb: None,
83 qdrant: None,
84 embedding: None,
85 code_vectors: crate::config::CodeVectorSettings::default(),
86 daemon_url: None,
87 };
88
89 let value = crate::falkor::with_falkor(&ctx, 7usize, |_| Ok(9usize))
90 .expect("missing FalkorDB config degrades to default");
91 assert_eq!(value, 7);
92 }
93
94 #[test]
95 fn foundation_consumer_migration() {
96 let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
97 let cargo = std::fs::read_to_string(manifest_dir.join("Cargo.toml"))
98 .expect("read gobby-code Cargo.toml");
99 for feature in ["postgres", "falkor", "qdrant", "search", "indexing"] {
100 assert!(
101 cargo.contains(feature),
102 "gobby-code must enable gobby-core feature `{feature}`"
103 );
104 }
105
106 let config =
107 std::fs::read_to_string(manifest_dir.join("src/config.rs")).expect("read config.rs");
108 assert!(config.contains("gobby_core::config::resolve_falkordb_config"));
109 assert!(config.contains("gobby_core::config::resolve_qdrant_config"));
110 assert!(config.contains("gobby_core::config::resolve_embedding_config"));
111 assert!(config.contains("impl gobby_core::config::ConfigSource for PostgresConfigSource"));
112 assert!(config.contains("gobby_core::postgres::read_config_value"));
113 assert!(!config.contains("fn decode_config_value("));
114
115 let db = std::fs::read_to_string(manifest_dir.join("src/db.rs")).expect("read db.rs");
116 assert!(db.contains("gobby_core::postgres::connect_readonly"));
117 assert!(db.contains("gobby_core::postgres::connect_readwrite"));
118 assert!(!db.contains("Client::connect(database_url, NoTls)"));
119
120 let graph = std::fs::read_to_string(manifest_dir.join("src/graph/code_graph.rs"))
121 .expect("read graph/code_graph.rs");
122 assert!(graph.contains("gobby_core::falkor::with_graph"));
123 assert!(!graph.contains("falkor::with_falkor"));
124
125 let falkor =
126 std::fs::read_to_string(manifest_dir.join("src/falkor.rs")).expect("read falkor.rs");
127 assert!(falkor.contains("gobby_core::falkor::GraphClient"));
128 for token in [
129 ["Falkor", "Client", "Builder"].concat(),
130 ["Falkor", "Connection", "Info"].concat(),
131 ["Sync", "Graph"].concat(),
132 ["Falkor", "Value"].concat(),
133 ["Lazy", "Result", "Set"].concat(),
134 ["Query", "Result"].concat(),
135 ] {
136 assert!(!falkor.contains(&token), "{token} leaked into falkor.rs");
137 }
138
139 let semantic = std::fs::read_to_string(manifest_dir.join("src/search/semantic.rs"))
140 .expect("read search/semantic.rs");
141 assert!(semantic.contains("gobby_core::qdrant::with_qdrant"));
142 assert!(semantic.contains("gobby_core::qdrant::collection_name"));
143 assert!(semantic.contains("gobby_core::qdrant::search"));
144 }
145
146 #[test]
147 fn indexing_search_primitive_migration() {
148 let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
149
150 let walker = std::fs::read_to_string(manifest_dir.join("src/index/walker.rs"))
151 .expect("read index/walker.rs");
152 assert!(walker.contains("gobby_core::indexing::WalkerSettings"));
153 let local_walker_builder = ["WalkBuilder", "::new(root)"].concat();
154 assert!(!walker.contains(&local_walker_builder));
155
156 let hasher = std::fs::read_to_string(manifest_dir.join("src/index/hasher.rs"))
157 .expect("read index/hasher.rs");
158 assert!(hasher.contains("gobby_core::indexing::file_content_hash"));
159 let local_buffer = format!("let mut buf = [0u8; {}]", 64 * 1024);
160 assert!(!hasher.contains(&local_buffer));
161
162 let rrf =
163 std::fs::read_to_string(manifest_dir.join("src/search/rrf.rs")).expect("read rrf.rs");
164 assert!(rrf.contains("gobby_core::search::rrf_merge"));
165 let local_rrf_const = ["const ", "RRF_K"].concat();
166 assert!(!rrf.contains(&local_rrf_const));
167
168 let chunker = std::fs::read_to_string(manifest_dir.join("src/index/chunker.rs"))
169 .expect("read index/chunker.rs");
170 assert!(!chunker.contains("use gobby_core::indexing::Chunk"));
171 assert!(!chunker.contains("use gobby_core::indexing::ChunkIdentity"));
172 assert!(!chunker.contains("use gobby_core::indexing::IndexEvent"));
173 assert!(!chunker.contains("use gobby_core::indexing::index_events_from_hashes"));
174 }
175
176 #[test]
177 fn falkor_facade_uses_core_graph_client_only() {
178 let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
179 let src_dir = manifest_dir.join("src");
180 let mut offenders = Vec::new();
181
182 fn visit(path: &std::path::Path, offenders: &mut Vec<std::path::PathBuf>) {
183 for entry in std::fs::read_dir(path).expect("read source directory") {
184 let entry = entry.expect("source entry");
185 let path = entry.path();
186 if path.is_dir() {
187 visit(&path, offenders);
188 continue;
189 }
190 if path.extension().and_then(|ext| ext.to_str()) != Some("rs") {
191 continue;
192 }
193 let source = std::fs::read_to_string(&path).expect("read source file");
194 let builder = ["Falkor", "Client", "Builder"].concat();
195 if source.contains(&builder) {
196 offenders.push(path);
197 }
198 }
199 }
200
201 visit(&src_dir, &mut offenders);
202 assert!(
203 offenders.is_empty(),
204 "gobby-code must use gobby_core::falkor::GraphClient instead of direct FalkorDB builders: {offenders:?}"
205 );
206 }
207}