143143
144144# =============================================================================
145145# 2. AI verdict — read every ai-review-* artifact
146+ #
147+ # Gate policy (severity-based): only HIGH/CRITICAL findings — or an explicit
148+ # Tier 3 (critical path / needs a human), or a review that couldn't run —
149+ # block the merge. MEDIUM/LOW findings are surfaced as non-blocking warnings
150+ # and do NOT stop auto-merge.
146151# =============================================================================
147152
148153declare -a ai_results=()
149154declare -i max_tier=1
155+ has_block_sev=false # any high/critical finding
156+ has_any_findings=false # any finding at all (for warning vs clean wording)
150157ai_findings_md=" "
151158
152159shopt -s nullglob
153160for d in " $ARTIFACTS_DIR " /ai-review-* /; do
154161 f=" ${d} result.json"
155162 [[ -f " $f " ]] || continue
156163 ai_results+=(" $f " )
164+
157165 tier=$( jq -r ' .tier // 2' " $f " )
158- if (( tier > max_tier )) ; then
159- max_tier=$tier
166+ (( tier > max_tier )) && max_tier=$tier
167+
168+ if jq -e ' [(.findings // [])[].severity // "" | ascii_downcase] | any(. == "high" or . == "critical")' " $f " > /dev/null 2>&1 ; then
169+ has_block_sev=true
170+ fi
171+ if jq -e ' ((.findings // []) | length) > 0' " $f " > /dev/null 2>&1 ; then
172+ has_any_findings=true
160173 fi
161174done
162175shopt -u nullglob
163176
177+ no_ai=false
164178if [[ ${# ai_results[@]} -eq 0 ]]; then
165- echo " ::warning::No AI review artifacts — treating as tier 2 fail-safe"
166- max_tier=2
179+ echo " ::warning::No AI review artifacts — fail-safe block "
180+ no_ai=true
167181fi
168182
169- # Build a markdown section per AI prompt result.
183+ # Final AI gate: block on a high/critical finding, an explicit Tier 3, or a
184+ # review that did not run. Tier 2 on its own (only medium/low) does NOT block.
185+ ai_blocked=false
186+ if $no_ai || $has_block_sev || (( max_tier >= 3 )) ; then
187+ ai_blocked=true
188+ fi
189+
190+ # Build a markdown section per AI prompt result, labelled by what it found
191+ # (blocking high/critical vs non-blocking warnings vs clean).
170192for f in " ${ai_results[@]} " ; do
171193 prompt=$( jq -r ' .prompt // "unknown"' " $f " )
172194 model=$( jq -r ' .model // "?"' " $f " )
173- tier=$( jq -r ' .tier // 2' " $f " )
174195 summary=$( jq -r ' .summary // "(no summary)"' " $f " )
196+ p_block=$( jq -r ' [(.findings // [])[].severity // "" | ascii_downcase] | any(. == "high" or . == "critical")' " $f " 2> /dev/null || echo false)
197+ p_count=$( jq -r ' (.findings // []) | length' " $f " 2> /dev/null || echo 0)
198+ p_tier=$( jq -r ' .tier // 2' " $f " )
175199 findings=$( jq -r '
176- .findings // [] |
200+ ( .findings // []) |
177201 if length == 0 then " _No findings._"
178202 else
179203 map(" - **\(.severity // "?")** `\(.file // "?"):\(.line // "?")` — \(.message // "")") | join("\n")
180204 end
181205 ' " $f " )
182- case " $tier " in
183- 1) icon=" ✅" label=" Tier 1 — looks clean" ;;
184- 2) icon=" ⚠️" label=" Tier 2 — changes requested" ;;
185- 3) icon=" 🛑" label=" Tier 3 — engineer review required" ;;
186- * ) icon=" ❓" label=" Tier ?" ;;
187- esac
206+ if [[ " $p_block " == " true" || " $p_tier " == " 3" ]]; then
207+ icon=" 🛑" label=" blocking — must fix before merge"
208+ elif (( p_count > 0 )) ; then
209+ icon=" ⚠️" label=" non-blocking warnings"
210+ else
211+ icon=" ✅" label=" clean"
212+ fi
188213 ai_findings_md+=$' \n ' " #### $icon \` $prompt \` (\` $model \` ) — $label " $' \n\n '
189214 ai_findings_md+=" **Summary:** $summary " $' \n\n '
190215 ai_findings_md+=" $findings " $' \n '
@@ -219,32 +244,30 @@ else
219244fi
220245
221246# --- AI verdict comment (always) ---
222- case " $max_tier " in
223- 1)
224- ai_header=" ### ✅ AI review — Approved"
225- ai_intro=" All prompts returned Tier 1. No blocking issues detected in this diff."
226- ;;
227- 2)
228- ai_header=" ### ⚠️ AI review — Changes requested"
229- ai_intro=" One or more prompts found issues the author should fix before merging. Details below."
230- ;;
231- 3)
232- mention=" "
233- if [[ -n " $TIER3_REVIEWERS " ]]; then
234- IFS=' ,' read -ra handles <<< " $TIER3_REVIEWERS"
235- for h in " ${handles[@]} " ; do
236- h=" $( echo " $h " | sed -e ' s/^[[:space:]]*//' -e ' s/[[:space:]]*$//' -e ' s/^@//' ) "
237- [[ -n " $h " ]] && mention+=" @$h "
238- done
239- fi
240- ai_header=" ### 🛑 AI review — Engineer review required"
241- ai_intro=" This PR touches critical paths or introduces changes the model cannot judge with sufficient confidence. ${mention} please review."
242- ;;
243- * )
244- ai_header=" ### ❓ AI review — Unknown verdict"
245- ai_intro=" The approver could not determine an overall tier."
246- ;;
247- esac
247+ if $no_ai ; then
248+ ai_header=" ### ❓ AI review — could not run"
249+ ai_intro=" No AI results were produced, so the merge is held for a human. Check the workflow logs."
250+ elif (( max_tier >= 3 )) ; then
251+ mention=" "
252+ if [[ -n " $TIER3_REVIEWERS " ]]; then
253+ IFS=' ,' read -ra handles <<< " $TIER3_REVIEWERS"
254+ for h in " ${handles[@]} " ; do
255+ h=" $( echo " $h " | sed -e ' s/^[[:space:]]*//' -e ' s/[[:space:]]*$//' -e ' s/^@//' ) "
256+ [[ -n " $h " ]] && mention+=" @$h "
257+ done
258+ fi
259+ ai_header=" ### 🛑 AI review — Engineer review required"
260+ ai_intro=" This PR touches critical paths or introduces changes the model cannot judge with sufficient confidence. ${mention} please review."
261+ elif $has_block_sev ; then
262+ ai_header=" ### 🛑 AI review — Blocking issues"
263+ ai_intro=" One or more high/critical issues can break things and must be fixed before merging. Details below."
264+ elif $has_any_findings ; then
265+ ai_header=" ### ✅ AI review — Approved with warnings"
266+ ai_intro=" Only minor (medium/low) issues were found. They won't block the merge, but consider addressing them."
267+ else
268+ ai_header=" ### ✅ AI review — Approved"
269+ ai_intro=" No issues detected in this diff."
270+ fi
248271
249272ai_body=$( cat << EOF
250273$ai_header
317340# =============================================================================
318341
319342if [[ -n " $APPROVER_TOKEN " || " $DRY_RUN " == " 1" ]]; then
320- if ! $deps_failed && [[ " $max_tier " == " 1 " ]] && $authorized ; then
343+ if ! $deps_failed && ! $ai_blocked && $authorized ; then
321344 review_event=" APPROVE"
322- review_body=" Approved by AI review (Tier 1 , deps OK, authorized author) ."
345+ review_body=" Approved — no blocking issues , deps OK, authorized author. Any non-blocking warnings are listed above ."
323346 elif ! $authorized ; then
324347 review_event=" REQUEST_CHANGES"
325348 review_body=" Author is not in @${ORG} /${ADMIN_TEAM} nor @${ORG} /${CORE_TEAM} — admin review required."
326- elif [[ " $max_tier " == " 3 " ]] || $deps_failed ; then
349+ elif $deps_failed ; then
327350 review_event=" REQUEST_CHANGES"
328- review_body=" Changes requested — see approver comments above."
351+ review_body=" Changes requested — Go dependencies check failed (see above) ."
329352 else
330353 review_event=" REQUEST_CHANGES"
331- review_body=" Changes requested by AI review (Tier 2) ."
354+ review_body=" Changes requested — AI review found blocking issues (high/critical, or engineer review required). See above ."
332355 fi
333356
334357 if [[ " $DRY_RUN " == " 1" ]]; then
359382# stays pending.
360383# =============================================================================
361384
362- if ! $deps_failed && [[ " $max_tier " == " 1 " ]] && $authorized ; then
385+ if ! $deps_failed && ! $ai_blocked && $authorized ; then
363386 if [[ " $BASE_REF " == release/* ]]; then
364387 if [[ " $DRY_RUN " == " 1" ]]; then
365388 echo " [DRY_RUN] Would enable auto-merge: gh pr merge $PR_NUMBER --auto --$MERGE_METHOD (base: $BASE_REF )"
383406
384407echo " "
385408echo " Summary:"
386- echo " deps_failed: $deps_failed "
387- echo " max_tier: $max_tier "
388- echo " authorized: $authorized "
389- echo " base_ref: $BASE_REF "
409+ echo " deps_failed: $deps_failed "
410+ echo " max_tier: $max_tier "
411+ echo " has_block_sev: $has_block_sev "
412+ echo " ai_blocked: $ai_blocked "
413+ echo " authorized: $authorized "
414+ echo " base_ref: $BASE_REF "
390415
391416if $deps_failed ; then
392417 exit 1
@@ -396,7 +421,7 @@ if ! $authorized; then
396421 exit 1
397422fi
398423
399- if [[ " $max_tier " -ge 2 ]] ; then
424+ if $ai_blocked ; then
400425 exit 1
401426fi
402427
0 commit comments