- 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>
91 lines
2.7 KiB
Rust
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,
|
|
}
|