use std::sync::Arc;
use tracing::warn;
use crate::integrations::github::{
AuthStrategy, CommentableLines, GithubClient, GithubError, RunMode, build_inline_plan,
fetch_pr_metadata,
};
use crate::{
config::ReviewConfig,
models::{InlineCommentOut, ReviewResult, Verdict},
pipeline::{
grade::derive_verdict_with_grade,
letter_grade::default_grade_for_verdict,
output::{print_review_result, write_review_log},
post::{PostContext, finalize_review},
prompt::ReviewPrMeta,
},
store::DedupStore,
};
use super::runner::{ReviewDeps, ReviewInput};
pub(super) fn apply_grade_and_floor(
parsed: &crate::pipeline::parser::ParsedReview,
) -> (Verdict, crate::pipeline::letter_grade::Grade) {
if parsed.is_fail_safe {
let v = parsed.verdict.clone();
let g = default_grade_for_verdict(&v);
return (v, g);
}
let grade = parsed
.grade
.as_deref()
.and_then(|s| s.parse().ok())
.unwrap_or_else(|| {
let g = default_grade_for_verdict(&parsed.verdict);
warn!(
verdict = %parsed.verdict,
default_grade = %g,
"LLM grade absent or unparseable — using default for verdict"
);
g
});
derive_verdict_with_grade(parsed.verdict.clone(), grade, &parsed.findings)
}
pub(super) async fn fetch_github_pr_meta(
config: &ReviewConfig,
owner: &str,
repo: &str,
pr: u64,
run_mode: RunMode,
) -> Result<(ReviewPrMeta, String), GithubError> {
let client = GithubClient::new()?;
let token = AuthStrategy::select(run_mode, None)
.resolve_token(&client, config, owner)
.await?;
let meta = fetch_pr_metadata(&client, owner, repo, pr, &token).await?;
let head_sha = meta.head.sha.clone();
Ok((
ReviewPrMeta {
title: meta.title,
body: meta.body.unwrap_or_default(),
author: meta.user.login,
url: meta.html_url,
},
head_sha,
))
}
pub(super) fn abort_dry(
mut result: ReviewResult,
config: &ReviewConfig,
input: &ReviewInput,
deps: &ReviewDeps,
) -> ReviewResult {
result.dry_run = true;
if !result.head_sha.is_empty()
&& let Some(store) = deps.dedup.as_ref()
&& let Err(e) = store.release(
&result.owner,
&result.repo,
result.pr_number,
&result.head_sha,
)
{
warn!("dedup release() after abort failed (non-fatal): {e}");
}
if input.write_log {
write_review_log(&result, &config.log_dir);
}
if input.print_result {
print_review_result(&result);
}
result
}
pub(super) fn attach_inline_comments(result: &mut ReviewResult, raw_diff: &str) {
if result.findings.is_empty() {
return;
}
let commentable = CommentableLines::from_unified_diff(raw_diff);
let plan = build_inline_plan(&result.findings, &commentable);
result.suppressed_nits = plan.suppressed_nits;
result.inline_finding_indices = plan.inline_indices;
result.inline_comments = plan
.comments
.into_iter()
.map(|c| InlineCommentOut {
path: c.path,
line: c.line,
body: c.body,
})
.collect();
}
pub(super) async fn finalize_run(
result: ReviewResult,
config: &ReviewConfig,
input: &ReviewInput,
dedup: Option<&Arc<DedupStore>>,
) -> ReviewResult {
let owner = result.owner.clone();
let repo = result.repo.clone();
let pr = result.pr_number;
let head_sha = result.head_sha.clone();
let post_ctx = PostContext {
owner: &owner,
repo: &repo,
pr,
head_sha: &head_sha,
run_mode: input.run_mode,
dedup,
};
finalize_review(
result,
config,
input.trigger,
input.allow_posting,
input.write_log,
input.print_result,
post_ctx,
)
.await
}