use serde::{Deserialize, Serialize};
use super::code::SymbolRole;
use super::code_repository_helpers::{
append_hash_list, append_hash_part, checked_u32, normalize_filter_list, stable_hash64,
};
use super::{
CodeParseStatus, CodeParseStatusCounts, CodeWorkspaceDetectionConfig, DomainError,
FreshnessPolicy, error::required_text,
};
const CODE_SNAPSHOT_FACT_VERSION: &str = "code-facts-js-ts-import-edges-v1-sbom-dependencies-v2-python-type-refs-v1-scope-compat-v1-workspace-imports-v1-generated-files-v1-web-routes-v1";
pub fn code_snapshot_scope_id(
repository_id: &str,
tree_hash: &str,
path_filters: &[String],
language_filters: &[String],
) -> String {
let mut input = Vec::new();
append_hash_part(&mut input, "git_snapshot");
append_hash_part(&mut input, repository_id);
append_hash_part(&mut input, tree_hash);
append_hash_list(&mut input, path_filters);
append_hash_list(&mut input, language_filters);
append_hash_part(&mut input, CODE_SNAPSHOT_FACT_VERSION);
format!("git_snapshot:{:016x}", stable_hash64(&input))
}
pub fn code_snapshot_expected_scope_id(
repository_id: &str,
tree_hash: &str,
path_filters: &[String],
language_filters: &[String],
) -> Option<String> {
Some(code_snapshot_scope_id(
repository_id,
tree_hash,
path_filters,
language_filters,
))
}
pub fn code_snapshot_scope_is_fact_versioned(source_scope: &str) -> bool {
let Some(scope_hash) = source_scope.strip_prefix("git_snapshot:") else {
return false;
};
scope_hash.len() == 16
&& scope_hash
.chars()
.all(|character| character.is_ascii_hexdigit())
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RepositoryCodeRange {
pub start: u32,
pub end: u32,
}
impl RepositoryCodeRange {
pub fn new(field: &'static str, start: usize, end: usize) -> Result<Self, DomainError> {
if end < start {
return Err(DomainError::invalid(
field,
"end must be greater than or equal to start",
));
}
Ok(Self {
start: checked_u32(field, start)?,
end: checked_u32(field, end)?,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CodeRepositoryRegistration {
pub repository_id: String,
pub alias: String,
pub root_path: String,
pub path_filters: Vec<String>,
pub language_filters: Vec<String>,
}
impl CodeRepositoryRegistration {
pub fn new(
repository_id: impl Into<String>,
alias: impl Into<String>,
root_path: impl Into<String>,
path_filters: Vec<String>,
language_filters: Vec<String>,
) -> Result<Self, DomainError> {
Ok(Self {
repository_id: required_text("repository_id", repository_id)?,
alias: required_text("alias", alias)?,
root_path: required_text("root_path", root_path)?,
path_filters: normalize_filter_list("path_filter", path_filters)?,
language_filters: normalize_filter_list("language_filter", language_filters)?,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CodeRepositorySelector {
pub repository: String,
pub ref_selector: String,
pub path_filters: Vec<String>,
pub language_filters: Vec<String>,
}
impl CodeRepositorySelector {
pub fn new(
repository: impl Into<String>,
ref_selector: impl Into<String>,
path_filters: Vec<String>,
language_filters: Vec<String>,
) -> Result<Self, DomainError> {
Ok(Self {
repository: required_text("repository", repository)?,
ref_selector: required_text("ref_selector", ref_selector)?,
path_filters: normalize_filter_list("path_filter", path_filters)?,
language_filters: normalize_filter_list("language_filter", language_filters)?,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CodeIndexMode {
Full,
Incremental { base_ref: String, head_ref: String },
WorktreeOverlay,
}
impl CodeIndexMode {
pub fn incremental(
base_ref: impl Into<String>,
head_ref: impl Into<String>,
) -> Result<Self, DomainError> {
Ok(Self::Incremental {
base_ref: required_text("base_ref", base_ref)?,
head_ref: required_text("head_ref", head_ref)?,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CodeIndexRequest {
pub repository: CodeRepositorySelector,
pub mode: CodeIndexMode,
#[serde(default)]
pub workspace_detection: CodeWorkspaceDetectionConfig,
pub freshness_policy: FreshnessPolicy,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CodeQueryKind {
Hybrid,
Symbol,
Definition,
References,
Callers,
Callees,
Imports,
Sbom,
Impact,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CodeRetrievalRequest {
pub query: String,
pub repository: CodeRepositorySelector,
pub code_query_kind: CodeQueryKind,
pub limit: usize,
pub freshness_policy: FreshnessPolicy,
#[serde(default)]
pub exclude_generated: bool,
}
impl CodeRetrievalRequest {
pub fn new(
query: impl Into<String>,
repository: CodeRepositorySelector,
code_query_kind: CodeQueryKind,
limit: usize,
freshness_policy: FreshnessPolicy,
) -> Result<Self, DomainError> {
let limit = match limit {
1..=50 => limit,
0 => return Err(DomainError::invalid("limit", "must be greater than zero")),
_ => return Err(DomainError::invalid("limit", "must be 50 or less")),
};
Ok(Self {
query: required_text("query", query)?,
repository,
code_query_kind,
limit,
freshness_policy,
exclude_generated: false,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CodeFeatureFlagRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub query: Option<String>,
pub repository: CodeRepositorySelector,
pub limit: usize,
pub freshness_policy: FreshnessPolicy,
}
impl CodeFeatureFlagRequest {
pub fn new(
query: Option<String>,
repository: CodeRepositorySelector,
limit: usize,
freshness_policy: FreshnessPolicy,
) -> Result<Self, DomainError> {
let limit = match limit {
1..=100 => limit,
0 => return Err(DomainError::invalid("limit", "must be greater than zero")),
_ => return Err(DomainError::invalid("limit", "must be 100 or less")),
};
let query = query
.map(|value| required_text("query", value))
.transpose()?;
Ok(Self {
query,
repository,
limit,
freshness_policy,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CodeImpactRequest {
pub repository: CodeRepositorySelector,
pub base_ref: String,
pub head_ref: String,
pub limit: usize,
}
impl CodeImpactRequest {
pub fn new(
repository: CodeRepositorySelector,
base_ref: impl Into<String>,
head_ref: impl Into<String>,
limit: usize,
) -> Result<Self, DomainError> {
let limit = match limit {
1..=100 => limit,
0 => return Err(DomainError::invalid("limit", "must be greater than zero")),
_ => return Err(DomainError::invalid("limit", "must be 100 or less")),
};
Ok(Self {
repository,
base_ref: required_text("base_ref", base_ref)?,
head_ref: required_text("head_ref", head_ref)?,
limit,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CodeRetrievalLayer {
Lexical,
Symbol,
Definition,
Reference,
CallGraph,
ImportGraph,
Sbom,
Impact,
TextFallback,
}
impl CodeRetrievalLayer {
pub const fn as_str(self) -> &'static str {
match self {
Self::Lexical => "lexical",
Self::Symbol => "symbol",
Self::Definition => "definition",
Self::Reference => "reference",
Self::CallGraph => "call_graph",
Self::ImportGraph => "import_graph",
Self::Sbom => "sbom",
Self::Impact => "impact",
Self::TextFallback => "text_fallback",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CodeRepositoryStatus {
pub repository_id: String,
pub alias: String,
pub root_path: String,
pub path_filters: Vec<String>,
pub language_filters: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_indexed_scope_id: Option<String>,
pub last_indexed_commit: Option<String>,
pub tree_hash: Option<String>,
pub state: String,
pub indexed_file_count: usize,
pub symbol_count: usize,
pub reference_count: usize,
pub chunk_count: usize,
pub stale: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub degraded_reason: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct CodeRepositoryRemovalSummary {
pub repository_id: String,
pub aliases_removed: Vec<String>,
pub removed_scope_count: usize,
pub removed_index_task_count: usize,
pub removed_repository_set_member_count: usize,
pub invalidated_repository_set_count: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RepositoryCodeFileRecord {
pub repository_id: String,
pub source_scope: String,
pub file_id: String,
pub path: String,
pub language_id: String,
pub blob_hash: String,
pub byte_len: usize,
pub line_count: usize,
pub parse_status: CodeParseStatus,
#[serde(default)]
pub is_generated: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub degraded_reason: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CodeFileFingerprint {
pub path: String,
pub blob_hash: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RepositoryCodeSymbolRecord {
pub repository_id: String,
pub source_scope: String,
pub symbol_snapshot_id: String,
pub canonical_symbol_id: String,
pub file_id: String,
pub path: String,
pub language_id: String,
pub name: String,
pub qualified_name: String,
pub kind: String,
pub signature: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub doc_comment: Option<String>,
pub byte_range: RepositoryCodeRange,
pub line_range: RepositoryCodeRange,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub symbol_role: Option<SymbolRole>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RepositoryCodeReferenceRecord {
pub repository_id: String,
pub source_scope: String,
pub reference_id: String,
pub file_id: String,
pub path: String,
pub name: String,
pub kind: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub target_symbol_snapshot_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub target_hint: Option<String>,
pub resolution_state: String,
pub confidence_basis_points: u16,
pub confidence_tier: String,
pub byte_range: RepositoryCodeRange,
pub line_range: RepositoryCodeRange,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CodeImportRecord {
pub repository_id: String,
pub source_scope: String,
pub import_id: String,
pub file_id: String,
pub path: String,
pub module: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub target_hint: Option<String>,
pub resolution_state: String,
pub confidence_basis_points: u16,
pub confidence_tier: String,
pub line_range: RepositoryCodeRange,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CodeCallRecord {
pub repository_id: String,
pub source_scope: String,
pub call_id: String,
pub file_id: String,
pub path: String,
pub caller_symbol_snapshot_id: Option<String>,
pub caller_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub callee_symbol_snapshot_id: Option<String>,
pub callee_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub target_hint: Option<String>,
pub resolution_state: String,
pub confidence_basis_points: u16,
pub confidence_tier: String,
pub line_range: RepositoryCodeRange,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CodeRouteRecord {
pub repository_id: String,
pub source_scope: String,
pub route_id: String,
pub file_id: String,
pub path: String,
pub language_id: String,
pub url: String,
pub http_method: String,
pub handler_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub handler_symbol_snapshot_id: Option<String>,
pub framework: String,
pub line_range: RepositoryCodeRange,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CodeFeatureFlagRecord {
pub repository_id: String,
pub source_scope: String,
pub feature_flag_id: String,
pub usage_id: String,
pub file_id: String,
pub path: String,
pub language_id: String,
pub name: String,
pub source_kind: String,
pub source_key: String,
pub edge_kind: String,
pub confidence_basis_points: u16,
pub confidence_tier: String,
pub byte_range: RepositoryCodeRange,
pub line_range: RepositoryCodeRange,
pub excerpt: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RepositoryCodeChunkRecord {
pub repository_id: String,
pub source_scope: String,
pub chunk_id: String,
pub file_id: String,
pub path: String,
pub language_id: String,
pub content: String,
pub byte_range: RepositoryCodeRange,
pub line_range: RepositoryCodeRange,
pub symbol_snapshot_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CodeFileDiagnostic {
pub repository_id: String,
pub source_scope: String,
pub path: String,
pub parse_status: CodeParseStatus,
pub message: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CodePathTombstone {
pub repository_id: String,
pub source_scope: String,
pub old_path: String,
pub new_path: Option<String>,
pub base_ref: String,
pub head_ref: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CodeRepositoryLanguagePreview {
pub language_id: String,
pub file_count: usize,
pub byte_count: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CodeRepositoryLargestFile {
pub path: String,
pub byte_count: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CodeRepositoryExcludedPath {
pub path: String,
pub reason: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CodeRepositoryScopePreview {
pub repository_id: String,
pub alias: String,
pub requested_ref: String,
pub resolved_commit_sha: String,
pub tree_hash: String,
pub selected_file_count: usize,
pub selected_byte_count: usize,
pub unsupported_file_count: usize,
pub generated_or_heavy_file_count: usize,
pub expected_degraded_file_count: usize,
pub language_distribution: Vec<CodeRepositoryLanguagePreview>,
pub largest_files: Vec<CodeRepositoryLargestFile>,
pub excluded_paths: Vec<CodeRepositoryExcludedPath>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct CodeRepositoryTotals {
pub repository_count: usize,
pub indexed_file_count: usize,
pub symbol_count: usize,
#[serde(default)]
pub handwritten_symbol_count: usize,
#[serde(default)]
pub generated_symbol_count: usize,
pub reference_count: usize,
pub chunk_count: usize,
pub degraded_file_count: usize,
pub parse_status_counts: CodeParseStatusCounts,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct CodeSymbolGenerationCounts {
#[serde(default)]
pub handwritten_symbol_count: usize,
#[serde(default)]
pub generated_symbol_count: usize,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CodeRepositoryLatencySample {
pub query: String,
pub kind: CodeQueryKind,
pub result_count: usize,
pub duration_ms: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CodeRepositoryReport {
pub repository_id: String,
pub alias: String,
pub root_path: String,
pub path_filters: Vec<String>,
pub language_filters: Vec<String>,
pub resolved_commit_sha: Option<String>,
pub tree_hash: Option<String>,
pub indexed_file_count: usize,
pub symbol_count: usize,
#[serde(default)]
pub handwritten_symbol_count: usize,
#[serde(default)]
pub generated_symbol_count: usize,
pub reference_count: usize,
pub chunk_count: usize,
pub degraded_file_count: usize,
pub resolved_edge_count: usize,
pub ambiguous_edge_count: usize,
pub unresolved_edge_count: usize,
pub degradation_summary: Vec<String>,
pub representative_queries: Vec<String>,
pub latency_samples: Vec<CodeRepositoryLatencySample>,
pub freshness_state: String,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct CodeImpactPathGroups {
pub in_scope_changed_paths: Vec<String>,
pub out_of_scope_changed_paths: Vec<String>,
}
pub use super::code_staleness::StalenessHint;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CodeRetrievalHit {
pub repository_id: String,
pub scope_id: String,
pub resolved_commit_sha: String,
pub tree_hash: String,
pub path: String,
pub language_id: String,
pub byte_range: RepositoryCodeRange,
pub line_range: RepositoryCodeRange,
pub symbol_snapshot_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub canonical_symbol_id: Option<String>,
pub file_id: Option<String>,
pub retrieval_layers: Vec<CodeRetrievalLayer>,
pub index_versions: Vec<String>,
pub stale: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub staleness_hint: Option<StalenessHint>,
#[serde(skip_serializing_if = "Option::is_none")]
pub degraded_reason: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub edge_kind: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub edge_resolution_state: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub edge_target_hint: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub edge_confidence_basis_points: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub edge_confidence_tier: Option<String>,
pub score: f64,
pub excerpt: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CodeFeatureFlagUsage {
pub usage_id: String,
pub path: String,
pub language_id: String,
pub file_id: String,
pub byte_range: RepositoryCodeRange,
pub line_range: RepositoryCodeRange,
pub edge_kind: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub related_symbol_snapshot_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub related_symbol_name: Option<String>,
pub confidence_basis_points: u16,
pub confidence_tier: String,
pub excerpt: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CodeFeatureFlagGraph {
pub feature_flag_id: String,
pub name: String,
pub source_kind: String,
pub source_key: String,
pub score: f64,
pub usages: Vec<CodeFeatureFlagUsage>,
}
#[cfg(test)]
mod fact_version_tests {
use super::CODE_SNAPSHOT_FACT_VERSION;
#[test]
fn code_snapshot_fact_version_includes_generated_and_web_route_facts() {
assert!(CODE_SNAPSHOT_FACT_VERSION.contains("generated-files-v1"));
assert!(CODE_SNAPSHOT_FACT_VERSION.contains("web-routes-v1"));
}
}