Files
skill-gitea/scripts/pr-create.sh

164 lines
4.7 KiB
Bash

#!/usr/bin/env bash
# gitea skill — crear un PR.
#
# Uso:
# pr-create.sh <owner>/<repo> --head <branch> [--base main] --title "..." \
# (--body "..." | --body-file <path>) [--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
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 <<EOF
Uso: pr-create.sh <owner>/<repo> --head <branch> [--base main] \\
--title "..." (--body "..." | --body-file <path>) [--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: <owner>/<repo> --head <branch> --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 <<EOF
ERROR: el title o body contiene marker de AI attribution:
pattern: $pat
Esta skill bloquea esos markers (regla dura, ver memoria
feedback_no_ai_attribution.md). El usuario considera deshonesto darle crédito
a Claude/Anthropic por trabajo del usuario. Removelos y volvé a correr.
Líneas que matchean:
EOF
echo "$combined" | grep -inE "$pat" | head -5 >&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)
"