1use std::path::Path;
6
7pub fn cmd_embed(root: &Path) -> crate::Result<()> {
10 let out = scc_engine::invoke(root, "embeddings.build", serde_json::json!({}))
11 .map_err(|e| crate::CliError::Other(e.to_string()))?;
12 println!(
13 "embedding with model '{}' stored {} embeddings",
14 out.get("model").and_then(|m| m.as_str()).unwrap_or(""),
15 out.get("stored").and_then(|n| n.as_u64()).unwrap_or(0),
16 );
17 Ok(())
18}
19
20pub use scc_engine::inference::{EmbeddingScorer, EngineReranker as CliReranker, rankers, remote_inference_allowed};
23
24#[cfg(test)]
25mod tests {
26 use super::*;
27 use scc_context::rank::{Reranker, ScoredEntity, SemanticScorer};
28 use scc_indexer::embed::EmbedConfig;
29 use scc_store::Store;
30
31 fn tmp_store() -> (Store, tempfile::TempDir) {
32 let dir = tempfile::TempDir::new().unwrap();
33 let root = dir.path().join("repo");
34 std::fs::create_dir_all(&root).unwrap();
35 let store = Store::open(&dir.path().join("scc.db"), &root).unwrap();
36 (store, dir)
37 }
38
39 #[test]
40 fn reranker_degrades_without_model() {
41 let cfg = EmbedConfig {
42 base_url: "http://127.0.0.1:1".into(),
43 model: "m".into(),
44 api_key: None,
45 rerank_model: None,
46 };
47 let rr = CliReranker::new(&cfg);
48 let mut cands = vec![ScoredEntity {
49 id: "a".into(),
50 kind: "symbol".into(),
51 name: "x".into(),
52 score: 1.0,
53 reason: "lexical".into(),
54 }];
55 rr.rerank("goal", &mut cands);
56 assert_eq!(cands.len(), 1); }
58
59 #[test]
60 fn remote_policy_fails_closed() {
61 let mut local = scc_indexer::Config::default();
63 local.inference.enabled = true;
64 local.inference.base_url = "http://127.0.0.1:11434/v1".into();
65 assert!(remote_inference_allowed(&local));
66
67 let mut remote = local.clone();
69 remote.inference.base_url = "https://api.openai.com/v1".into();
70 assert!(!remote_inference_allowed(&remote), "remote must fail closed");
71 remote.security.allow_remote_models = true;
72 assert!(remote_inference_allowed(&remote));
73
74 let mut off = remote.clone();
76 off.inference.enabled = false;
77 assert!(!remote_inference_allowed(&off));
78
79 let mut local2 = scc_indexer::Config::default();
81 local2.inference.enabled = true;
82 local2.inference.provider = "local".into();
83 assert!(remote_inference_allowed(&local2));
84 }
85
86 #[test]
87 fn remote_classification_covers_common_hosts() {
88 let mk = |base_url: &str| EmbedConfig {
89 base_url: base_url.into(),
90 model: "m".into(),
91 api_key: None,
92 rerank_model: None,
93 };
94 assert!(!mk("http://127.0.0.1:11434/v1").is_remote());
95 assert!(!mk("http://localhost:11434").is_remote());
96 assert!(!mk("http://[::1]:11434/v1").is_remote());
97 assert!(!mk("http://0.0.0.0:8080").is_remote());
98 assert!(mk("https://api.openai.com/v1").is_remote());
99 assert!(mk("https://gateway.example/v1").is_remote());
100 assert!(mk("http://192.168.1.10:8080").is_remote());
101 }
102
103 #[test]
104fn scorer_uses_stored_embeddings() {
106 let (store, _d) = tmp_store();
107 let mut e = scc_core::Entity::new("repo://r/symbol/a.py/boosted", "symbol", "boosted");
108 e.attr("file", serde_json::json!("a.py"));
109 store.insert_entity(&e, &["a.py".into()]).unwrap();
110 let mut v = vec![0.0f32; 8];
112 v[0] = 1.0;
113 store.put_embedding(&e.id, &v, "test").unwrap();
114 let _cfg = EmbedConfig {
115 base_url: "http://127.0.0.1:1".into(),
116 model: "test".into(),
117 api_key: None,
118 rerank_model: None,
119 };
120 let mut m = std::collections::HashMap::new();
123 m.insert(e.id.clone(), v);
124 let scorer = EmbeddingScorer::from_vectors(
125 vec![1.0f32, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
126 m,
127 );
128assert!((scorer.score("goal", &e) - 1.0).abs() < 1e-6);
129 let other = scc_core::Entity::new("repo://r/symbol/a.py/z", "symbol", "z");
131 assert_eq!(scorer.score("goal", &other), 0.0);
132 }
133}