asterinas/.agents/skills/aster-code-review/scripts/parse_pr_command.sh

79 lines
3.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# SPDX-License-Identifier: MPL-2.0
#
# parse_pr_command.sh — parse a `/aster-code-review …` PR-comment command into a validated CI plan.
# Reads the command text as $1, or the whole comment body on stdin.
# On success prints `key=value` lines to stdout;
# on an invalid command it prints an error to stderr and exits 2.
#
# Grammar (mirrors the skill interface, spec/interface.md):
# /aster-code-review -> review the PR diff (alias for `diff`)
# /aster-code-review diff -> review the PR diff
# /aster-code-review files <p1> … <pN> -> review those files
# /aster-code-review smoke [--problems="…"] -> the smoke test
# /aster-code-review benchmark [--problems="…"] -> the benchmark (informational in CI)
#
# The command mimics a CLI:
# `--problems="<id1> … <idN>"` selects a subset of benchmark problems (four-digit ids).
# The workflow translates it into the make knob `PROBLEMS=` internally;
# the user-facing flag is `--problems`.
#
# STRICT ALLOWLIST.
# This is a UX/routing layer with nice errors;
# it is NOT the security boundary.
# The workflow RE-VALIDATES every value that reaches a command
# (see .github/workflows/invoke_aster_code_review.yml),
# so even a PR-modified copy of this script cannot widen what the trusted workflow will run.
#
# Output keys:
# kind=review mode=diff
# kind=review mode=files paths=<space-separated, validated>
# kind=test target=smoke|benchmark problems=<empty | "NNNN …">
set -uo pipefail
fail() { printf 'parse_pr_command.sh: %s\n' "$1" >&2; exit 2; }
body="${1-}"
[ -n "$body" ] || body="$(cat)"
# The first line that starts (ignoring leading spaces) with the exact trigger token.
line="$(printf '%s\n' "$body" | grep -m1 -E '^[[:space:]]*/aster-code-review([[:space:]]|$)' || true)"
[ -n "$line" ] || fail "no '/aster-code-review' command found"
rest="$(printf '%s' "$line" | sed -E 's#^[[:space:]]*/aster-code-review[[:space:]]*##; s#[[:space:]]*$##')"
sub="${rest%%[[:space:]]*}"
args="$(printf '%s' "${rest#"$sub"}" | sed -E 's#^[[:space:]]*##')"
[ -n "$sub" ] || sub="diff"
case "$sub" in
diff)
[ -z "$args" ] || fail "'diff' takes no arguments"
printf 'kind=review\nmode=diff\n' ;;
files)
[ -n "$args" ] || fail "'files' needs at least one path"
for p in $args; do
case "$p" in
-*|/*|*..*) fail "invalid path: $p" ;;
esac
printf '%s' "$p" | grep -qE '^[A-Za-z0-9._/-]+$' || fail "invalid path: $p"
done
printf 'kind=review\nmode=files\npaths=%s\n' "$args" ;;
smoke|benchmark)
# Only --problems="…" is accepted, and only as space-separated problem numbers.
# Validated in the main shell (not a subshell) so `fail` aborts the script.
problems=""
if [ -n "$args" ]; then
case "$args" in
--problems=*) problems="${args#--problems=}"; problems="${problems%\"}"; problems="${problems#\"}" ;;
*) fail "'$sub' accepts only --problems=\"…\"" ;;
esac
printf '%s' "$problems" | grep -qE '^[0-9]{1,4}( [0-9]{1,4})*$' \
|| fail "--problems must be space-separated problem numbers, e.g. \"0002 0006\""
fi
printf 'kind=test\ntarget=%s\nproblems=%s\n' "$sub" "$problems" ;;
*)
fail "unknown subcommand '$sub' (use: diff | files | smoke | benchmark)" ;;
esac