Codex Handbook
prompts/src/review_request.rs 137 lines
use codex_git_utils::merge_base_with_head;use codex_protocol::protocol::ReviewRequest;use codex_protocol::protocol::ReviewTarget;use codex_utils_absolute_path::AbsolutePathBuf;use codex_utils_template::Template;use std::sync::LazyLock;/// Review thread system prompt.pub const REVIEW_PROMPT: &str = include_str!("../templates/review/rubric.md");#[derive(Clone, Debug, PartialEq)]pub struct ResolvedReviewRequest {    pub target: ReviewTarget,    pub prompt: String,    pub user_facing_hint: String,}const UNCOMMITTED_PROMPT: &str = "Review the current code changes (staged, unstaged, and untracked files) and provide prioritized findings.";const BASE_BRANCH_PROMPT_BACKUP: &str = "Review the code changes against the base branch '{{branch}}'. Start by finding the merge diff between the current branch and {{branch}}'s upstream e.g. (`git merge-base HEAD \"$(git rev-parse --abbrev-ref \"{{branch}}@{upstream}\")\"`), then run `git diff` against that SHA to see what changes we would merge into the {{branch}} branch. Provide prioritized, actionable findings.";const BASE_BRANCH_PROMPT: &str = "Review the code changes against the base branch '{{base_branch}}'. The merge base commit for this comparison is {{merge_base_sha}}. Run `git diff {{merge_base_sha}}` to inspect the changes relative to {{base_branch}}. Provide prioritized, actionable findings.";static BASE_BRANCH_PROMPT_BACKUP_TEMPLATE: LazyLock<Template> = LazyLock::new(|| {    Template::parse(BASE_BRANCH_PROMPT_BACKUP)        .unwrap_or_else(|err| panic!("base branch backup review prompt must parse: {err}"))});static BASE_BRANCH_PROMPT_TEMPLATE: LazyLock<Template> = LazyLock::new(|| {    Template::parse(BASE_BRANCH_PROMPT)        .unwrap_or_else(|err| panic!("base branch review prompt must parse: {err}"))});const COMMIT_PROMPT_WITH_TITLE: &str = "Review the code changes introduced by commit {{sha}} (\"{{title}}\"). Provide prioritized, actionable findings.";const COMMIT_PROMPT: &str = "Review the code changes introduced by commit {{sha}}. Provide prioritized, actionable findings.";static COMMIT_PROMPT_WITH_TITLE_TEMPLATE: LazyLock<Template> = LazyLock::new(|| {    Template::parse(COMMIT_PROMPT_WITH_TITLE)        .unwrap_or_else(|err| panic!("commit review prompt with title must parse: {err}"))});static COMMIT_PROMPT_TEMPLATE: LazyLock<Template> = LazyLock::new(|| {    Template::parse(COMMIT_PROMPT)        .unwrap_or_else(|err| panic!("commit review prompt must parse: {err}"))});pub fn resolve_review_request(    request: ReviewRequest,    cwd: &AbsolutePathBuf,) -> anyhow::Result<ResolvedReviewRequest> {    let target = request.target;    let prompt = review_prompt(&target, cwd)?;    let user_facing_hint = request        .user_facing_hint        .unwrap_or_else(|| user_facing_hint(&target));    Ok(ResolvedReviewRequest {        target,        prompt,        user_facing_hint,    })}pub fn review_prompt(target: &ReviewTarget, cwd: &AbsolutePathBuf) -> anyhow::Result<String> {    match target {        ReviewTarget::UncommittedChanges => Ok(UNCOMMITTED_PROMPT.to_string()),        ReviewTarget::BaseBranch { branch } => {            if let Some(commit) = merge_base_with_head(cwd, branch)? {                Ok(render_review_prompt(                    &BASE_BRANCH_PROMPT_TEMPLATE,                    [                        ("base_branch", branch.as_str()),                        ("merge_base_sha", commit.as_str()),                    ],                ))            } else {                Ok(render_review_prompt(                    &BASE_BRANCH_PROMPT_BACKUP_TEMPLATE,                    [("branch", branch.as_str())],                ))            }        }        ReviewTarget::Commit { sha, title } => {            if let Some(title) = title {                Ok(render_review_prompt(                    &COMMIT_PROMPT_WITH_TITLE_TEMPLATE,                    [("sha", sha.as_str()), ("title", title.as_str())],                ))            } else {                Ok(render_review_prompt(                    &COMMIT_PROMPT_TEMPLATE,                    [("sha", sha.as_str())],                ))            }        }        ReviewTarget::Custom { instructions } => {            let prompt = instructions.trim();            if prompt.is_empty() {                anyhow::bail!("Review prompt cannot be empty");            }            Ok(prompt.to_string())        }    }}fn render_review_prompt<'a, const N: usize>(    template: &Template,    variables: [(&'a str, &'a str); N],) -> String {    template        .render(variables)        .unwrap_or_else(|err| panic!("review prompt template must render: {err}"))}pub fn user_facing_hint(target: &ReviewTarget) -> String {    match target {        ReviewTarget::UncommittedChanges => "current changes".to_string(),        ReviewTarget::BaseBranch { branch } => format!("changes against '{branch}'"),        ReviewTarget::Commit { sha, title } => {            let short_sha: String = sha.chars().take(7).collect();            if let Some(title) = title {                format!("commit {short_sha}: {title}")            } else {                format!("commit {short_sha}")            }        }        ReviewTarget::Custom { instructions } => instructions.trim().to_string(),    }}impl From<ResolvedReviewRequest> for ReviewRequest {    fn from(resolved: ResolvedReviewRequest) -> Self {        ReviewRequest {            target: resolved.target,            user_facing_hint: Some(resolved.user_facing_hint),        }    }}#[cfg(test)]#[path = "review_request_tests.rs"]mod review_request_tests;