Skip to content

Commit f6150cd

Browse files
committed
refactor: split pipeline file context assembly
Break file context gathering into focused helpers so the review pipeline can evolve each context source independently without changing behavior. Made-with: Cursor
1 parent 939d458 commit f6150cd

File tree

4 files changed

+230
-188
lines changed

4 files changed

+230
-188
lines changed
Lines changed: 24 additions & 188 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
use anyhow::Result;
22

3+
#[path = "file_context/base.rs"]
4+
mod base;
5+
#[path = "file_context/finalize.rs"]
6+
mod finalize;
7+
#[path = "file_context/sources.rs"]
8+
mod sources;
9+
310
use crate::config;
411
use crate::core;
512

6-
use super::context::{extract_symbols_from_diff, gather_related_file_context};
713
use super::services::PipelineServices;
814
use super::session::ReviewSession;
915

@@ -21,191 +27,21 @@ pub(super) async fn assemble_file_context(
2127
pre_analysis_context: Vec<core::LLMContextChunk>,
2228
deterministic_comments: Vec<core::Comment>,
2329
) -> Result<PreparedFileContext> {
24-
let mut builder =
25-
FileContextBuilder::new(services, session, diff, pre_analysis_context).await?;
26-
builder.add_symbol_context().await?;
27-
builder.add_related_file_context();
28-
builder.add_semantic_context().await;
29-
builder.add_path_context().await?;
30-
builder.inject_repository_context().await?;
31-
Ok(builder.finalize(deterministic_comments))
32-
}
33-
34-
struct FileContextBuilder<'a> {
35-
services: &'a PipelineServices,
36-
session: &'a ReviewSession,
37-
diff: &'a core::UnifiedDiff,
38-
context_chunks: Vec<core::LLMContextChunk>,
39-
path_config: Option<config::PathConfig>,
40-
}
41-
42-
impl<'a> FileContextBuilder<'a> {
43-
async fn new(
44-
services: &'a PipelineServices,
45-
session: &'a ReviewSession,
46-
diff: &'a core::UnifiedDiff,
47-
pre_analysis_context: Vec<core::LLMContextChunk>,
48-
) -> Result<Self> {
49-
let mut context_chunks = services
50-
.context_fetcher
51-
.fetch_context_for_file(&diff.file_path, &changed_line_ranges(diff))
52-
.await?;
53-
context_chunks.extend(pre_analysis_context);
54-
55-
Ok(Self {
56-
services,
57-
session,
58-
diff,
59-
context_chunks,
60-
path_config: services.config.get_path_config(&diff.file_path).cloned(),
61-
})
62-
}
63-
64-
async fn add_symbol_context(&mut self) -> Result<()> {
65-
let symbols = extract_symbols_from_diff(self.diff);
66-
if symbols.is_empty() {
67-
return Ok(());
68-
}
69-
70-
let definition_chunks = self
71-
.services
72-
.context_fetcher
73-
.fetch_related_definitions(&self.diff.file_path, &symbols)
74-
.await?;
75-
self.context_chunks.extend(definition_chunks);
76-
77-
if let Some(index) = self.session.symbol_index.as_ref() {
78-
let index_chunks = self
79-
.services
80-
.context_fetcher
81-
.fetch_related_definitions_with_index(
82-
&self.diff.file_path,
83-
&symbols,
84-
index,
85-
self.services.config.symbol_index_max_locations,
86-
self.services.config.symbol_index_graph_hops,
87-
self.services.config.symbol_index_graph_max_files,
88-
)
89-
.await?;
90-
self.context_chunks.extend(index_chunks);
91-
}
92-
93-
Ok(())
94-
}
95-
96-
fn add_related_file_context(&mut self) {
97-
if let Some(index) = self.session.symbol_index.as_ref() {
98-
let caller_chunks =
99-
gather_related_file_context(index, &self.diff.file_path, &self.services.repo_path);
100-
self.context_chunks.extend(caller_chunks);
101-
}
102-
}
103-
104-
async fn add_semantic_context(&mut self) {
105-
let Some(index) = self.session.semantic_index.as_ref() else {
106-
return;
107-
};
108-
109-
let semantic_chunks = core::semantic_context_for_diff(
110-
index,
111-
self.diff,
112-
self.session
113-
.source_files
114-
.get(&self.diff.file_path)
115-
.map(|content| content.as_str()),
116-
self.services.embedding_adapter.as_deref(),
117-
self.services.config.semantic_rag_top_k,
118-
self.services.config.semantic_rag_min_similarity,
119-
)
120-
.await;
121-
self.context_chunks.extend(semantic_chunks);
122-
}
123-
124-
async fn add_path_context(&mut self) -> Result<()> {
125-
let Some(path_config) = self.path_config.as_ref() else {
126-
return Ok(());
127-
};
128-
129-
if !path_config.focus.is_empty() {
130-
self.context_chunks.push(
131-
core::LLMContextChunk::documentation(
132-
self.diff.file_path.clone(),
133-
format!(
134-
"Focus areas for this file: {}",
135-
path_config.focus.join(", ")
136-
),
137-
)
138-
.with_provenance(core::ContextProvenance::PathSpecificFocusAreas),
139-
);
140-
}
141-
142-
if !path_config.extra_context.is_empty() {
143-
let extra_chunks = self
144-
.services
145-
.context_fetcher
146-
.fetch_additional_context(&path_config.extra_context)
147-
.await?;
148-
self.context_chunks.extend(extra_chunks);
149-
}
150-
151-
Ok(())
152-
}
153-
154-
async fn inject_repository_context(&mut self) -> Result<()> {
155-
super::super::context_helpers::inject_custom_context(
156-
&self.services.config,
157-
&self.services.context_fetcher,
158-
self.diff,
159-
&mut self.context_chunks,
160-
)
161-
.await?;
162-
super::super::context_helpers::inject_pattern_repository_context(
163-
&self.services.config,
164-
&self.services.pattern_repositories,
165-
&self.services.context_fetcher,
166-
self.diff,
167-
&mut self.context_chunks,
168-
)
169-
.await?;
170-
171-
Ok(())
172-
}
173-
174-
fn finalize(mut self, deterministic_comments: Vec<core::Comment>) -> PreparedFileContext {
175-
let active_rules = core::active_rules_for_file(
176-
&self.services.review_rules,
177-
&self.diff.file_path,
178-
self.services.config.max_active_rules,
179-
);
180-
super::super::rule_helpers::inject_rule_context(
181-
self.diff,
182-
&active_rules,
183-
&mut self.context_chunks,
184-
);
185-
self.context_chunks = super::super::context_helpers::rank_and_trim_context_chunks(
186-
self.diff,
187-
self.context_chunks,
188-
self.services.config.context_max_chunks,
189-
self.services.config.context_budget_chars,
190-
);
191-
192-
PreparedFileContext {
193-
active_rules,
194-
path_config: self.path_config,
195-
deterministic_comments,
196-
context_chunks: self.context_chunks,
197-
}
198-
}
199-
}
200-
201-
fn changed_line_ranges(diff: &core::UnifiedDiff) -> Vec<(usize, usize)> {
202-
diff.hunks
203-
.iter()
204-
.map(|hunk| {
205-
(
206-
hunk.new_start,
207-
hunk.new_start + hunk.new_lines.saturating_sub(1),
208-
)
209-
})
210-
.collect()
30+
let path_config = services.config.get_path_config(&diff.file_path).cloned();
31+
let mut context_chunks =
32+
base::initial_context_chunks(services, diff, pre_analysis_context).await?;
33+
34+
sources::add_symbol_context(services, session, diff, &mut context_chunks).await?;
35+
sources::add_related_file_context(services, session, diff, &mut context_chunks);
36+
sources::add_semantic_context(services, session, diff, &mut context_chunks).await;
37+
sources::add_path_context(services, diff, path_config.as_ref(), &mut context_chunks).await?;
38+
sources::inject_repository_context(services, diff, &mut context_chunks).await?;
39+
40+
Ok(finalize::finalize_file_context(
41+
services,
42+
diff,
43+
path_config,
44+
deterministic_comments,
45+
context_chunks,
46+
))
21147
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use anyhow::Result;
2+
3+
use crate::core;
4+
5+
use super::super::services::PipelineServices;
6+
7+
pub(super) async fn initial_context_chunks(
8+
services: &PipelineServices,
9+
diff: &core::UnifiedDiff,
10+
pre_analysis_context: Vec<core::LLMContextChunk>,
11+
) -> Result<Vec<core::LLMContextChunk>> {
12+
let mut context_chunks = services
13+
.context_fetcher
14+
.fetch_context_for_file(&diff.file_path, &changed_line_ranges(diff))
15+
.await?;
16+
context_chunks.extend(pre_analysis_context);
17+
Ok(context_chunks)
18+
}
19+
20+
fn changed_line_ranges(diff: &core::UnifiedDiff) -> Vec<(usize, usize)> {
21+
diff.hunks
22+
.iter()
23+
.map(|hunk| {
24+
(
25+
hunk.new_start,
26+
hunk.new_start + hunk.new_lines.saturating_sub(1),
27+
)
28+
})
29+
.collect()
30+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use crate::config;
2+
use crate::core;
3+
4+
use super::super::services::PipelineServices;
5+
use super::PreparedFileContext;
6+
7+
pub(super) fn finalize_file_context(
8+
services: &PipelineServices,
9+
diff: &core::UnifiedDiff,
10+
path_config: Option<config::PathConfig>,
11+
deterministic_comments: Vec<core::Comment>,
12+
mut context_chunks: Vec<core::LLMContextChunk>,
13+
) -> PreparedFileContext {
14+
let active_rules = core::active_rules_for_file(
15+
&services.review_rules,
16+
&diff.file_path,
17+
services.config.max_active_rules,
18+
);
19+
super::super::super::rule_helpers::inject_rule_context(
20+
diff,
21+
&active_rules,
22+
&mut context_chunks,
23+
);
24+
context_chunks = super::super::super::context_helpers::rank_and_trim_context_chunks(
25+
diff,
26+
context_chunks,
27+
services.config.context_max_chunks,
28+
services.config.context_budget_chars,
29+
);
30+
31+
PreparedFileContext {
32+
active_rules,
33+
path_config,
34+
deterministic_comments,
35+
context_chunks,
36+
}
37+
}

0 commit comments

Comments
 (0)