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