feat(pr-review): run the review through a listed Mistral agent #1251

Closed
claude-bot wants to merge 1 commits from feat/pr-review-listed-agent into main
4 changed files with 92 additions and 23 deletions
+49
View File
@@ -0,0 +1,49 @@
#!/bin/sh
# Create or update the listed Mistral agent "pr-review" from mistral-prompt.md, so the
# agent definition in Mistral Studio follows git. Prints the agent id.
#
# Run it after changing mistral-prompt.md, the model or the connector:
# MISTRAL_API_KEY=... sh ci/agents/pr-review/mistral-agent-sync.sh
#
# Env: MISTRAL_API_KEY (required), MISTRAL_AGENT_NAME, MISTRAL_MODEL, MISTRAL_CONNECTOR_ID,
# MISTRAL_API_BASE, MISTRAL_MAX_TOKENS.
set -eu
HERE="$(cd "$(dirname "$0")" && pwd)"
: "${MISTRAL_API_KEY:?MISTRAL_API_KEY is not set}"
NAME="${MISTRAL_AGENT_NAME:-pr-review}"
MODEL="${MISTRAL_MODEL:-mistral-large-4-0}"
CONNECTOR="${MISTRAL_CONNECTOR_ID:-01a12adf-cdc3-7562-b32b-b1fa96be244c}"
BASE="${MISTRAL_API_BASE:-https://api.mistral.ai/v1}"
MAX_TOKENS="${MISTRAL_MAX_TOKENS:-64000}"
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
# The key goes to curl through a config file, not argv.
umask 077
printf 'header = "Authorization: Bearer %s"\n' "$MISTRAL_API_KEY" > "$WORK/auth"
api() { # method path [datafile] -> body on stdout, fails on HTTP >= 300
if [ -n "${3:-}" ]; then set -- "$1" "$2" -d @"$3"; else set -- "$1" "$2"; fi
m="$1"; p="$2"; shift 2
code=$(curl -s -m 60 -K "$WORK/auth" -H 'Content-Type: application/json' -X "$m" "$@" \
-o "$WORK/resp" -w '%{http_code}' "$BASE$p")
if [ "$code" -ge 300 ]; then echo "Mistral API $m $p -> HTTP $code: $(head -c 400 "$WORK/resp")" >&2; return 1; fi
cat "$WORK/resp"
}
HASH="$(sha256sum "$HERE/mistral-prompt.md" | cut -c1-12)"
jq -n --arg name "$NAME" --arg model "$MODEL" --arg cid "$CONNECTOR" --argjson max "$MAX_TOKENS" \
--arg desc "PR review, read-only Gitea connector. prompt sha256:$HASH" \
--rawfile instructions "$HERE/mistral-prompt.md" \
'{name: $name, model: $model, description: $desc, instructions: $instructions,
tools: [{type: "connector", connector_id: $cid}],
completion_args: {temperature: 0.1, max_tokens: $max}}' > "$WORK/body.json"
ID="$(api GET "/agents?page_size=100" | jq -r --arg n "$NAME" '[.[]? | select(.name == $n) | .id][0] // empty')"
if [ -n "$ID" ]; then
api PATCH "/agents/$ID" "$WORK/body.json" > /dev/null
echo "updated $NAME (prompt sha256:$HASH)" >&2
else
ID="$(api POST "/agents" "$WORK/body.json" | jq -r '.id')"
echo "created $NAME (prompt sha256:$HASH)" >&2
fi
echo "$ID"
+15
View File
@@ -27,6 +27,21 @@ HARD RULES
training data is not evidence for how current releases behave.
5. Before you write "no other code does X", search for it.
## Mandatory checks
Do these for every PR, in addition to reading the diff and the changed files:
1. Read the pull request itself for `mergeable`, its base branch and whether the base moved. Read each changed file
at the base branch tip as well as at the head, and list recent commits on the base branch that touch those files.
State plainly if the change is already on the base branch, superseded by another PR, or conflicts with it. That is
a blocking finding.
2. Read every issue the PR links to.
3. Search the repository for other code that does the same thing before you write that nothing else does.
## Efficiency
Request all independent reads in the same step instead of one at a time (the diff, the changed files at head and
base, neighbouring files, README or CLAUDE.md together). Do not repeat a call whose result you already have. There is
no cap on tool calls; stop reading when every mandatory check is done and every suspicion is settled or listed as
unverified.
## What to look for, in priority order
1. Correctness: real bugs, wrong conditionals, unhandled errors, race conditions, resource leaks, incorrect API
usage, broken backward compatibility, functions whose return value is not what the caller assumes.
+24 -20
View File
@@ -1,45 +1,49 @@
#!/bin/sh
# Review one PR with a Mistral agent that reads the repository through the Gitea
# connector registered in Mistral Studio (read-only credential, see mistral-prompt.md).
# Review one PR with the listed Mistral agent "pr-review". The agent holds the prompt, the
# model and the read-only Gitea connector (kept in sync from git by mistral-agent-sync.sh).
#
# Usage: mistral-review.sh <workdir> <owner/repo> <pr number> <head sha>
# Writes <workdir>/report.md. Exits non-zero, and leaves no report, when the request
# fails or the agent called a tool that is not read-only.
# Writes <workdir>/report.md and <workdir>/model.txt. Exits non-zero, and leaves no report,
# when the request fails or the agent called a tool that is not read-only.
#
# Env: MISTRAL_API_KEY (required), MISTRAL_MODEL, MISTRAL_CONNECTOR_ID, MISTRAL_API,
# MISTRAL_MAX_TOKENS, MISTRAL_TIMEOUT.
# Env: MISTRAL_API_KEY (required), MISTRAL_AGENT_ID, MISTRAL_API_BASE, MISTRAL_TIMEOUT.
set -eu
D="$1"; SLUG="$2"; NUM="$3"; HEAD="$4"
HERE="$(cd "$(dirname "$0")" && pwd)"
: "${MISTRAL_API_KEY:?MISTRAL_API_KEY is not set}"
MODEL="${MISTRAL_MODEL:-mistral-large-4-0}"
CONNECTOR="${MISTRAL_CONNECTOR_ID:-01a12adf-cdc3-7562-b32b-b1fa96be244c}"
API="${MISTRAL_API:-https://api.mistral.ai/v1/conversations}"
MAX_TOKENS="${MISTRAL_MAX_TOKENS:-64000}"
AGENT="${MISTRAL_AGENT_ID:-ag_01a12b392002750b8ad2b4692a9de03c}"
BASE="${MISTRAL_API_BASE:-https://api.mistral.ai/v1}"
TIMEOUT="${MISTRAL_TIMEOUT:-1500}"
rm -f "$D/report.md"
jq -n --arg model "$MODEL" --arg connector "$CONNECTOR" --argjson max "$MAX_TOKENS" \
--arg task "Review pull request #$NUM of $SLUG. Head commit: $HEAD." \
--rawfile prompt "$HERE/mistral-prompt.md" \
'{model: $model, inputs: ($task + "\n\n" + $prompt), store: false,
tools: [{type: "connector", connector_id: $connector}],
completion_args: {temperature: 0.1, max_tokens: $max}}' > "$D/mistral-request.json"
rm -f "$D/report.md" "$D/model.txt"
# The key goes to curl through a config file, not argv.
umask 077
printf 'header = "Authorization: Bearer %s"\n' "$MISTRAL_API_KEY" > "$D/.curl-auth"
# Model name for the review header, and a warning when the agent's prompt is not the repo's.
if curl -sf -m 60 -K "$D/.curl-auth" -o "$D/agent.json" "$BASE/agents/$AGENT"; then
jq -r '.model' "$D/agent.json" > "$D/model.txt"
HASH="$(sha256sum "$HERE/mistral-prompt.md" | cut -c1-12)"
jq -e --arg h "sha256:$HASH" '.description | contains($h)' "$D/agent.json" > /dev/null \
|| echo "WARNING: agent $AGENT was not built from this mistral-prompt.md; run mistral-agent-sync.sh."
else
echo "WARNING: could not read agent $AGENT."
fi
jq -n --arg agent "$AGENT" --arg task "Review pull request #$NUM of $SLUG. Head commit: $HEAD." \
'{agent_id: $agent, inputs: $task, store: false}' > "$D/mistral-request.json"
code=000
for TRY in 1 2; do
code=$(curl -s -m "$TIMEOUT" -K "$D/.curl-auth" -H 'Content-Type: application/json' \
-d @"$D/mistral-request.json" -o "$D/mistral-response.json" -w '%{http_code}' "$API" || echo 000)
-d @"$D/mistral-request.json" -o "$D/mistral-response.json" -w '%{http_code}' "$BASE/conversations" || echo 000)
[ "$code" = 200 ] && break
echo "Mistral request attempt $TRY failed (HTTP $code): $(head -c 300 "$D/mistral-response.json" 2>&1 || true)"
done
rm -f "$D/.curl-auth"
[ "$code" = 200 ] || exit 1
echo "Mistral agent ($MODEL) made $(sh "$HERE/parse-response.sh" calls < "$D/mistral-response.json") tool calls."
echo "Mistral agent $AGENT made $(sh "$HERE/parse-response.sh" calls < "$D/mistral-response.json") tool calls."
BAD=$(sh "$HERE/parse-response.sh" violations < "$D/mistral-response.json")
if [ -n "$BAD" ]; then
echo "The agent called tools that are not read-only; no review is posted:"
+4 -3
View File
@@ -147,14 +147,15 @@ while IFS= read -r ITEM; do
# --settings "$PROMPTS/settings.json" \
# -p ) || echo "(review agent exited non-zero)"
# --- Review with a Mistral agent (read-only Gitea connector, see mistral-review.sh) ---
MODEL="${MISTRAL_MODEL:-mistral-large-4-0}"
echo "Review model: $MODEL"
# --- Review with the listed Mistral agent "pr-review" (see mistral-review.sh) ---
if ! sh "$PROMPTS/mistral-review.sh" "$D" "$SLUG" "$NUM" "$HEAD"; then
echo "Mistral review failed for $SLUG#$NUM."
REVIEW_FAILED=1
continue
fi
MODEL="Mistral agent"
[ -s "$D/model.txt" ] && MODEL="$(cat "$D/model.txt")"
echo "Review model: $MODEL"
if [ ! -s "$D/report.md" ]; then
echo "Agent wrote no report for $SLUG#$NUM — skipping post."