Skip to content

Commit fcc452c

Browse files
committed
refactor: split pipeline request helpers
Separate specialized-pass selection, prompt construction, and response schema setup so request building stays easier to maintain without changing behavior. Made-with: Cursor
1 parent 0efb84d commit fcc452c

File tree

4 files changed

+161
-147
lines changed

4 files changed

+161
-147
lines changed

src/review/pipeline/request.rs

Lines changed: 9 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -1,147 +1,9 @@
1-
use anyhow::Result;
2-
3-
use crate::adapters;
4-
use crate::config;
5-
use crate::core;
6-
use crate::core::offline::optimize_prompt_for_local;
7-
8-
use super::guidance::build_review_guidance;
9-
use super::services::PipelineServices;
10-
use super::session::ReviewSession;
11-
12-
pub(super) fn specialized_passes(config: &config::Config) -> Vec<core::SpecializedPassKind> {
13-
if !config.multi_pass_specialized {
14-
return Vec::new();
15-
}
16-
17-
let mut passes = vec![
18-
core::SpecializedPassKind::Security,
19-
core::SpecializedPassKind::Correctness,
20-
];
21-
if config.strictness >= 2 {
22-
passes.push(core::SpecializedPassKind::Style);
23-
}
24-
passes
25-
}
26-
27-
pub(super) fn build_review_request(
28-
services: &PipelineServices,
29-
session: &ReviewSession,
30-
diff: &core::UnifiedDiff,
31-
context_chunks: &[core::LLMContextChunk],
32-
path_config: Option<&config::PathConfig>,
33-
pass_kind: Option<core::SpecializedPassKind>,
34-
) -> Result<adapters::llm::LLMRequest> {
35-
let local_prompt_builder = core::PromptBuilder::new(build_prompt_config(
36-
services,
37-
session,
38-
path_config,
39-
pass_kind,
40-
));
41-
let (system_prompt, user_prompt) = local_prompt_builder.build_prompt(diff, context_chunks)?;
42-
43-
let (system_prompt, user_prompt) = if services.is_local {
44-
let context_window = services.config.context_window.unwrap_or(8192);
45-
optimize_prompt_for_local(&system_prompt, &user_prompt, context_window)
46-
} else {
47-
(system_prompt, user_prompt)
48-
};
49-
50-
Ok(adapters::llm::LLMRequest {
51-
system_prompt,
52-
user_prompt,
53-
temperature: None,
54-
max_tokens: None,
55-
response_schema: Some(review_comments_response_schema()),
56-
})
57-
}
58-
59-
fn build_prompt_config(
60-
services: &PipelineServices,
61-
session: &ReviewSession,
62-
path_config: Option<&config::PathConfig>,
63-
pass_kind: Option<core::SpecializedPassKind>,
64-
) -> core::prompt::PromptConfig {
65-
let mut local_prompt_config = services.base_prompt_config.clone();
66-
67-
if let Some(pass_kind) = pass_kind {
68-
local_prompt_config.system_prompt = pass_kind.system_prompt();
69-
70-
if !session.enhanced_guidance.is_empty() {
71-
local_prompt_config.system_prompt.push_str("\n\n");
72-
local_prompt_config
73-
.system_prompt
74-
.push_str(&session.enhanced_guidance);
75-
}
76-
if let Some(instructions) = session.auto_instructions.as_ref() {
77-
local_prompt_config
78-
.system_prompt
79-
.push_str("\n\n# Project-specific instructions (auto-detected):\n");
80-
local_prompt_config.system_prompt.push_str(instructions);
81-
}
82-
83-
return local_prompt_config;
84-
}
85-
86-
if let Some(custom_prompt) = &services.config.system_prompt {
87-
local_prompt_config.system_prompt = custom_prompt.clone();
88-
}
89-
if let Some(path_config) = path_config {
90-
if let Some(ref prompt) = path_config.system_prompt {
91-
local_prompt_config.system_prompt = prompt.clone();
92-
}
93-
}
94-
if let Some(guidance) = build_review_guidance(&services.config, path_config) {
95-
local_prompt_config.system_prompt.push_str("\n\n");
96-
local_prompt_config.system_prompt.push_str(&guidance);
97-
}
98-
if !session.enhanced_guidance.is_empty() {
99-
local_prompt_config.system_prompt.push_str("\n\n");
100-
local_prompt_config
101-
.system_prompt
102-
.push_str(&session.enhanced_guidance);
103-
}
104-
if !services.feedback_context.is_empty() {
105-
local_prompt_config.system_prompt.push_str("\n\n");
106-
local_prompt_config
107-
.system_prompt
108-
.push_str(&services.feedback_context);
109-
}
110-
if let Some(instructions) = session.auto_instructions.as_ref() {
111-
local_prompt_config
112-
.system_prompt
113-
.push_str("\n\n# Project-specific instructions (auto-detected):\n");
114-
local_prompt_config.system_prompt.push_str(instructions);
115-
}
116-
117-
local_prompt_config
118-
}
119-
120-
pub(super) fn review_comments_response_schema() -> adapters::llm::StructuredOutputSchema {
121-
adapters::llm::StructuredOutputSchema::json_schema(
122-
"review_findings",
123-
serde_json::json!({
124-
"type": "array",
125-
"items": {
126-
"type": "object",
127-
"additionalProperties": false,
128-
"required": ["line", "content", "severity", "category", "confidence", "fix_effort", "tags"],
129-
"properties": {
130-
"line": {"type": "integer", "minimum": 1},
131-
"content": {"type": "string"},
132-
"severity": {"type": "string", "enum": ["error", "warning", "info", "suggestion"]},
133-
"category": {"type": "string", "enum": ["bug", "security", "performance", "style", "best_practice"]},
134-
"confidence": {"type": ["number", "string"]},
135-
"fix_effort": {"type": "string", "enum": ["low", "medium", "high"]},
136-
"rule_id": {"type": ["string", "null"]},
137-
"suggestion": {"type": ["string", "null"]},
138-
"code_suggestion": {"type": ["string", "null"]},
139-
"tags": {
140-
"type": "array",
141-
"items": {"type": "string"}
142-
}
143-
}
144-
}
145-
}),
146-
)
147-
}
1+
#[path = "request/passes.rs"]
2+
mod passes;
3+
#[path = "request/prompt.rs"]
4+
mod prompt;
5+
#[path = "request/schema.rs"]
6+
mod schema;
7+
8+
pub(super) use passes::specialized_passes;
9+
pub(super) use prompt::build_review_request;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
use crate::config;
2+
use crate::core;
3+
4+
pub(in super::super) fn specialized_passes(
5+
config: &config::Config,
6+
) -> Vec<core::SpecializedPassKind> {
7+
if !config.multi_pass_specialized {
8+
return Vec::new();
9+
}
10+
11+
let mut passes = vec![
12+
core::SpecializedPassKind::Security,
13+
core::SpecializedPassKind::Correctness,
14+
];
15+
if config.strictness >= 2 {
16+
passes.push(core::SpecializedPassKind::Style);
17+
}
18+
passes
19+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
use anyhow::Result;
2+
3+
use crate::adapters;
4+
use crate::config;
5+
use crate::core;
6+
use crate::core::offline::optimize_prompt_for_local;
7+
8+
use super::super::guidance::build_review_guidance;
9+
use super::super::services::PipelineServices;
10+
use super::super::session::ReviewSession;
11+
12+
pub(in super::super) fn build_review_request(
13+
services: &PipelineServices,
14+
session: &ReviewSession,
15+
diff: &core::UnifiedDiff,
16+
context_chunks: &[core::LLMContextChunk],
17+
path_config: Option<&config::PathConfig>,
18+
pass_kind: Option<core::SpecializedPassKind>,
19+
) -> Result<adapters::llm::LLMRequest> {
20+
let local_prompt_builder = core::PromptBuilder::new(build_prompt_config(
21+
services,
22+
session,
23+
path_config,
24+
pass_kind,
25+
));
26+
let (system_prompt, user_prompt) = local_prompt_builder.build_prompt(diff, context_chunks)?;
27+
28+
let (system_prompt, user_prompt) = if services.is_local {
29+
let context_window = services.config.context_window.unwrap_or(8192);
30+
optimize_prompt_for_local(&system_prompt, &user_prompt, context_window)
31+
} else {
32+
(system_prompt, user_prompt)
33+
};
34+
35+
Ok(adapters::llm::LLMRequest {
36+
system_prompt,
37+
user_prompt,
38+
temperature: None,
39+
max_tokens: None,
40+
response_schema: Some(super::schema::review_comments_response_schema()),
41+
})
42+
}
43+
44+
fn build_prompt_config(
45+
services: &PipelineServices,
46+
session: &ReviewSession,
47+
path_config: Option<&config::PathConfig>,
48+
pass_kind: Option<core::SpecializedPassKind>,
49+
) -> core::prompt::PromptConfig {
50+
let mut local_prompt_config = services.base_prompt_config.clone();
51+
52+
if let Some(pass_kind) = pass_kind {
53+
local_prompt_config.system_prompt = pass_kind.system_prompt();
54+
55+
if !session.enhanced_guidance.is_empty() {
56+
local_prompt_config.system_prompt.push_str("\n\n");
57+
local_prompt_config
58+
.system_prompt
59+
.push_str(&session.enhanced_guidance);
60+
}
61+
if let Some(instructions) = session.auto_instructions.as_ref() {
62+
local_prompt_config
63+
.system_prompt
64+
.push_str("\n\n# Project-specific instructions (auto-detected):\n");
65+
local_prompt_config.system_prompt.push_str(instructions);
66+
}
67+
68+
return local_prompt_config;
69+
}
70+
71+
if let Some(custom_prompt) = &services.config.system_prompt {
72+
local_prompt_config.system_prompt = custom_prompt.clone();
73+
}
74+
if let Some(path_config) = path_config {
75+
if let Some(ref prompt) = path_config.system_prompt {
76+
local_prompt_config.system_prompt = prompt.clone();
77+
}
78+
}
79+
if let Some(guidance) = build_review_guidance(&services.config, path_config) {
80+
local_prompt_config.system_prompt.push_str("\n\n");
81+
local_prompt_config.system_prompt.push_str(&guidance);
82+
}
83+
if !session.enhanced_guidance.is_empty() {
84+
local_prompt_config.system_prompt.push_str("\n\n");
85+
local_prompt_config
86+
.system_prompt
87+
.push_str(&session.enhanced_guidance);
88+
}
89+
if !services.feedback_context.is_empty() {
90+
local_prompt_config.system_prompt.push_str("\n\n");
91+
local_prompt_config
92+
.system_prompt
93+
.push_str(&services.feedback_context);
94+
}
95+
if let Some(instructions) = session.auto_instructions.as_ref() {
96+
local_prompt_config
97+
.system_prompt
98+
.push_str("\n\n# Project-specific instructions (auto-detected):\n");
99+
local_prompt_config.system_prompt.push_str(instructions);
100+
}
101+
102+
local_prompt_config
103+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use crate::adapters;
2+
3+
pub(super) fn review_comments_response_schema() -> adapters::llm::StructuredOutputSchema {
4+
adapters::llm::StructuredOutputSchema::json_schema(
5+
"review_findings",
6+
serde_json::json!({
7+
"type": "array",
8+
"items": {
9+
"type": "object",
10+
"additionalProperties": false,
11+
"required": ["line", "content", "severity", "category", "confidence", "fix_effort", "tags"],
12+
"properties": {
13+
"line": {"type": "integer", "minimum": 1},
14+
"content": {"type": "string"},
15+
"severity": {"type": "string", "enum": ["error", "warning", "info", "suggestion"]},
16+
"category": {"type": "string", "enum": ["bug", "security", "performance", "style", "best_practice"]},
17+
"confidence": {"type": ["number", "string"]},
18+
"fix_effort": {"type": "string", "enum": ["low", "medium", "high"]},
19+
"rule_id": {"type": ["string", "null"]},
20+
"suggestion": {"type": ["string", "null"]},
21+
"code_suggestion": {"type": ["string", "null"]},
22+
"tags": {
23+
"type": "array",
24+
"items": {"type": "string"}
25+
}
26+
}
27+
}
28+
}),
29+
)
30+
}

0 commit comments

Comments
 (0)