use async_trait::async_trait;
use serde::{Deserialize, Serialize};
pub mod github;
pub mod local;
pub mod multi;
pub mod scoring;
pub use github::GitHubSource;
pub use local::LocalCloneSource;
pub use multi::MultiSource;
pub use scoring::score_with_llm;
pub(crate) fn language_matches(languages: &[String], path: &str) -> bool {
if languages.is_empty() {
return true;
}
let ext = std::path::Path::new(path)
.extension()
.and_then(|s| s.to_str())
.unwrap_or("")
.to_ascii_lowercase();
for lang in languages {
let l = lang.to_ascii_lowercase();
let ok = match l.as_str() {
"rust" => ext == "rs",
"typescript" => matches!(ext.as_str(), "ts" | "tsx"),
"javascript" => matches!(ext.as_str(), "js" | "jsx" | "mjs" | "cjs"),
"python" => ext == "py",
"go" => ext == "go",
"c" => matches!(ext.as_str(), "c" | "h"),
"cpp" | "c++" => matches!(ext.as_str(), "cpp" | "cc" | "cxx" | "hpp" | "hh" | "hxx"),
"java" => ext == "java",
"ruby" => ext == "rb",
"swift" => ext == "swift",
"kotlin" => matches!(ext.as_str(), "kt" | "kts"),
other => ext == other,
};
if ok {
return true;
}
}
false
}
pub(crate) fn license_matches(allowlist: &[String], license: Option<&str>) -> bool {
if allowlist.is_empty() {
return true;
}
match license {
None => false,
Some(id) => allowlist.iter().any(|a| a.eq_ignore_ascii_case(id)),
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CodeReference {
pub repo: String,
pub commit: String,
pub path: String,
pub snippet: String,
pub score: f32,
pub license: Option<String>,
pub why_relevant: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MiningScope {
Remote,
Local,
All,
}
impl Default for MiningScope {
fn default() -> Self {
MiningScope::All
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MiningFilters {
pub languages: Vec<String>,
pub license_allowlist: Vec<String>,
pub max_results: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MiningQuery {
pub query: String,
#[serde(default)]
pub scope: MiningScope,
#[serde(default)]
pub filters: MiningFilters,
}
#[derive(Debug, thiserror::Error)]
pub enum MiningError {
#[error("rate limit exceeded: {0}")]
RateLimited(String),
#[error("source unavailable: {0}")]
Unavailable(String),
#[error("invalid query: {0}")]
InvalidQuery(String),
#[error(transparent)]
Other(#[from] anyhow::Error),
}
#[async_trait]
pub trait ReferenceMiner: Send + Sync {
fn name(&self) -> &str;
async fn search(&self, query: &MiningQuery) -> Result<Vec<CodeReference>, MiningError>;
}