1use serde::{Deserialize, Serialize};
2
3use super::{
4 CodeDependencyRecord, CodeParseStatus, CodeParseStatusCounts, DomainError, FreshnessPolicy,
5 error::required_text,
6};
7
8const CODE_SNAPSHOT_FACT_VERSION: &str = "code-facts-js-ts-import-edges-v1-sbom-dependencies-v2";
9
10pub fn code_snapshot_scope_id(
12 repository_id: &str,
13 tree_hash: &str,
14 path_filters: &[String],
15 language_filters: &[String],
16) -> String {
17 let mut input = Vec::new();
18 append_hash_part(&mut input, "git_snapshot");
19 append_hash_part(&mut input, repository_id);
20 append_hash_part(&mut input, tree_hash);
21 append_hash_list(&mut input, path_filters);
22 append_hash_list(&mut input, language_filters);
23 append_hash_part(&mut input, CODE_SNAPSHOT_FACT_VERSION);
24
25 format!("git_snapshot:{:016x}", stable_hash64(&input))
26}
27
28pub fn code_snapshot_expected_scope_id(
29 repository_id: &str,
30 tree_hash: &str,
31 path_filters: &[String],
32 language_filters: &[String],
33) -> Option<String> {
34 Some(code_snapshot_scope_id(
35 repository_id,
36 tree_hash,
37 path_filters,
38 language_filters,
39 ))
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44pub struct RepositoryCodeRange {
45 pub start: u32,
46 pub end: u32,
47}
48
49impl RepositoryCodeRange {
50 pub fn new(field: &'static str, start: usize, end: usize) -> Result<Self, DomainError> {
52 if end < start {
53 return Err(DomainError::invalid(
54 field,
55 "end must be greater than or equal to start",
56 ));
57 }
58
59 Ok(Self {
60 start: checked_u32(field, start)?,
61 end: checked_u32(field, end)?,
62 })
63 }
64}
65
66#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
68pub struct CodeRepositoryRegistration {
69 pub repository_id: String,
70 pub alias: String,
71 pub root_path: String,
72 pub path_filters: Vec<String>,
73 pub language_filters: Vec<String>,
74}
75
76impl CodeRepositoryRegistration {
77 pub fn new(
79 repository_id: impl Into<String>,
80 alias: impl Into<String>,
81 root_path: impl Into<String>,
82 path_filters: Vec<String>,
83 language_filters: Vec<String>,
84 ) -> Result<Self, DomainError> {
85 Ok(Self {
86 repository_id: required_text("repository_id", repository_id)?,
87 alias: required_text("alias", alias)?,
88 root_path: required_text("root_path", root_path)?,
89 path_filters: normalize_filter_list("path_filter", path_filters)?,
90 language_filters: normalize_filter_list("language_filter", language_filters)?,
91 })
92 }
93}
94
95#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
97pub struct CodeRepositorySelector {
98 pub repository: String,
99 pub ref_selector: String,
100 pub path_filters: Vec<String>,
101 pub language_filters: Vec<String>,
102}
103
104impl CodeRepositorySelector {
105 pub fn new(
107 repository: impl Into<String>,
108 ref_selector: impl Into<String>,
109 path_filters: Vec<String>,
110 language_filters: Vec<String>,
111 ) -> Result<Self, DomainError> {
112 Ok(Self {
113 repository: required_text("repository", repository)?,
114 ref_selector: required_text("ref_selector", ref_selector)?,
115 path_filters: normalize_filter_list("path_filter", path_filters)?,
116 language_filters: normalize_filter_list("language_filter", language_filters)?,
117 })
118 }
119}
120
121#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
123#[serde(rename_all = "snake_case")]
124pub enum CodeIndexMode {
125 Full,
126 Incremental { base_ref: String, head_ref: String },
127 WorktreeOverlay,
128}
129
130impl CodeIndexMode {
131 pub fn incremental(
133 base_ref: impl Into<String>,
134 head_ref: impl Into<String>,
135 ) -> Result<Self, DomainError> {
136 Ok(Self::Incremental {
137 base_ref: required_text("base_ref", base_ref)?,
138 head_ref: required_text("head_ref", head_ref)?,
139 })
140 }
141}
142
143#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
145pub struct CodeIndexRequest {
146 pub repository: CodeRepositorySelector,
147 pub mode: CodeIndexMode,
148 pub freshness_policy: FreshnessPolicy,
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
153#[serde(rename_all = "snake_case")]
154pub enum CodeQueryKind {
155 Hybrid,
156 Symbol,
157 Definition,
158 References,
159 Callers,
160 Callees,
161 Imports,
162 Sbom,
163 Impact,
164}
165
166#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
168pub struct CodeRetrievalRequest {
169 pub query: String,
170 pub repository: CodeRepositorySelector,
171 pub code_query_kind: CodeQueryKind,
172 pub limit: usize,
173 pub freshness_policy: FreshnessPolicy,
174}
175
176impl CodeRetrievalRequest {
177 pub fn new(
179 query: impl Into<String>,
180 repository: CodeRepositorySelector,
181 code_query_kind: CodeQueryKind,
182 limit: usize,
183 freshness_policy: FreshnessPolicy,
184 ) -> Result<Self, DomainError> {
185 let limit = match limit {
186 1..=50 => limit,
187 0 => return Err(DomainError::invalid("limit", "must be greater than zero")),
188 _ => return Err(DomainError::invalid("limit", "must be 50 or less")),
189 };
190
191 Ok(Self {
192 query: required_text("query", query)?,
193 repository,
194 code_query_kind,
195 limit,
196 freshness_policy,
197 })
198 }
199}
200
201#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
203pub struct CodeFeatureFlagRequest {
204 #[serde(skip_serializing_if = "Option::is_none")]
205 pub query: Option<String>,
206 pub repository: CodeRepositorySelector,
207 pub limit: usize,
208 pub freshness_policy: FreshnessPolicy,
209}
210
211impl CodeFeatureFlagRequest {
212 pub fn new(
214 query: Option<String>,
215 repository: CodeRepositorySelector,
216 limit: usize,
217 freshness_policy: FreshnessPolicy,
218 ) -> Result<Self, DomainError> {
219 let limit = match limit {
220 1..=100 => limit,
221 0 => return Err(DomainError::invalid("limit", "must be greater than zero")),
222 _ => return Err(DomainError::invalid("limit", "must be 100 or less")),
223 };
224 let query = query
225 .map(|value| required_text("query", value))
226 .transpose()?;
227
228 Ok(Self {
229 query,
230 repository,
231 limit,
232 freshness_policy,
233 })
234 }
235}
236
237#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
239pub struct CodeImpactRequest {
240 pub repository: CodeRepositorySelector,
241 pub base_ref: String,
242 pub head_ref: String,
243 pub limit: usize,
244}
245
246impl CodeImpactRequest {
247 pub fn new(
249 repository: CodeRepositorySelector,
250 base_ref: impl Into<String>,
251 head_ref: impl Into<String>,
252 limit: usize,
253 ) -> Result<Self, DomainError> {
254 let limit = match limit {
255 1..=100 => limit,
256 0 => return Err(DomainError::invalid("limit", "must be greater than zero")),
257 _ => return Err(DomainError::invalid("limit", "must be 100 or less")),
258 };
259
260 Ok(Self {
261 repository,
262 base_ref: required_text("base_ref", base_ref)?,
263 head_ref: required_text("head_ref", head_ref)?,
264 limit,
265 })
266 }
267}
268
269#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
271#[serde(rename_all = "snake_case")]
272pub enum CodeRetrievalLayer {
273 Lexical,
274 Symbol,
275 Definition,
276 Reference,
277 CallGraph,
278 ImportGraph,
279 Sbom,
280 Impact,
281 TextFallback,
282}
283
284impl CodeRetrievalLayer {
285 pub const fn as_str(self) -> &'static str {
287 match self {
288 Self::Lexical => "lexical",
289 Self::Symbol => "symbol",
290 Self::Definition => "definition",
291 Self::Reference => "reference",
292 Self::CallGraph => "call_graph",
293 Self::ImportGraph => "import_graph",
294 Self::Sbom => "sbom",
295 Self::Impact => "impact",
296 Self::TextFallback => "text_fallback",
297 }
298 }
299}
300
301#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
303pub struct CodeRepositoryStatus {
304 pub repository_id: String,
305 pub alias: String,
306 pub root_path: String,
307 pub path_filters: Vec<String>,
308 pub language_filters: Vec<String>,
309 #[serde(skip_serializing_if = "Option::is_none")]
310 pub last_indexed_scope_id: Option<String>,
311 pub last_indexed_commit: Option<String>,
312 pub tree_hash: Option<String>,
313 pub state: String,
314 pub indexed_file_count: usize,
315 pub symbol_count: usize,
316 pub reference_count: usize,
317 pub chunk_count: usize,
318 pub stale: bool,
319 #[serde(skip_serializing_if = "Option::is_none")]
320 pub degraded_reason: Option<String>,
321}
322
323#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
325pub struct RepositoryCodeFileRecord {
326 pub repository_id: String,
327 pub source_scope: String,
328 pub file_id: String,
329 pub path: String,
330 pub language_id: String,
331 pub blob_hash: String,
332 pub byte_len: usize,
333 pub line_count: usize,
334 pub parse_status: CodeParseStatus,
335 #[serde(skip_serializing_if = "Option::is_none")]
336 pub degraded_reason: Option<String>,
337}
338
339#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
341pub struct CodeFileFingerprint {
342 pub path: String,
343 pub blob_hash: String,
344}
345
346#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
348pub struct RepositoryCodeSymbolRecord {
349 pub repository_id: String,
350 pub source_scope: String,
351 pub symbol_snapshot_id: String,
352 pub canonical_symbol_id: String,
353 pub file_id: String,
354 pub path: String,
355 pub language_id: String,
356 pub name: String,
357 pub qualified_name: String,
358 pub kind: String,
359 pub signature: String,
360 #[serde(skip_serializing_if = "Option::is_none")]
361 pub doc_comment: Option<String>,
362 pub byte_range: RepositoryCodeRange,
363 pub line_range: RepositoryCodeRange,
364}
365
366#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
368pub struct RepositoryCodeReferenceRecord {
369 pub repository_id: String,
370 pub source_scope: String,
371 pub reference_id: String,
372 pub file_id: String,
373 pub path: String,
374 pub name: String,
375 pub kind: String,
376 #[serde(skip_serializing_if = "Option::is_none")]
377 pub target_symbol_snapshot_id: Option<String>,
378 #[serde(skip_serializing_if = "Option::is_none")]
379 pub target_hint: Option<String>,
380 pub resolution_state: String,
381 pub confidence_basis_points: u16,
382 pub confidence_tier: String,
383 pub byte_range: RepositoryCodeRange,
384 pub line_range: RepositoryCodeRange,
385}
386
387#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
389pub struct CodeImportRecord {
390 pub repository_id: String,
391 pub source_scope: String,
392 pub import_id: String,
393 pub file_id: String,
394 pub path: String,
395 pub module: String,
396 #[serde(skip_serializing_if = "Option::is_none")]
397 pub target_hint: Option<String>,
398 pub resolution_state: String,
399 pub confidence_basis_points: u16,
400 pub confidence_tier: String,
401 pub line_range: RepositoryCodeRange,
402}
403
404#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
406pub struct CodeCallRecord {
407 pub repository_id: String,
408 pub source_scope: String,
409 pub call_id: String,
410 pub file_id: String,
411 pub path: String,
412 pub caller_symbol_snapshot_id: Option<String>,
413 pub caller_name: Option<String>,
414 #[serde(skip_serializing_if = "Option::is_none")]
415 pub callee_symbol_snapshot_id: Option<String>,
416 pub callee_name: String,
417 #[serde(skip_serializing_if = "Option::is_none")]
418 pub target_hint: Option<String>,
419 pub resolution_state: String,
420 pub confidence_basis_points: u16,
421 pub confidence_tier: String,
422 pub line_range: RepositoryCodeRange,
423}
424
425#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
427pub struct CodeFeatureFlagRecord {
428 pub repository_id: String,
429 pub source_scope: String,
430 pub feature_flag_id: String,
431 pub usage_id: String,
432 pub file_id: String,
433 pub path: String,
434 pub language_id: String,
435 pub name: String,
436 pub source_kind: String,
437 pub source_key: String,
438 pub edge_kind: String,
439 pub confidence_basis_points: u16,
440 pub confidence_tier: String,
441 pub byte_range: RepositoryCodeRange,
442 pub line_range: RepositoryCodeRange,
443 pub excerpt: String,
444}
445
446#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
448pub struct RepositoryCodeChunkRecord {
449 pub repository_id: String,
450 pub source_scope: String,
451 pub chunk_id: String,
452 pub file_id: String,
453 pub path: String,
454 pub language_id: String,
455 pub content: String,
456 pub byte_range: RepositoryCodeRange,
457 pub line_range: RepositoryCodeRange,
458 pub symbol_snapshot_id: Option<String>,
459}
460
461#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
463pub struct CodeFileDiagnostic {
464 pub repository_id: String,
465 pub source_scope: String,
466 pub path: String,
467 pub parse_status: CodeParseStatus,
468 pub message: String,
469}
470
471#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
473pub struct CodePathTombstone {
474 pub repository_id: String,
475 pub source_scope: String,
476 pub old_path: String,
477 pub new_path: Option<String>,
478 pub base_ref: String,
479 pub head_ref: String,
480}
481
482#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
484pub struct CodeIndexSnapshot {
485 pub repository_id: String,
486 pub source_scope: String,
487 #[serde(skip_serializing_if = "Option::is_none")]
488 pub base_resolved_commit_sha: Option<String>,
489 pub resolved_commit_sha: String,
490 pub tree_hash: String,
491 pub path_filters: Vec<String>,
492 pub language_filters: Vec<String>,
493 pub full_replace: bool,
494 pub changed_path_count: usize,
495 pub skipped_unchanged_count: usize,
496 pub deleted_paths: Vec<String>,
497 pub tombstones: Vec<CodePathTombstone>,
498 pub files: Vec<RepositoryCodeFileRecord>,
499 pub symbols: Vec<RepositoryCodeSymbolRecord>,
500 pub references: Vec<RepositoryCodeReferenceRecord>,
501 pub imports: Vec<CodeImportRecord>,
502 pub calls: Vec<CodeCallRecord>,
503 pub dependencies: Vec<CodeDependencyRecord>,
504 pub feature_flags: Vec<CodeFeatureFlagRecord>,
505 pub chunks: Vec<RepositoryCodeChunkRecord>,
506 pub diagnostics: Vec<CodeFileDiagnostic>,
507}
508
509#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
511pub struct CodeIndexResourceBudget {
512 pub max_files_per_batch: usize,
513 pub max_bytes_per_batch: usize,
514 pub max_rows_per_batch: usize,
515}
516
517impl CodeIndexResourceBudget {
518 pub const DEFAULT_MAX_FILES_PER_BATCH: usize = 512;
519 pub const DEFAULT_MAX_BYTES_PER_BATCH: usize = 16 * 1024 * 1024;
520 pub const DEFAULT_MAX_ROWS_PER_BATCH: usize = 150_000;
521
522 pub fn new(
524 max_files_per_batch: usize,
525 max_bytes_per_batch: usize,
526 max_rows_per_batch: usize,
527 ) -> Result<Self, DomainError> {
528 if max_files_per_batch == 0 {
529 return Err(DomainError::invalid(
530 "max_files_per_batch",
531 "must be greater than zero",
532 ));
533 }
534 if max_bytes_per_batch == 0 {
535 return Err(DomainError::invalid(
536 "max_bytes_per_batch",
537 "must be greater than zero",
538 ));
539 }
540 if max_rows_per_batch == 0 {
541 return Err(DomainError::invalid(
542 "max_rows_per_batch",
543 "must be greater than zero",
544 ));
545 }
546
547 Ok(Self {
548 max_files_per_batch,
549 max_bytes_per_batch,
550 max_rows_per_batch,
551 })
552 }
553}
554
555impl Default for CodeIndexResourceBudget {
556 fn default() -> Self {
557 Self {
558 max_files_per_batch: Self::DEFAULT_MAX_FILES_PER_BATCH,
559 max_bytes_per_batch: Self::DEFAULT_MAX_BYTES_PER_BATCH,
560 max_rows_per_batch: Self::DEFAULT_MAX_ROWS_PER_BATCH,
561 }
562 }
563}
564
565#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
567pub struct CodeIndexSession {
568 pub repository_id: String,
569 pub source_scope: String,
570 #[serde(skip_serializing_if = "Option::is_none")]
571 pub base_resolved_commit_sha: Option<String>,
572 pub resolved_commit_sha: String,
573 pub tree_hash: String,
574 pub path_filters: Vec<String>,
575 pub language_filters: Vec<String>,
576 pub full_replace: bool,
577 pub total_path_count: usize,
578 pub changed_path_count: usize,
579 pub skipped_unchanged_count: usize,
580 pub deleted_paths: Vec<String>,
581 pub tombstones: Vec<CodePathTombstone>,
582 pub resource_budget: CodeIndexResourceBudget,
583}
584
585#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
587pub struct CodeIndexBatch {
588 pub repository_id: String,
589 pub source_scope: String,
590 pub batch_index: usize,
591 pub parsed_byte_count: usize,
592 pub files: Vec<RepositoryCodeFileRecord>,
593 pub symbols: Vec<RepositoryCodeSymbolRecord>,
594 pub references: Vec<RepositoryCodeReferenceRecord>,
595 pub imports: Vec<CodeImportRecord>,
596 pub dependencies: Vec<CodeDependencyRecord>,
597 pub feature_flags: Vec<CodeFeatureFlagRecord>,
598 pub chunks: Vec<RepositoryCodeChunkRecord>,
599 pub diagnostics: Vec<CodeFileDiagnostic>,
600}
601
602impl CodeIndexBatch {
603 pub fn row_count(&self) -> usize {
605 self.files
606 .len()
607 .saturating_add(self.symbols.len())
608 .saturating_add(self.references.len())
609 .saturating_add(self.imports.len())
610 .saturating_add(self.dependencies.len())
611 .saturating_add(self.feature_flags.len())
612 .saturating_add(self.chunks.len())
613 .saturating_add(self.diagnostics.len())
614 }
615}
616
617#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
619pub struct CodeIndexCheckpoint {
620 pub repository_id: String,
621 pub source_scope: String,
622 pub state: String,
623 pub total_path_count: usize,
624 pub parsed_file_count: usize,
625 pub committed_file_count: usize,
626 pub committed_symbol_count: usize,
627 pub committed_reference_count: usize,
628 pub committed_chunk_count: usize,
629 pub batch_count: usize,
630 #[serde(skip_serializing_if = "Option::is_none")]
631 pub last_path: Option<String>,
632 pub resource_budget: CodeIndexResourceBudget,
633 pub updated_at_ms: u64,
634}
635
636#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
638#[serde(rename_all = "snake_case")]
639pub enum CodeIndexTaskState {
640 Queued,
641 Running,
642 Succeeded,
643 Retrying,
644 Failed,
645 DeadLetter,
646 Cancelled,
647}
648
649impl CodeIndexTaskState {
650 pub const fn as_str(self) -> &'static str {
652 match self {
653 Self::Queued => "queued",
654 Self::Running => "running",
655 Self::Succeeded => "succeeded",
656 Self::Retrying => "retrying",
657 Self::Failed => "failed",
658 Self::DeadLetter => "dead_letter",
659 Self::Cancelled => "cancelled",
660 }
661 }
662
663 pub fn parse(value: &str) -> Result<Self, DomainError> {
665 match value {
666 "queued" => Ok(Self::Queued),
667 "running" => Ok(Self::Running),
668 "succeeded" => Ok(Self::Succeeded),
669 "retrying" => Ok(Self::Retrying),
670 "failed" => Ok(Self::Failed),
671 "dead_letter" => Ok(Self::DeadLetter),
672 "cancelled" => Ok(Self::Cancelled),
673 _ => Err(DomainError::invalid(
674 "code_index_task_state",
675 "unknown code index task state",
676 )),
677 }
678 }
679
680 pub const fn is_unfinished(self) -> bool {
682 matches!(self, Self::Queued | Self::Running | Self::Retrying)
683 }
684}
685
686#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
688pub struct CodeIndexTaskRecord {
689 pub task_id: String,
690 pub repository_id: String,
691 pub alias: String,
692 pub ref_selector: String,
693 pub resolved_commit_sha: String,
694 pub tree_hash: String,
695 pub source_scope: String,
696 pub path_filters: Vec<String>,
697 pub language_filters: Vec<String>,
698 pub mode: CodeIndexMode,
699 pub state: CodeIndexTaskState,
700 #[serde(skip_serializing_if = "Option::is_none")]
701 pub lease_owner: Option<String>,
702 #[serde(skip_serializing_if = "Option::is_none")]
703 pub lease_expires_at_ms: Option<u64>,
704 pub attempt_count: u32,
705 pub next_retry_at_ms: u64,
706 pub input_fingerprint: String,
707 pub resource_budget: CodeIndexResourceBudget,
708 pub payload_json: String,
709 #[serde(skip_serializing_if = "Option::is_none")]
710 pub last_error_kind: Option<String>,
711 #[serde(skip_serializing_if = "Option::is_none")]
712 pub last_error_message: Option<String>,
713 pub created_at_ms: u64,
714 pub updated_at_ms: u64,
715}
716
717#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
719pub struct CodeScopeRetentionSummary {
720 pub repository_id: String,
721 pub retained_scope_count: usize,
722 pub prunable_scope_count: usize,
723 pub pruned_scope_count: usize,
724 pub retained_scopes: Vec<String>,
725 pub prunable_scopes: Vec<String>,
726 pub pruned_scopes: Vec<String>,
727}
728
729#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
731pub struct CodeIndexProgressSummary {
732 pub git_file_count: usize,
733 pub blob_read_count: usize,
734 pub parsed_file_count: usize,
735 pub sqlite_write_count: usize,
736 pub skipped_file_count: usize,
737 pub degraded_file_count: usize,
738 pub batch_count: usize,
739 pub checkpoint_file_count: usize,
740 pub resource_budget: CodeIndexResourceBudget,
741}
742
743#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
745pub struct CodeIndexSummary {
746 pub repository_id: String,
747 pub source_scope: String,
748 pub resolved_commit_sha: String,
749 pub tree_hash: String,
750 pub indexed_file_count: usize,
751 pub changed_path_count: usize,
752 pub skipped_unchanged_count: usize,
753 pub deleted_path_count: usize,
754 pub symbol_count: usize,
755 pub reference_count: usize,
756 pub chunk_count: usize,
757 pub degraded_file_count: usize,
758 pub progress: CodeIndexProgressSummary,
759}
760
761#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
763pub struct CodeRepositoryLanguagePreview {
764 pub language_id: String,
765 pub file_count: usize,
766 pub byte_count: usize,
767}
768
769#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
771pub struct CodeRepositoryLargestFile {
772 pub path: String,
773 pub byte_count: usize,
774}
775
776#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
778pub struct CodeRepositoryExcludedPath {
779 pub path: String,
780 pub reason: String,
781}
782
783#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
785pub struct CodeRepositoryScopePreview {
786 pub repository_id: String,
787 pub alias: String,
788 pub requested_ref: String,
789 pub resolved_commit_sha: String,
790 pub tree_hash: String,
791 pub selected_file_count: usize,
792 pub selected_byte_count: usize,
793 pub unsupported_file_count: usize,
794 pub generated_or_heavy_file_count: usize,
795 pub expected_degraded_file_count: usize,
796 pub language_distribution: Vec<CodeRepositoryLanguagePreview>,
797 pub largest_files: Vec<CodeRepositoryLargestFile>,
798 pub excluded_paths: Vec<CodeRepositoryExcludedPath>,
799}
800
801#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
803pub struct CodeRepositoryTotals {
804 pub repository_count: usize,
805 pub indexed_file_count: usize,
806 pub symbol_count: usize,
807 pub reference_count: usize,
808 pub chunk_count: usize,
809 pub degraded_file_count: usize,
810 pub parse_status_counts: CodeParseStatusCounts,
811}
812
813#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
815pub struct CodeRepositoryLatencySample {
816 pub query: String,
817 pub kind: CodeQueryKind,
818 pub result_count: usize,
819 pub duration_ms: u64,
820}
821
822#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
824pub struct CodeRepositoryReport {
825 pub repository_id: String,
826 pub alias: String,
827 pub root_path: String,
828 pub path_filters: Vec<String>,
829 pub language_filters: Vec<String>,
830 pub resolved_commit_sha: Option<String>,
831 pub tree_hash: Option<String>,
832 pub indexed_file_count: usize,
833 pub symbol_count: usize,
834 pub reference_count: usize,
835 pub chunk_count: usize,
836 pub degraded_file_count: usize,
837 pub resolved_edge_count: usize,
838 pub ambiguous_edge_count: usize,
839 pub unresolved_edge_count: usize,
840 pub degradation_summary: Vec<String>,
841 pub representative_queries: Vec<String>,
842 pub latency_samples: Vec<CodeRepositoryLatencySample>,
843 pub freshness_state: String,
844}
845
846#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
848pub struct CodeImpactPathGroups {
849 pub in_scope_changed_paths: Vec<String>,
850 pub out_of_scope_changed_paths: Vec<String>,
851}
852
853#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
855pub struct CodeRetrievalHit {
856 pub repository_id: String,
857 pub scope_id: String,
858 pub resolved_commit_sha: String,
859 pub tree_hash: String,
860 pub path: String,
861 pub language_id: String,
862 pub byte_range: RepositoryCodeRange,
863 pub line_range: RepositoryCodeRange,
864 pub symbol_snapshot_id: Option<String>,
865 #[serde(skip_serializing_if = "Option::is_none")]
866 pub canonical_symbol_id: Option<String>,
867 pub file_id: Option<String>,
868 pub retrieval_layers: Vec<CodeRetrievalLayer>,
869 pub index_versions: Vec<String>,
870 pub stale: bool,
871 #[serde(skip_serializing_if = "Option::is_none")]
872 pub degraded_reason: Option<String>,
873 #[serde(skip_serializing_if = "Option::is_none")]
874 pub edge_kind: Option<String>,
875 #[serde(skip_serializing_if = "Option::is_none")]
876 pub edge_resolution_state: Option<String>,
877 #[serde(skip_serializing_if = "Option::is_none")]
878 pub edge_target_hint: Option<String>,
879 #[serde(skip_serializing_if = "Option::is_none")]
880 pub edge_confidence_basis_points: Option<u16>,
881 #[serde(skip_serializing_if = "Option::is_none")]
882 pub edge_confidence_tier: Option<String>,
883 pub score: f64,
884 pub excerpt: String,
885}
886
887#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
889pub struct CodeFeatureFlagUsage {
890 pub usage_id: String,
891 pub path: String,
892 pub language_id: String,
893 pub file_id: String,
894 pub byte_range: RepositoryCodeRange,
895 pub line_range: RepositoryCodeRange,
896 pub edge_kind: String,
897 #[serde(skip_serializing_if = "Option::is_none")]
898 pub related_symbol_snapshot_id: Option<String>,
899 #[serde(skip_serializing_if = "Option::is_none")]
900 pub related_symbol_name: Option<String>,
901 pub confidence_basis_points: u16,
902 pub confidence_tier: String,
903 pub excerpt: String,
904}
905
906#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
908pub struct CodeFeatureFlagGraph {
909 pub feature_flag_id: String,
910 pub name: String,
911 pub source_kind: String,
912 pub source_key: String,
913 pub score: f64,
914 pub usages: Vec<CodeFeatureFlagUsage>,
915}
916
917fn normalize_filter_list(
918 field: &'static str,
919 values: Vec<String>,
920) -> Result<Vec<String>, DomainError> {
921 let mut normalized = Vec::new();
922 for value in values {
923 let value = required_text(field, value)?;
924 if !normalized.contains(&value) {
925 normalized.push(value);
926 }
927 }
928
929 Ok(normalized)
930}
931
932fn checked_u32(field: &'static str, value: usize) -> Result<u32, DomainError> {
933 u32::try_from(value).map_err(|_| DomainError::invalid(field, "must fit in u32"))
934}
935
936fn append_hash_list(input: &mut Vec<u8>, values: &[String]) {
937 input.extend_from_slice(&(values.len() as u64).to_le_bytes());
938 for value in values {
939 append_hash_part(input, value);
940 }
941}
942
943fn append_hash_part(input: &mut Vec<u8>, value: &str) {
944 input.extend_from_slice(&(value.len() as u64).to_le_bytes());
945 input.extend_from_slice(value.as_bytes());
946}
947
948fn stable_hash64(bytes: &[u8]) -> u64 {
949 const FNV_OFFSET_BASIS: u64 = 0xcbf29ce484222325;
950 const FNV_PRIME: u64 = 0x100000001b3;
951
952 let mut hash = FNV_OFFSET_BASIS;
953 for byte in bytes {
954 hash ^= u64::from(*byte);
955 hash = hash.wrapping_mul(FNV_PRIME);
956 }
957
958 hash
959}