#!/usr/bin/env bash # ============================================================================= # skills/ingest/scripts/open-pr.sh # Branch, commit (conventional), push, and open a Forgejo PR for the wiki/ changes. # Mirrors the API conventions of providers/forgejo.sh (token auth + http_code). # Runs inside the genome checkout (cwd = genome root). Never touches main. # # open-pr.sh --slug --title "feat: ingest " --body-file \ # [--base main] [--label CONFLICT] # # Requires env: FORGEJO_URL, FORGEJO_USER, FORGEJO_TOKEN. # ============================================================================= set -euo pipefail : "${FORGEJO_URL:?missing FORGEJO_URL}" : "${FORGEJO_USER:?missing FORGEJO_USER}" : "${FORGEJO_TOKEN:?missing FORGEJO_TOKEN}" slug="" title="" body_file="" base="main" label="" while [[ $# -gt 0 ]]; do case "$1" in --slug) slug="$2"; shift 2 ;; --title) title="$2"; shift 2 ;; --body-file) body_file="$2"; shift 2 ;; --base) base="$2"; shift 2 ;; --label) label="$2"; shift 2 ;; *) echo "open-pr: unknown arg: $1" >&2; exit 1 ;; esac done : "${slug:?--slug required}" : "${title:?--title required}" : "${body_file:?--body-file required}" [[ -f "$body_file" ]] || { echo "open-pr: body file not found: $body_file" >&2; exit 1; } branch="feat/ai-ingest-${slug}" repo="$(basename -s .git "$(git config --get remote.origin.url)")" # 1. Branch + commit + push (AGENTS.md rule 5: never commit to main) git switch -c "$branch" 2>/dev/null || git switch "$branch" git add wiki/ # Scope BOTH the emptiness check and the commit to wiki/ — never commit anything that # happened to be staged outside wiki/ (a stray hook, an aborted prior run, etc.). if git diff --cached --quiet -- wiki/; then echo "open-pr: nothing staged under wiki/ — aborting" >&2 exit 1 fi git commit -m "$title" -- wiki/ git push -u origin "$branch" # DRY_RUN: local git work done; skip the Forgejo API (offline tests). if [[ -n "${DRY_RUN:-}" ]]; then echo "PR opened: DRY-RUN ${branch} -> ${base}" exit 0 fi # 2. Open the PR via Forgejo API (jq builds the JSON safely) # TODO: Forgejo-only. When registry.sh/globals.env sets PROVIDER=github, branch on # $PROVIDER here and delegate to providers/github.sh (same token + http_code contract). body="$(cat "$body_file")" payload="$(jq -n --arg head "$branch" --arg base "$base" \ --arg title "$title" --arg body "$body" \ '{head:$head, base:$base, title:$title, body:$body}')" resp="$(curl --max-time 30 -s -w '\n%{http_code}' \ -H "Authorization: token ${FORGEJO_TOKEN}" \ -H "Content-Type: application/json" \ -X POST "${FORGEJO_URL}/api/v1/repos/${FORGEJO_USER}/${repo}/pulls" \ -d "$payload")" # curl -w appends '\n' AFTER the body, so the code is always the final line and the # body is everything before it. Parameter expansion (no subshells), robust to multi-line JSON. code="${resp##*$'\n'}" json="${resp%$'\n'*}" case "$code" in 201) url="$(printf '%s' "$json" | jq -r '.html_url')" number="$(printf '%s' "$json" | jq -r '.number')" echo "PR opened: ${url}" ;; 409) # PR already exists — fetch it so the orchestrator still gets the URL. existing="$(curl --max-time 15 -s -H "Authorization: token ${FORGEJO_TOKEN}" \ "${FORGEJO_URL}/api/v1/repos/${FORGEJO_USER}/${repo}/pulls?state=open" \ | jq -r --arg b "$branch" '.[] | select(.head.ref==$b) | .html_url' | head -n1)" if [[ -n "$existing" && "$existing" != "null" ]]; then echo "PR opened: ${existing}" else echo "open-pr: a PR for '${branch}' already exists (push updated the branch)." >&2 fi exit 0 ;; 401) echo "open-pr: unauthorized — check FORGEJO_TOKEN (n8n-bot)." >&2 exit 1 ;; *) echo "open-pr: Forgejo API HTTP ${code}: ${json}" >&2 exit 1 ;; esac # 3. Optional label (e.g. CONFLICT). Best-effort; non-fatal. if [[ -n "$label" && -n "${number:-}" ]]; then label_id="$(curl --max-time 15 -s -H "Authorization: token ${FORGEJO_TOKEN}" \ "${FORGEJO_URL}/api/v1/repos/${FORGEJO_USER}/${repo}/labels" \ | jq -r --arg n "$label" '.[] | select(.name==$n) | .id' | head -n1)" if [[ -n "$label_id" && "$label_id" != "null" ]]; then curl --max-time 15 -s -o /dev/null \ -H "Authorization: token ${FORGEJO_TOKEN}" -H "Content-Type: application/json" \ -X POST "${FORGEJO_URL}/api/v1/repos/${FORGEJO_USER}/${repo}/issues/${number}/labels" \ -d "{\"labels\":[${label_id}]}" \ && echo "label '${label}' applied" >&2 else echo "open-pr: label '${label}' not found in repo — skipped." >&2 fi fi