set_review_mode() { local mode="$1" # Security: shell-escape values to prevent command injection when sourced. printf '%s=%q\n' \ REVIEW_MODE "$mode" \ REVIEW_MODE_SET_AT "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ > .local/review-mode.env } review_claim() { local pr="$1" local root root=$(repo_root) cd "$root" mkdir -p .local local reviewer="" local max_attempts=3 local attempt for attempt in $(seq 1 "$max_attempts"); do local user_log user_log=".local/review-claim-user-attempt-$attempt.log" if reviewer=$(gh api user --jq .login 2>"$user_log"); then printf "%s\n" "$reviewer" >"$user_log" break fi echo "Claim reviewer lookup failed (attempt $attempt/$max_attempts)." print_relevant_log_excerpt "$user_log" if [ "$attempt" -lt "$max_attempts" ]; then sleep 2 fi done if [ -z "$reviewer" ]; then echo "Failed to resolve reviewer login after $max_attempts attempts." return 1 fi for attempt in $(seq 1 "$max_attempts"); do local claim_log claim_log=".local/review-claim-assignee-attempt-$attempt.log" if gh pr edit "$pr" --add-assignee "$reviewer" >"$claim_log" 2>&1; then echo "review claim succeeded: @$reviewer assigned to PR #$pr" return 0 fi echo "Claim assignee update failed (attempt $attempt/$max_attempts)." print_relevant_log_excerpt "$claim_log" if [ "$attempt" -lt "$max_attempts" ]; then sleep 2 fi done echo "Failed to assign @$reviewer to PR #$pr after $max_attempts attempts." return 1 } review_checkout_main() { local pr="$1" enter_worktree "$pr" false git fetch origin main git checkout --detach origin/main set_review_mode main echo "review mode set to main baseline" echo "branch=$(git branch --show-current)" echo "head=$(git rev-parse --short HEAD)" } review_checkout_pr() { local pr="$1" enter_worktree "$pr" false git fetch origin "pull/$pr/head:pr-$pr" --force git checkout --detach "pr-$pr" set_review_mode pr echo "review mode set to PR head" echo "branch=$(git branch --show-current)" echo "head=$(git rev-parse --short HEAD)" } review_guard() { local pr="$1" enter_worktree "$pr" false require_artifact .local/review-mode.env require_artifact .local/pr-meta.env # shellcheck disable=SC1091 source .local/review-mode.env # shellcheck disable=SC1091 source .local/pr-meta.env local branch branch=$(git branch --show-current) local head_sha head_sha=$(git rev-parse HEAD) case "${REVIEW_MODE:-}" in main) local expected_main_sha expected_main_sha=$(git rev-parse origin/main) if [ "$head_sha" != "$expected_main_sha" ]; then echo "Review guard failed: expected HEAD at origin/main ($expected_main_sha) for main baseline mode, got $head_sha" exit 1 fi ;; pr) if [ -z "${PR_HEAD_SHA:-}" ]; then echo "Review guard failed: missing PR_HEAD_SHA in .local/pr-meta.env" exit 1 fi if [ "$head_sha" != "$PR_HEAD_SHA" ]; then echo "Review guard failed: expected HEAD at PR_HEAD_SHA ($PR_HEAD_SHA), got $head_sha" exit 1 fi ;; *) echo "Review guard failed: unknown review mode '${REVIEW_MODE:-}'" exit 1 ;; esac echo "review guard passed" echo "mode=$REVIEW_MODE" echo "branch=$branch" echo "head=$head_sha" } review_artifacts_init() { local pr="$1" enter_worktree "$pr" false require_artifact .local/pr-meta.env if [ ! -f .local/review.md ]; then cat > .local/review.md <<'EOF_MD' A) TL;DR recommendation B) What changed and what is good? C) Security findings D) What is the PR intent? Is this the most optimal implementation? E) Concerns or questions (actionable) F) Tests G) Docs status H) Changelog I) Follow ups (optional) J) Suggested PR comment (optional) EOF_MD fi if [ ! -f .local/review.json ]; then cat > .local/review.json <<'EOF_JSON' { "recommendation": "NEEDS WORK", "findings": [], "nitSweep": { "performed": true, "status": "none", "summary": "No optional nits identified." }, "behavioralSweep": { "performed": true, "status": "not_applicable", "summary": "No runtime branch-level behavior changes require sweep evidence.", "silentDropRisk": "none", "branches": [] }, "issueValidation": { "performed": true, "source": "pr_body", "status": "unclear", "summary": "Review not completed yet." }, "tests": { "ran": [], "gaps": [], "result": "pass" }, "docs": "not_applicable", "changelog": "not_required" } EOF_JSON fi echo "review artifact templates are ready" echo "files=.local/review.md .local/review.json" } review_validate_artifacts() { local pr="$1" enter_worktree "$pr" false require_artifact .local/review.md require_artifact .local/review.json require_artifact .local/pr-meta.env require_artifact .local/pr-meta.json review_guard "$pr" jq . .local/review.json >/dev/null local section for section in "A)" "B)" "C)" "D)" "E)" "F)" "G)" "H)" "I)" "J)"; do awk -v s="$section" 'index($0, s) == 1 { found=1; exit } END { exit(found ? 0 : 1) }' .local/review.md || { echo "Missing section header in .local/review.md: $section" exit 1 } done local recommendation recommendation=$(jq -r '.recommendation // ""' .local/review.json) case "$recommendation" in "READY FOR /prepare-pr"|"NEEDS WORK"|"NEEDS DISCUSSION"|"NOT USEFUL (CLOSE)") ;; *) echo "Invalid recommendation in .local/review.json: $recommendation" exit 1 ;; esac local invalid_severity_count invalid_severity_count=$(jq '[.findings[]? | select((.severity // "") != "BLOCKER" and (.severity // "") != "IMPORTANT" and (.severity // "") != "NIT")] | length' .local/review.json) if [ "$invalid_severity_count" -gt 0 ]; then echo "Invalid finding severity in .local/review.json" exit 1 fi local invalid_findings_count invalid_findings_count=$(jq '[.findings[]? | select((.id|type)!="string" or (.title|type)!="string" or (.area|type)!="string" or (.fix|type)!="string")] | length' .local/review.json) if [ "$invalid_findings_count" -gt 0 ]; then echo "Invalid finding shape in .local/review.json (id/title/area/fix must be strings)" exit 1 fi local nit_findings_count nit_findings_count=$(jq '[.findings[]? | select((.severity // "") == "NIT")] | length' .local/review.json) local nit_sweep_performed nit_sweep_performed=$(jq -r '.nitSweep.performed // empty' .local/review.json) if [ "$nit_sweep_performed" != "true" ]; then echo "Invalid nit sweep in .local/review.json: nitSweep.performed must be true" exit 1 fi local nit_sweep_status nit_sweep_status=$(jq -r '.nitSweep.status // ""' .local/review.json) case "$nit_sweep_status" in "none") if [ "$nit_findings_count" -gt 0 ]; then echo "Invalid nit sweep in .local/review.json: nitSweep.status is none but NIT findings exist" exit 1 fi ;; "has_nits") if [ "$nit_findings_count" -lt 1 ]; then echo "Invalid nit sweep in .local/review.json: nitSweep.status is has_nits but no NIT findings exist" exit 1 fi ;; *) echo "Invalid nit sweep status in .local/review.json: $nit_sweep_status" exit 1 ;; esac local invalid_nit_summary_count invalid_nit_summary_count=$(jq '[.nitSweep.summary | select((type != "string") or (gsub("^\\s+|\\s+$";"") | length == 0))] | length' .local/review.json) if [ "$invalid_nit_summary_count" -gt 0 ]; then echo "Invalid nit sweep summary in .local/review.json: nitSweep.summary must be a non-empty string" exit 1 fi local issue_validation_performed issue_validation_performed=$(jq -r '.issueValidation.performed // empty' .local/review.json) if [ "$issue_validation_performed" != "true" ]; then echo "Invalid issue validation in .local/review.json: issueValidation.performed must be true" exit 1 fi local issue_validation_source issue_validation_source=$(jq -r '.issueValidation.source // ""' .local/review.json) case "$issue_validation_source" in "linked_issue"|"pr_body"|"both") ;; *) echo "Invalid issue validation source in .local/review.json: $issue_validation_source" exit 1 ;; esac local issue_validation_status issue_validation_status=$(jq -r '.issueValidation.status // ""' .local/review.json) case "$issue_validation_status" in "valid"|"unclear"|"invalid"|"already_fixed_on_main") ;; *) echo "Invalid issue validation status in .local/review.json: $issue_validation_status" exit 1 ;; esac local invalid_issue_summary_count invalid_issue_summary_count=$(jq '[.issueValidation.summary | select((type != "string") or (gsub("^\\s+|\\s+$";"") | length == 0))] | length' .local/review.json) if [ "$invalid_issue_summary_count" -gt 0 ]; then echo "Invalid issue validation summary in .local/review.json: issueValidation.summary must be a non-empty string" exit 1 fi local runtime_file_count runtime_file_count=$(jq '[.files[]? | (.path // "") | select(test("^(src|extensions|apps)/")) | select(test("(^|/)__tests__/|\\.test\\.|\\.spec\\.") | not) | select(test("\\.(md|mdx)$") | not)] | length' .local/pr-meta.json) local runtime_review_required="false" if [ "$runtime_file_count" -gt 0 ]; then runtime_review_required="true" fi local behavioral_sweep_performed behavioral_sweep_performed=$(jq -r '.behavioralSweep.performed // empty' .local/review.json) if [ "$behavioral_sweep_performed" != "true" ]; then echo "Invalid behavioral sweep in .local/review.json: behavioralSweep.performed must be true" exit 1 fi local behavioral_sweep_status behavioral_sweep_status=$(jq -r '.behavioralSweep.status // ""' .local/review.json) case "$behavioral_sweep_status" in "pass"|"needs_work"|"not_applicable") ;; *) echo "Invalid behavioral sweep status in .local/review.json: $behavioral_sweep_status" exit 1 ;; esac local behavioral_sweep_risk behavioral_sweep_risk=$(jq -r '.behavioralSweep.silentDropRisk // ""' .local/review.json) case "$behavioral_sweep_risk" in "none"|"present"|"unknown") ;; *) echo "Invalid behavioral sweep risk in .local/review.json: $behavioral_sweep_risk" exit 1 ;; esac local invalid_behavioral_summary_count invalid_behavioral_summary_count=$(jq '[.behavioralSweep.summary | select((type != "string") or (gsub("^\\s+|\\s+$";"") | length == 0))] | length' .local/review.json) if [ "$invalid_behavioral_summary_count" -gt 0 ]; then echo "Invalid behavioral sweep summary in .local/review.json: behavioralSweep.summary must be a non-empty string" exit 1 fi local behavioral_branches_is_array behavioral_branches_is_array=$(jq -r 'if (.behavioralSweep.branches | type) == "array" then "true" else "false" end' .local/review.json) if [ "$behavioral_branches_is_array" != "true" ]; then echo "Invalid behavioral sweep in .local/review.json: behavioralSweep.branches must be an array" exit 1 fi local invalid_behavioral_branch_count invalid_behavioral_branch_count=$(jq '[.behavioralSweep.branches[]? | select((.path|type)!="string" or (.decision|type)!="string" or (.outcome|type)!="string")] | length' .local/review.json) if [ "$invalid_behavioral_branch_count" -gt 0 ]; then echo "Invalid behavioral sweep branch entry in .local/review.json: each branch needs string path/decision/outcome" exit 1 fi local behavioral_branch_count behavioral_branch_count=$(jq '[.behavioralSweep.branches[]?] | length' .local/review.json) if [ "$runtime_review_required" = "true" ] && [ "$behavioral_sweep_status" = "not_applicable" ]; then echo "Invalid behavioral sweep in .local/review.json: runtime file changes require behavioralSweep.status=pass|needs_work" exit 1 fi if [ "$runtime_review_required" = "true" ] && [ "$behavioral_branch_count" -lt 1 ]; then echo "Invalid behavioral sweep in .local/review.json: runtime file changes require at least one branch entry" exit 1 fi if [ "$behavioral_sweep_status" = "not_applicable" ] && [ "$behavioral_branch_count" -gt 0 ]; then echo "Invalid behavioral sweep in .local/review.json: not_applicable cannot include branch entries" exit 1 fi if [ "$behavioral_sweep_status" = "pass" ] && [ "$behavioral_sweep_risk" != "none" ]; then echo "Invalid behavioral sweep in .local/review.json: status=pass requires silentDropRisk=none" exit 1 fi if [ "$recommendation" = "READY FOR /prepare-pr" ] && [ "$issue_validation_status" != "valid" ]; then echo "Invalid recommendation in .local/review.json: READY FOR /prepare-pr requires issueValidation.status=valid" exit 1 fi if [ "$recommendation" = "READY FOR /prepare-pr" ] && [ "$behavioral_sweep_status" = "needs_work" ]; then echo "Invalid recommendation in .local/review.json: READY FOR /prepare-pr requires behavioralSweep.status!=needs_work" exit 1 fi if [ "$recommendation" = "READY FOR /prepare-pr" ] && [ "$runtime_review_required" = "true" ] && [ "$behavioral_sweep_status" != "pass" ]; then echo "Invalid recommendation in .local/review.json: READY FOR /prepare-pr on runtime changes requires behavioralSweep.status=pass" exit 1 fi if [ "$recommendation" = "READY FOR /prepare-pr" ] && [ "$behavioral_sweep_risk" = "present" ]; then echo "Invalid recommendation in .local/review.json: READY FOR /prepare-pr is not allowed when behavioralSweep.silentDropRisk=present" exit 1 fi local docs_status docs_status=$(jq -r '.docs // ""' .local/review.json) case "$docs_status" in "up_to_date"|"missing"|"not_applicable") ;; *) echo "Invalid docs status in .local/review.json: $docs_status" exit 1 ;; esac local changelog_status changelog_status=$(jq -r '.changelog // ""' .local/review.json) case "$changelog_status" in "required"|"not_required") ;; *) echo "Invalid changelog status in .local/review.json: $changelog_status (must be \"required\" or \"not_required\")" exit 1 ;; esac echo "review artifacts validated" print_review_stdout_summary } review_tests() { local pr="$1" shift if [ "$#" -lt 1 ]; then echo "Usage: scripts/pr review-tests [ ...]" exit 2 fi enter_worktree "$pr" false review_guard "$pr" local target for target in "$@"; do if [ ! -f "$target" ]; then echo "Missing test target file: $target" exit 1 fi done bootstrap_deps_if_needed local run_log=".local/review-tests-run.log" run_quiet_logged "pnpm test" "$run_log" pnpm test -- "$@" local missing_run=() for target in "$@"; do local base base=$(basename "$target") if ! rg -F -q "$target" "$run_log" && ! rg -F -q "$base" "$run_log"; then missing_run+=("$target") fi done if [ "${#missing_run[@]}" -gt 0 ]; then echo "These requested targets were not observed in vitest run output:" printf ' - %s\n' "${missing_run[@]}" exit 1 fi # Security: shell-escape values to prevent command injection when sourced. printf '%s=%q\n' \ REVIEW_TESTS_AT "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ REVIEW_TEST_TARGET_COUNT "$#" \ > .local/review-tests.env echo "review tests passed and were observed in output" } review_init() { local pr="$1" enter_worktree "$pr" true local json json=$(pr_meta_json "$pr") write_pr_meta_files "$json" git fetch origin "pull/$pr/head:pr-$pr" --force local mb mb=$(git merge-base origin/main "pr-$pr") # Security: shell-escape values to prevent command injection when sourced. printf '%s=%q\n' \ PR_NUMBER "$pr" \ MERGE_BASE "$mb" \ REVIEW_STARTED_AT "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ > .local/review-context.env set_review_mode main printf '%s\n' "$json" | jq '{number,title,url,state,isDraft,author:.author.login,base:.baseRefName,head:.headRefName,headSha:.headRefOid,headRepo:.headRepository.nameWithOwner,additions,deletions,files:(.files|length)}' echo "worktree=$PWD" echo "merge_base=$mb" echo "branch=$(git branch --show-current)" echo "wrote=.local/pr-meta.json .local/pr-meta.env .local/review-context.env .local/review-mode.env" cat <