Files
mimic/scripts/open-pr.sh
Knacky 0f6ae857b3 feat(infra): design-reviewer agent + PR helper (US-24 + US-25)
US-24 — Process hygiene UI:
- New .claude/agents/design-reviewer.md (model: opus, read-only) — visual + design-system reviewer that runs after frontend-builder and before code-reviewer. Audits alignment, DESIGN.md tokens, light/dark consistency, typo hierarchy, whitespace rhythm, responsive sanity at 1280x720, button convention, V1 a11y. Output format mirrors code-reviewer.
- Updated .claude/agents/frontend-builder.md DoD: screenshots are MANDATORY (one per feature/state introduced or modified, light+dark when theming is in scope). Hard block on "Dev server not started" — must be flagged explicitly. Screenshots feed the design-reviewer step.

US-25 — PR helper:
- scripts/open-pr.sh wraps `POST /api/v1/repos/{owner}/{repo}/pulls`. Detects host/owner/repo from `git remote get-url origin`, reads basic-auth credentials from `~/.git-credentials` (same source as `git push`, no token in env), uses jq to compose the multiline-safe payload. Validates args, prints PR URL on success, exits non-zero with the server message on failure.
- Makefile target `open-pr TITLE="..." BODY=path/to/body.md [BASE=main]` wraps the script with the same arg validation.
- README.md "Make targets" table extended.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 19:41:34 +02:00

137 lines
3.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# Open a pull request against the Mimic Gitea repository using the credentials
# already stored in ~/.git-credentials (the same token used for `git push`).
#
# Usage:
# scripts/open-pr.sh --title "feat: sprint N — short summary" \
# --body path/to/body.md \
# [--base main] \
# [--head <current branch by default>]
#
# Or via the Makefile wrapper:
# make open-pr TITLE="feat: sprint 4 — UI polish" BODY=tasks/pr-body-sprint-4.md
#
# Output: prints the PR URL on success, exits non-zero on failure.
set -euo pipefail
# --- Arg parsing ------------------------------------------------------------
TITLE=""
BODY_FILE=""
BASE="main"
HEAD=""
while [[ $# -gt 0 ]]; do
case "$1" in
--title)
TITLE="${2:-}"
shift 2
;;
--body)
BODY_FILE="${2:-}"
shift 2
;;
--base)
BASE="${2:-}"
shift 2
;;
--head)
HEAD="${2:-}"
shift 2
;;
--sprint)
# purely informational; ignored — the title is what carries semantics
shift 2
;;
-h|--help)
sed -n '2,15p' "$0"
exit 0
;;
*)
echo "Unknown arg: $1" >&2
exit 2
;;
esac
done
[[ -n "$TITLE" ]] || { echo "--title is required" >&2; exit 2; }
[[ -n "$BODY_FILE" ]] || { echo "--body is required" >&2; exit 2; }
[[ -f "$BODY_FILE" ]] || { echo "body file not found: $BODY_FILE" >&2; exit 2; }
# --- Credentials ------------------------------------------------------------
CRED_FILE="${HOME}/.git-credentials"
[[ -f "$CRED_FILE" ]] || { echo "no ~/.git-credentials — git push must have run at least once" >&2; exit 3; }
# Detect Gitea host from origin remote
ORIGIN_URL=$(git remote get-url origin)
# Strip protocol, .git suffix → host/owner/repo
case "$ORIGIN_URL" in
https://*)
REST="${ORIGIN_URL#https://}"
;;
*)
echo "origin is not https (got: $ORIGIN_URL) — this script supports HTTPS Gitea only" >&2
exit 3
;;
esac
REST="${REST%.git}"
HOST="${REST%%/*}"
PATHPART="${REST#*/}" # owner/repo
OWNER="${PATHPART%%/*}"
REPO="${PATHPART#*/}"
REPO="${REPO%%/*}" # belt + braces in case of trailing slash
# Match the credential line for this host
CRED_LINE=$(grep -E "^https://[^@]+@${HOST}\$" "$CRED_FILE" || true)
[[ -n "$CRED_LINE" ]] || { echo "no credential for host ${HOST} in ${CRED_FILE}" >&2; exit 3; }
USER_PART=$(echo "$CRED_LINE" | sed -E 's|^https://([^:]+):.*|\1|')
TOKEN=$(echo "$CRED_LINE" | sed -E 's|^https://[^:]+:([^@]+)@.*$|\1|')
[[ -n "$USER_PART" && -n "$TOKEN" ]] || { echo "could not parse user/token from credential" >&2; exit 3; }
# --- Branch -----------------------------------------------------------------
if [[ -z "$HEAD" ]]; then
HEAD=$(git rev-parse --abbrev-ref HEAD)
fi
[[ "$HEAD" != "HEAD" ]] || { echo "detached HEAD — pass --head explicitly" >&2; exit 3; }
# --- Compose payload --------------------------------------------------------
API_URL="https://${HOST}/api/v1/repos/${OWNER}/${REPO}/pulls"
PAYLOAD=$(jq -n \
--arg title "$TITLE" \
--rawfile body "$BODY_FILE" \
--arg head "$HEAD" \
--arg base "$BASE" \
'{title:$title, body:$body, head:$head, base:$base}')
# --- POST -------------------------------------------------------------------
RESPONSE_FILE=$(mktemp)
HTTP_CODE=$(curl -sS -u "${USER_PART}:${TOKEN}" \
-H "Content-Type: application/json" \
-X POST \
-d "$PAYLOAD" \
-o "$RESPONSE_FILE" \
-w "%{http_code}" \
"$API_URL")
if [[ "$HTTP_CODE" != "201" ]]; then
echo "PR creation failed (HTTP $HTTP_CODE):" >&2
jq -r '.message // empty' "$RESPONSE_FILE" >&2 2>/dev/null || cat "$RESPONSE_FILE" >&2
rm -f "$RESPONSE_FILE"
exit 4
fi
PR_URL=$(jq -r '.html_url' "$RESPONSE_FILE")
PR_NUMBER=$(jq -r '.number' "$RESPONSE_FILE")
rm -f "$RESPONSE_FILE"
echo "Opened PR #${PR_NUMBER}"
echo "$PR_URL"