项目文件夹

文件
wehub-resource-sync 7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

156 行
7.5 KiB
Bash
可执行文件

#!/usr/bin/env bash
#
# Combine multiple per-model AI code reviews into a single sticky PR comment.
#
# This is the pure-assembly half of the AI Code Reviewer workflow
# (.github/workflows/ai-code-reviewer.yml), split out so the non-trivial parts —
# header/footer stripping, "Reviewer N" section assembly, label union, and the
# pass/fail aggregation — can be unit-tested without any network or GitHub API
# access. The workflow runs the models (network) and writes each reviewer's raw
# stdout to resp_<i>.json and its exit code to code_<i>; this script turns those
# into the comment body, label set, decision, and success count.
#
# Usage:
# combine-ai-reviews.sh <work_dir> <model_1> [<model_2> ...]
#
# Reads, for i in 0..N-1 (N = number of model args):
# <work_dir>/resp_<i>.json - raw stdout from ai-reviewer.sh for reviewer i
# <work_dir>/code_<i> - that reviewer's integer exit code
# <work_dir>/err_<i>.log - that reviewer's stderr (only shown in DEBUG)
# The model names are used only for the per-reviewer log-group labels; the
# posted comment stays anonymized ("Reviewer 1", "Reviewer 2", ...).
#
# Environment:
# HEAD_SHA - head commit sha for the "Last reviewed at commit" line (opt)
# DEBUG_MODE - "true" to echo raw responses + stderr to this script's stderr
#
# Writes (into <work_dir>):
# comment_body.md - the combined sticky-comment body
# labels.txt - deduped labels, one per line (may be empty)
# decision.txt - "pass" or "fail"
# success_count.txt - number of reviewers that produced a usable review
#
# Logs ::group:: sections and debug output to stderr (kept off stdout so the
# output files are the only contract). Exit status is 0 even when every
# reviewer failed — the caller inspects success_count.txt and decides whether
# to fail the workflow. A non-zero exit means a usage error only.
set -euo pipefail
WORK_DIR="${1:-}"
if [ -z "$WORK_DIR" ] || [ ! -d "$WORK_DIR" ]; then
echo "usage: $0 <work_dir> <model> [<model> ...]" >&2
exit 2
fi
shift
MODELS=("$@")
if [ "${#MODELS[@]}" -eq 0 ]; then
echo "error: at least one model name is required" >&2
exit 2
fi
DEBUG_MODE="${DEBUG_MODE:-false}"
HEAD_SHA="${HEAD_SHA:-}"
COMBINED_REVIEW=""
ALL_LABELS=""
ANY_FAIL="false"
SUCCESS_COUNT=0
# Assemble the combined comment in model order. Reviewers are anonymized as
# "Reviewer N" — the model -> number mapping appears only in the (debug) logs
# below, so readers judge the feedback, not the model.
for i in "${!MODELS[@]}"; do
REVIEWER_NUM=$((i + 1))
EXIT_CODE="$(cat "$WORK_DIR/code_$i" 2>/dev/null || echo 1)"
# A missing/empty/garbage exit-code file counts as a failed reviewer rather
# than tripping the numeric comparison below (which would print "integer
# expression expected" and then fall through to wrongly treating the reviewer
# as a success).
[[ "$EXIT_CODE" =~ ^[0-9]+$ ]] || EXIT_CODE=1
AI_RESPONSE="$(cat "$WORK_DIR/resp_$i.json" 2>/dev/null || echo "")"
echo "::group::Reviewer #$REVIEWER_NUM (${MODELS[i]})" >&2
if [ "$DEBUG_MODE" = "true" ]; then
{
echo "=== RAW AI RESPONSE (reviewer #$REVIEWER_NUM, exit $EXIT_CODE) ==="
echo "$AI_RESPONSE"
echo "--- stderr ---"
cat "$WORK_DIR/err_$i.log" 2>/dev/null || true
echo "=== END RAW AI RESPONSE ==="
} >&2
fi
# One model failing (hard error, empty, or non-JSON) must not sink the others:
# record a short failure note and keep going. The caller fails the workflow
# only if *every* reviewer failed (success_count == 0). A reviewer that errors
# out never sets a verdict, so it can never flip the aggregate decision to
# "fail" — only a model that returns a valid "fail" verdict does. Sensitive raw
# output is never written to the comment.
# Require a JSON *object*: a bare `jq .` accepts any valid JSON, including a
# top-level string/array/number that a refusing or misbehaving model might
# emit ("I refuse to review this"). That would pass the gate and then crash
# the `.review` access below under `set -e`, sinking *every* reviewer and
# writing no output at all. `type == "object"` rejects non-objects so they
# degrade to a per-reviewer failure note, preserving the others.
if [ "$EXIT_CODE" -ne 0 ] || [ -z "$AI_RESPONSE" ] || ! echo "$AI_RESPONSE" | jq -e 'type == "object"' >/dev/null 2>&1; then
echo "⚠️ Reviewer #$REVIEWER_NUM did not return a usable review (exit $EXIT_CODE)" >&2
REVIEW_BODY="_This reviewer could not complete its review (see workflow logs for details)._"
LABELS=""
else
REVIEW_RAW=$(echo "$AI_RESPONSE" | jq -r '.review // "No review provided"')
# Trim whitespace with tr (the verdict is a single token). The previous
# `| xargs` aborted the entire script on an unbalanced quote in the value,
# which—inside this per-reviewer loop—would sink every reviewer.
DECISION=$(echo "$AI_RESPONSE" | jq -r '.fail_pass_workflow // "uncertain"' | tr -d '[:space:]')
LABELS=$(echo "$AI_RESPONSE" | jq -r '.labels_added[]? // empty')
SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
[ "$DECISION" = "fail" ] && ANY_FAIL="true"
# Each review opens with a "## AI Code Review" H2 and closes with the
# singular Friendly AI Reviewer footer. Under a "### 👤 Reviewer N"
# subheading the H2 is redundant and the footer would repeat once per model,
# so strip both here; a single (plural) footer is added to the combined
# comment below. The footer match keys on the stable "Review by [Friendly AI
# Reviewer]" prefix and the \s*\z tail absorbs trailing whitespace; if the
# upstream markers ever change and aren't found, the text is left intact
# (nothing is dropped).
REVIEW_BODY=$(printf '%s' "$REVIEW_RAW" \
| perl -0pe 's/\A\s*##\s*AI Code Review\s*\n+//' \
| perl -0pe 's/\n*---\s*\n\*Review by \[Friendly AI Reviewer\][^\n]*\n?\s*\z//s')
fi
# Lead each section with a blank line so the "### Reviewer N" heading always
# renders (command substitution strips trailing newlines, so the separator
# must precede the heading, not follow the previous body).
COMBINED_REVIEW=$(printf '%s\n\n### 👤 Reviewer %s\n\n%s' "$COMBINED_REVIEW" "$REVIEWER_NUM" "$REVIEW_BODY")
[ -n "$LABELS" ] && ALL_LABELS=$(printf '%s\n%s' "$ALL_LABELS" "$LABELS")
echo "::endgroup::" >&2
done
# Aggregate labels (union across reviewers) and the pass/fail decision (request
# changes if ANY reviewer did).
LABELS=$(printf '%s\n' "$ALL_LABELS" | sed '/^[[:space:]]*$/d' | sort -u)
if [ "$ANY_FAIL" = "true" ]; then
DECISION="fail"
else
DECISION="pass"
fi
REVIEWER_WORD="reviewers"
[ "${#MODELS[@]}" -eq 1 ] && REVIEWER_WORD="reviewer"
STICKY_MARKER="<!-- ai-code-review:sticky -->"
FOOTER="*Reviews by [Friendly AI Reviewer](https://github.com/LearningCircuit/Friendly-AI-Reviewer) - made with ❤️*"
# COMBINED_REVIEW already starts with "\n\n", so it follows the title directly
# (no extra newline) to yield exactly one blank line before "### Reviewer 1".
# The backticks in `%s` are intentional literal Markdown (inline-code the SHA),
# not a command substitution — single quotes keep them literal on purpose.
# shellcheck disable=SC2016
COMMENT_BODY=$(printf '%s\n\n## 🤖 AI Code Review (%s %s)%s\n\n---\n%s\n\n_Last reviewed at commit `%s`_' \
"$STICKY_MARKER" "${#MODELS[@]}" "$REVIEWER_WORD" "$COMBINED_REVIEW" "$FOOTER" "${HEAD_SHA:0:7}")
printf '%s' "$COMMENT_BODY" > "$WORK_DIR/comment_body.md"
printf '%s' "$LABELS" > "$WORK_DIR/labels.txt"
printf '%s' "$DECISION" > "$WORK_DIR/decision.txt"
printf '%s' "$SUCCESS_COUNT" > "$WORK_DIR/success_count.txt"