learningcircuit--local-deep-research
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
156 行
7.5 KiB
Bash
可执行文件
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"
|