|
1 | | -use std::path::{Path, PathBuf}; |
2 | | - |
3 | | -use crate::core; |
4 | | - |
5 | | -pub(in crate::review::pipeline) fn gather_related_file_context( |
6 | | - index: &core::SymbolIndex, |
7 | | - file_path: &Path, |
8 | | - repo_path: &Path, |
9 | | -) -> Vec<core::LLMContextChunk> { |
10 | | - let mut chunks: Vec<core::LLMContextChunk> = Vec::new(); |
11 | | - |
12 | | - let callers = index.reverse_deps(file_path); |
13 | | - for caller_path in callers.iter().take(3) { |
14 | | - if let Some(summary) = index.file_summary(caller_path) { |
15 | | - let truncated: String = if summary.len() > 2000 { |
16 | | - let mut end = 2000; |
17 | | - while end > 0 && !summary.is_char_boundary(end) { |
18 | | - end -= 1; |
19 | | - } |
20 | | - format!("{}...[truncated]", &summary[..end]) |
21 | | - } else { |
22 | | - summary.to_string() |
23 | | - }; |
24 | | - chunks.push( |
25 | | - core::LLMContextChunk::reference( |
26 | | - caller_path.clone(), |
27 | | - format!("[Caller/dependent file]\n{}", truncated), |
28 | | - ) |
29 | | - .with_provenance(core::ContextProvenance::ReverseDependencySummary), |
30 | | - ); |
31 | | - } |
32 | | - } |
33 | | - |
34 | | - let test_files = find_test_files(file_path, repo_path); |
35 | | - for test_path in test_files.iter().take(2) { |
36 | | - let relative: &Path = test_path.strip_prefix(repo_path).unwrap_or(test_path); |
37 | | - if let Ok(content) = std::fs::read_to_string(test_path) { |
38 | | - let snippet: String = content.lines().take(60).collect::<Vec<_>>().join("\n"); |
39 | | - if !snippet.is_empty() { |
40 | | - chunks.push( |
41 | | - core::LLMContextChunk::reference( |
42 | | - relative.to_path_buf(), |
43 | | - format!("[Test file]\n{}", snippet), |
44 | | - ) |
45 | | - .with_provenance(core::ContextProvenance::RelatedTestFile), |
46 | | - ); |
47 | | - } |
48 | | - } |
49 | | - } |
50 | | - |
51 | | - chunks |
52 | | -} |
53 | | - |
54 | | -fn find_test_files(file_path: &Path, repo_path: &Path) -> Vec<PathBuf> { |
55 | | - let stem = match file_path.file_stem().and_then(|s| s.to_str()) { |
56 | | - Some(s) => s.to_string(), |
57 | | - None => return Vec::new(), |
58 | | - }; |
59 | | - let ext = file_path.extension().and_then(|e| e.to_str()).unwrap_or(""); |
60 | | - let parent = file_path.parent().unwrap_or(Path::new("")); |
61 | | - |
62 | | - let candidates: Vec<PathBuf> = vec![ |
63 | | - repo_path |
64 | | - .join(parent) |
65 | | - .join(format!("test_{}.{}", stem, ext)), |
66 | | - repo_path |
67 | | - .join(parent) |
68 | | - .join(format!("{}_test.{}", stem, ext)), |
69 | | - repo_path |
70 | | - .join(parent) |
71 | | - .join(format!("{}.test.{}", stem, ext)), |
72 | | - repo_path |
73 | | - .join(parent) |
74 | | - .join(format!("{}.spec.{}", stem, ext)), |
75 | | - repo_path |
76 | | - .join(parent) |
77 | | - .join("tests") |
78 | | - .join(format!("{}.{}", stem, ext)), |
79 | | - repo_path |
80 | | - .join(parent) |
81 | | - .join("__tests__") |
82 | | - .join(format!("{}.{}", stem, ext)), |
83 | | - ]; |
84 | | - |
85 | | - candidates |
86 | | - .into_iter() |
87 | | - .filter(|path: &PathBuf| path.is_file()) |
88 | | - .collect() |
89 | | -} |
90 | | - |
91 | | -#[cfg(test)] |
92 | | -mod tests { |
93 | | - #[test] |
94 | | - fn test_utf8_safe_truncation() { |
95 | | - let prefix = "a".repeat(1999); |
96 | | - let value = format!("{}€rest", prefix); |
97 | | - assert!(value.len() > 2000); |
98 | | - |
99 | | - let mut end = 2000; |
100 | | - while end > 0 && !value.is_char_boundary(end) { |
101 | | - end -= 1; |
102 | | - } |
103 | | - let truncated = &value[..end]; |
104 | | - |
105 | | - assert_eq!(end, 1999); |
106 | | - assert!(truncated.starts_with("aaa")); |
107 | | - assert!(!truncated.contains('€')); |
108 | | - } |
109 | | -} |
| 1 | +#[path = "related/callers.rs"] |
| 2 | +mod callers; |
| 3 | +#[path = "related/run.rs"] |
| 4 | +mod run; |
| 5 | +#[path = "related/test_files.rs"] |
| 6 | +mod test_files; |
| 7 | + |
| 8 | +pub(in crate::review::pipeline) use run::gather_related_file_context; |
0 commit comments