Initial commit: Compliance Scanner Agent
Autonomous security and compliance scanning agent for git repositories. Features: SAST (Semgrep), SBOM (Syft), CVE monitoring (OSV.dev/NVD), GDPR/OAuth pattern detection, LLM triage, issue creation (GitHub/GitLab/Jira), PR reviews, and Dioxus fullstack dashboard. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
77
compliance-agent/src/llm/pr_review.rs
Normal file
77
compliance-agent/src/llm/pr_review.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
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,
|
||||
}
|
||||
Reference in New Issue
Block a user