feat(pr-review): run the review through a listed Mistral agent #1251
@@ -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"
|
||||
@@ -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.
|
||||
|
||||
@@ -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:"
|
||||
|
||||
@@ -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."
|
||||
|
||||
Reference in New Issue
Block a user