Files
compliance-scanner-agent/compliance-agent/src/llm/pr_review.rs
Sharang Parnerkar 03ee69834d
All checks were successful
CI / Format (push) Successful in 3s
CI / Clippy (push) Successful in 2m15s
CI / Security Audit (push) Successful in 1m34s
CI / Tests (push) Successful in 3m4s
Fix formatting and clippy warnings across workspace
- Run cargo fmt on all crates
- Fix regex patterns using unsupported lookahead in patterns.rs
- Replace unwrap() calls with compile_regex() helper
- Fix never type fallback in GitHub tracker
- Fix redundant field name in findings page
- Allow enum_variant_names for Dioxus Route enum
- Fix &mut Vec -> &mut [T] clippy lint in sbom.rs
- Mark unused-but-intended APIs with #[allow(dead_code)]

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 17:41:03 +01:00

91 lines
2.7 KiB
Rust

use std::sync::Arc;
use compliance_core::models::Finding;
use compliance_core::traits::issue_tracker::ReviewComment;
use crate::error::AgentError;
use crate::llm::LlmClient;
const PR_REVIEW_SYSTEM_PROMPT: &str = r#"You are a security-focused code reviewer. Given a list of security findings in a PR diff, generate concise review comments. Each comment should:
1. Briefly explain the issue
2. Suggest a specific fix
3. Reference the relevant security standard (CWE, OWASP) if applicable
Be constructive and professional. Return JSON array:
[{"path": "file.rs", "line": 42, "body": "..."}]"#;
pub async fn generate_pr_review(
llm: &Arc<LlmClient>,
findings: &[Finding],
) -> Result<(String, Vec<ReviewComment>), AgentError> {
if findings.is_empty() {
return Ok((
"No security issues found in this PR.".to_string(),
Vec::new(),
));
}
let findings_text: Vec<String> = findings
.iter()
.map(|f| {
format!(
"- [{severity}] {title} in {file}:{line}\n Code: {code}\n Rule: {rule}",
severity = f.severity,
title = f.title,
file = f.file_path.as_deref().unwrap_or("unknown"),
line = f
.line_number
.map(|n| n.to_string())
.unwrap_or_else(|| "?".to_string()),
code = f.code_snippet.as_deref().unwrap_or("N/A"),
rule = f.rule_id.as_deref().unwrap_or("N/A"),
)
})
.collect();
let user_prompt = format!(
"Generate review comments for these {} findings:\n{}",
findings.len(),
findings_text.join("\n"),
);
let response = llm
.chat(PR_REVIEW_SYSTEM_PROMPT, &user_prompt, Some(0.3))
.await?;
// Parse comments from LLM response
let comments: Vec<ReviewComment> = serde_json::from_str::<Vec<PrComment>>(&response)
.unwrap_or_default()
.into_iter()
.map(|c| ReviewComment {
path: c.path,
line: c.line,
body: c.body,
})
.collect();
let summary = format!(
"## Security Review\n\nFound **{}** potential security issue(s) in this PR.\n\n{}",
findings.len(),
findings
.iter()
.map(|f| format!(
"- **[{}]** {} in `{}`",
f.severity,
f.title,
f.file_path.as_deref().unwrap_or("unknown")
))
.collect::<Vec<_>>()
.join("\n"),
);
Ok((summary, comments))
}
#[derive(serde::Deserialize)]
struct PrComment {
path: String,
line: u32,
body: String,
}