Skip to main content

jar_genesis/workflow/
review.rs

1use std::path::Path;
2
3use crate::cache;
4use crate::git;
5use crate::github;
6use crate::lean;
7use crate::review;
8use crate::snapshot;
9use crate::types::{MergeReadiness, SelectTargetsOutput};
10
11/// Run the review workflow: process a /review comment.
12pub fn run(
13    pr: u64,
14    comment_author: &str,
15    _comment_body: &str,
16) -> Result<(), Box<dyn std::error::Error>> {
17    // Guard against non-open PRs
18    let state_json = github::pr_view(pr, "state")?;
19    let state = state_json["state"].as_str().unwrap_or("");
20    if state != "OPEN" {
21        github::pr_comment(
22            pr,
23            &format!("**JAR Bot:** PR is not open (state: {state}) — ignoring `/review`."),
24        )?;
25        return Err(format!("PR #{pr} is not open (state: {state})").into());
26    }
27
28    let repo_root = git::repo_root()?;
29    let spec_dir = Path::new(&repo_root).join("spec");
30
31    // Fetch and read cache
32    git::fetch("origin", "genesis-state")?;
33    let cache_json = git::show_file("origin/genesis-state:genesis.json")?;
34    let cache_indices: Vec<serde_json::Value> = serde_json::from_str(&cache_json)?;
35
36    // Check cache staleness
37    if let Err(e) = cache::check_staleness(&cache_indices, &spec_dir) {
38        github::pr_comment(
39            pr,
40            &format!("**JAR Bot:** Genesis cache is stale — cannot process review. {e}"),
41        )?;
42        return Err(e.into());
43    }
44
45    // Get PR head SHA and created_at
46    let pr_json = github::pr_view(pr, "headRefOid,createdAt")?;
47    let head_sha = pr_json["headRefOid"].as_str().unwrap_or("").to_string();
48    let pr_created_at = pr_json["createdAt"].as_str().unwrap_or("");
49
50    let pr_created_epoch = {
51        let output = std::process::Command::new("date")
52            .args(["-d", pr_created_at, "+%s"])
53            .output()?;
54        String::from_utf8_lossy(&output.stdout)
55            .trim()
56            .parse::<u64>()?
57    };
58
59    // Get ranking + variances snapshot
60    let ranking_json =
61        git::show_file("origin/genesis-state:ranking.json").unwrap_or_else(|_| "{}".to_string());
62    let ranking_map: serde_json::Value = serde_json::from_str(&ranking_json)?;
63    let scores_json =
64        git::show_file("origin/genesis-state:scores.json").unwrap_or_else(|_| "{}".to_string());
65    let scores_map: serde_json::Value = serde_json::from_str(&scores_json)?;
66
67    let snap = snapshot::find(&cache_indices, &ranking_map, &scores_map, pr_created_epoch)?;
68
69    // Compute comparison targets
70    let mut targets_input = serde_json::json!({
71        "prId": pr,
72        "prCreatedAt": pr_created_epoch,
73        "indices": cache_indices,
74    });
75    if let Some(s) = &snap {
76        targets_input["ranking"] = s.ranking.clone();
77        if let Some(v) = &s.variances {
78            targets_input["variances"] = v.clone();
79        }
80    }
81
82    let targets_output: SelectTargetsOutput =
83        lean::invoke("genesis_select_targets", &targets_input, &spec_dir)?;
84    let targets = targets_output.targets;
85
86    // Collect all reviews (not just this one — we need the full set for quorum check)
87    let collected = review::collect(pr, &head_sha, &targets)?;
88
89    // Check merge readiness
90    let check_input = serde_json::json!({
91        "reviews": collected.reviews,
92        "metaReviews": collected.meta_reviews,
93        "indices": cache_indices,
94    });
95    let readiness: MergeReadiness = lean::invoke("genesis_check_merge", &check_input, &spec_dir)?;
96
97    let review_count = collected.reviews.len();
98    let meta_count = collected.meta_reviews.len();
99
100    if readiness.ready {
101        // Quorum reached — trigger merge workflow
102        github::workflow_run("genesis-merge.yml", &[("pr_number", &pr.to_string())])?;
103
104        github::pr_comment(
105            pr,
106            &format!(
107                "**JAR Bot:** Quorum reached — triggering merge.\n\
108             Reviews: {review_count}, meta-reviews: {meta_count}.\n\
109             Merge weight: {}/{} (>50%).",
110                readiness.merge_weight, readiness.total_weight
111            ),
112        )?;
113    } else {
114        github::pr_comment(
115            pr,
116            &format!(
117                "**JAR Bot:** Review recorded from @{comment_author} ({review_count} reviews, {meta_count} meta-reviews).\n\
118             Merge weight: {}/{} (need >50%).",
119                readiness.merge_weight, readiness.total_weight
120            ),
121        )?;
122    }
123
124    Ok(())
125}