#!/usr/bin/env bash # gitea skill β€” crear un PR. # # Uso: # pr-create.sh / --head [--base main] --title "..." \ # (--body "..." | --body-file ) [--draft] # # GarantΓ­as: # - Body en UTF-8 puro (vΓ­a Python json.dumps + curl --data-binary @file) # - Anti-AI-attribution guard sobre title + body (rechaza si encuentra # "Co-Authored-By: Claude", "πŸ€–", "Generated with Claude", "Anthropic" en # contexto de atribuciΓ³n) # - Trap EXIT borra el temp dir con el body al terminar set -euo pipefail # Forzar UTF-8 en stdout de Python. Windows defaultea a cp1252 y crashea # con literales no-ASCII (e.g. el `OK`/`->` aquΓ­ abajo o un PR title con Γ±). # Ver memoria feedback_api_utf8_encoding.md. export PYTHONIOENCODING=utf-8 SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" QUERY="$SKILL_DIR/scripts/query.sh" repo_arg="" head="" base="main" title="" body="" body_file="" draft=0 while [[ $# -gt 0 ]]; do case "$1" in --head) head="$2"; shift 2 ;; --base) base="$2"; shift 2 ;; --title) title="$2"; shift 2 ;; --body) body="$2"; shift 2 ;; --body-file) body_file="$2"; shift 2 ;; --draft) draft=1; shift ;; -h|--help) cat </ --head [--base main] \\ --title "..." (--body "..." | --body-file ) [--draft] Ejemplos: pr-create.sh NucleOS/nucleo-infra --head fix/foo --title "fix(x): ..." \\ --body-file /tmp/pr-body.md pr-create.sh nucleo-infra --head feat/y --title "..." --body "linea 1" EOF exit 0 ;; -*) echo "ERROR: flag desconocida: $1" >&2; exit 2 ;; *) repo_arg="$1"; shift ;; esac done if [[ -z "$repo_arg" || -z "$head" || -z "$title" ]]; then echo "ERROR: faltan args. NecesitΓ‘s: / --head --title \"...\"" >&2 exit 2 fi # Resolver owner/repo if [[ "$repo_arg" == */* ]]; then owner="${repo_arg%%/*}" repo="${repo_arg##*/}" else set -a; source "$SKILL_DIR/.env"; set +a owner="${GITEA_DEFAULT_OWNER:?owner no especificado y GITEA_DEFAULT_OWNER vacΓ­o}" repo="$repo_arg" fi # Cargar body desde archivo si corresponde if [[ -n "$body_file" ]]; then if [[ -n "$body" ]]; then echo "ERROR: pasΓ‘ --body o --body-file, no ambos." >&2 exit 2 fi if [[ ! -f "$body_file" ]]; then echo "ERROR: --body-file '$body_file' no existe." >&2 exit 2 fi body="$(cat "$body_file")" fi # ─── Anti-AI-attribution guard ────────────────────────────────────────── # Regla dura. Ver memoria feedback_no_ai_attribution.md. combined="$title $body" ai_patterns=( "Co-Authored-By:[[:space:]]*Claude" "Co-Authored-By:[[:space:]]*Anthropic" "πŸ€–" "Generated with[[:space:]]*\[?Claude" "Generated with[[:space:]]*Anthropic" "Created with[[:space:]]*\[?Claude" "Powered by[[:space:]]*Claude" "Made with[[:space:]]*Claude" ) for pat in "${ai_patterns[@]}"; do if echo "$combined" | grep -iqE "$pat"; then cat >&2 <&2 exit 4 fi done # ─── Build body JSON con Python (UTF-8 safe) ──────────────────────────── TMP_DIR="$(mktemp -d)" trap 'rm -rf "$TMP_DIR"' EXIT BODY_JSON="$TMP_DIR/body.json" PY_TITLE="$title" PY_BODY="$body" PY_HEAD="$head" PY_BASE="$base" PY_DRAFT="$draft" \ python -c ' import json, os data = { "title": os.environ["PY_TITLE"], "body": os.environ["PY_BODY"], "head": os.environ["PY_HEAD"], "base": os.environ["PY_BASE"], } if os.environ.get("PY_DRAFT") == "1": # Gitea no tiene draft API field, pero podΓ©s prefijar el title data["title"] = "[WIP] " + data["title"] # json.dumps con ensure_ascii=True (default) escapa todo non-ASCII a \uXXXX, # eliminando cualquier riesgo de encoding raro en transit. print(json.dumps(data)) ' > "$BODY_JSON" # Sanity check if [[ ! -s "$BODY_JSON" ]]; then echo "ERROR: body JSON vacΓ­o despuΓ©s de python. Bug del script." >&2 exit 5 fi echo "β†’ POST /repos/${owner}/${repo}/pulls" >&2 echo " head: $head β†’ base: $base" >&2 echo " title: $title" >&2 echo " body: ${#body} chars" >&2 resp="$("$QUERY" -X POST \ -H 'Content-Type: application/json; charset=utf-8' \ --data-binary @"$BODY_JSON" \ "/repos/${owner}/${repo}/pulls")" echo "$resp" | python -c " import json, sys d = json.load(sys.stdin) if d.get('number'): print(f'βœ“ PR #{d[\"number\"]} creado') print(f' url: {d.get(\"html_url\")}') else: print('ERROR:', d.get('message') or d, file=sys.stderr) sys.exit(1) "