CtrlK
BlogDocsLog inGet started
Tessl Logo

jbaruch/coding-policy

General-purpose coding policy for Baruch's AI agents

Quality

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Low

Low-risk findings worth noting

Overview
Quality
Evals
Security
Files

adopt.shskills/adopt-fork-pr/

#!/usr/bin/env bash
#
# adopt.sh — Adopt a fork pull request's branch into the base repo so the
# policy reviewer (fork-guarded) can run on it.
#
# Usage:   adopt.sh <pr-number>
# Repo:    operates on the current git repository (origin).
#
# What it does (all deterministic):
#   1. Reads PR metadata via `gh pr view` (including the PR body).
#   2. Refuses anything that is not an OPEN cross-repo (fork) PR.
#   3. Computes the adopted branch name:
#        adopt/pr-<N>-<slug(headRefName)>
#      slug = headRefName lowercased, every run of non-[a-z0-9] folded to a
#      single '-', leading/trailing '-' trimmed, truncated to 50 chars.
#   4. Idempotency / partial-run recovery, keyed on the adopted branch:
#        - branch on origin AND an open PR for it  -> "already-adopted" (still
#          ensures the original carries the pointer comment, repairing a prior
#          run that died before commenting)
#        - branch on origin but NO open PR (a prior run pushed then died before
#          `gh pr create`) -> recover: open the PR + comment, emit "adopted"
#        - branch not on origin -> fresh adoption (checkout, push, PR, comment)
#   5. Fresh adoption `gh pr checkout`s the fork head and pushes it to origin
#      under the adopted branch name (commits unchanged, so authorship
#      survives), then opens a same-repo PR and comments on the original fork
#      PR. The original is left OPEN. An EXIT trap restores the caller's
#      original branch even if a later step fails after `gh pr checkout`.
#   6. The pointer comment on the original PR is part of the contract: it is
#      idempotent (skipped when the original already links the adopted URL) and
#      a posting failure is an operational error (exit 1), not a warning.
#
# Output: a single JSON object on stdout:
#   {"state": "...", "adopted_branch": "...", "new_pr_url": "...",
#    "original_pr": N, "author": "..."}
#   state ∈ {"adopted", "already-adopted"}
#   Error path: when jq itself is missing, the script emits
#   {"state": "error", "reason": "..."} on stdout (so stdout-parsing wrappers
#   still get a payload) AND a diagnostic on stderr, then exits 1 — jq is
#   required to format the normal envelope, so the failure is hand-rolled. Every
#   other failure is a stderr diagnostic with the exit code below (no stdout JSON).
#
# New-PR body template (verbatim):
#   Adopted from #<N> by @<author> (fork <owner>/<repo>).
#
#   Carries the contributor's original commits unchanged — authorship is
#   preserved. As a same-repo PR, it gets the policy review.
#
#   Original PR: <url>
#
# Original-PR pointer-comment template (emitted as a single line; wrapped here
# only for readability):
#   Adopted into the base repo as <new_pr_url> so the policy reviewer can run —
#   fork PRs are skipped by the reviewer's fork-guard. Leaving this PR open;
#   close it whenever you like, it's your call.
#
# Exit codes:
#   0  success (adopted, recovered, or already-adopted no-op)
#   1  operational failure (dirty tree, gh/git failure, pointer-comment failure,
#      PR not OPEN)
#   2  usage / invalid argument
#   3  PR is not a fork PR (adoption does not apply)
#
set -euo pipefail

orig_ref=""   # caller's branch; restored by the EXIT trap once fresh adoption starts

emit_jq_missing() {
  # jq is needed to format the normal envelope, so hand-roll the failure: a JSON
  # payload on stdout for stdout-parsing wrappers AND a diagnostic on stderr for
  # stderr-only watchers.
  local msg="jq is required but not installed — install it (macOS: brew install jq; Debian/Ubuntu: apt install jq) and re-run."
  printf '{"state":"error","reason":"%s"}\n' "$msg"
  printf 'adopt.sh: %s\n' "$msg" >&2
}

die() { printf 'adopt.sh: %s\n' "$1" >&2; exit "${2:-1}"; }

restore_orig_ref() {
  if [ -n "$orig_ref" ]; then
    # Best-effort: this runs on the error path, where the caller is already
    # reporting a failure — a checkout failure here must not replace that
    # message or the exit code with its own. Best-effort is not silent
    # though: `|| true` left the operator on an unexpected branch with no
    # hint, which is the worst version of this outcome.
    if ! git checkout --quiet "$orig_ref" >/dev/null 2>&1; then
      echo "adopt.sh: warning: could not restore the original ref '${orig_ref}' — you are left on the branch adopt.sh was using; run 'git checkout ${orig_ref}' to get back" >&2
    fi
  fi
  return 0
}

slugify() {
  # lowercase → fold non-alnum runs to '-' → trim → cap at 50 chars
  local s
  s=$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9' '-')
  s=$(printf '%s' "$s" | sed -E 's/-+/-/g; s/^-//; s/-$//')
  printf '%s' "${s:0:50}"
}

open_pr_url_for() {
  # echoes the URL of the open PR whose head is $1, or empty if none.
  # Exits non-zero (with gh's stderr intact) if the query itself fails, so the
  # caller can distinguish "no PR" from "couldn't ask".
  gh pr list --head "$1" --state open --json url --jq '.[0].url // empty'
}

ensure_pointer_comment() {
  # args: original_pr adopted_url
  # Idempotent: skip when the original already links the adopted URL. Both the
  # read probe and the post are operational errors on failure, not warnings —
  # capture the read first and die rather than letting a failed probe look like
  # "no comment" (which would duplicate the pointer on rerun).
  local original_pr="$1" adopted_url="$2" comment existing_comments
  existing_comments=$(gh pr view "$original_pr" --json comments) \
    || die "could not read comments on original #$original_pr to check for an existing pointer — see the gh error above." 1
  if grep -qF "$adopted_url" <<<"$existing_comments"; then
    return 0
  fi
  comment=$(printf 'Adopted into the base repo as %s so the policy reviewer can run — fork PRs are skipped by the reviewer'\''s fork-guard. Leaving this PR open; close it whenever you like, it'\''s your call.\n' "$adopted_url")
  gh pr comment "$original_pr" --body "$comment" >/dev/null \
    || die "adopted PR ($adopted_url) exists but posting the pointer comment on original #$original_pr failed — see the gh error above; rerun to retry." 1
}

create_adopted_pr() {
  # args: base branch title pr_n author fork_owner fork_repo orig_url
  # opens the same-repo PR, links the original, echoes the new PR URL
  local base="$1" branch="$2" title="$3" pr_n="$4" author="$5" fork_owner="$6" fork_repo="$7" orig_url="$8"
  local body new_url
  body=$(printf 'Adopted from #%s by @%s (fork %s/%s).\n\nCarries the contributor'\''s original commits unchanged — authorship is preserved. As a same-repo PR, it gets the policy review.\n\nOriginal PR: %s\n' \
    "$pr_n" "$author" "$fork_owner" "$fork_repo" "$orig_url")
  new_url=$(gh pr create --base "$base" --head "$branch" --title "$title" --body "$body") \
    || die "gh pr create for branch $branch failed — see the gh error above (permissions, an existing PR for the branch, or validation)." 1
  ensure_pointer_comment "$pr_n" "$new_url"
  printf '%s' "$new_url"
}

emit() {
  # args: state adopted_branch new_pr_url pr_n author
  jq -nc --arg s "$1" --arg b "$2" --arg u "$3" --argjson n "$4" --arg a "$5" \
    '{state:$s,adopted_branch:$b,new_pr_url:$u,original_pr:$n,author:$a}'
}

main() {
  command -v jq >/dev/null 2>&1 || { emit_jq_missing; exit 1; }
  command -v gh >/dev/null 2>&1 || die "GitHub CLI (gh) not found — install it and run 'gh auth login'." 1
  command -v git >/dev/null 2>&1 || die "git not found." 1

  [ "$#" -eq 1 ] || die "usage: adopt.sh <pr-number>" 2
  local n="$1"
  [[ "$n" =~ ^[1-9][0-9]*$ ]] || die "PR number must be a positive integer, got: $n" 2

  git rev-parse --is-inside-work-tree >/dev/null 2>&1 || die "not inside a git work tree." 1

  local meta
  meta=$(gh pr view "$n" --json number,isCrossRepository,headRefName,headRepositoryOwner,headRepository,author,title,url,state,baseRefName) \
    || die "could not read PR #$n — see the gh error above; check the number and that 'gh auth status' is healthy." 1

  local is_fork state head_ref base_ref fork_owner fork_repo author title url
  is_fork=$(jq -r '.isCrossRepository' <<<"$meta")
  state=$(jq -r '.state' <<<"$meta")
  head_ref=$(jq -r '.headRefName' <<<"$meta")
  base_ref=$(jq -r '.baseRefName' <<<"$meta")
  fork_owner=$(jq -r '.headRepositoryOwner.login // empty' <<<"$meta")
  fork_repo=$(jq -r '.headRepository.name // empty' <<<"$meta")
  author=$(jq -r '.author.login // empty' <<<"$meta")
  title=$(jq -r '.title' <<<"$meta")
  url=$(jq -r '.url' <<<"$meta")

  [ "$is_fork" = "true" ] || die "PR #$n is a same-repo PR — adoption only applies to fork PRs; the reviewer already covers it." 3
  [ "$state" = "OPEN" ] || die "PR #$n is $state — only OPEN fork PRs can be adopted." 1

  local branch new_url
  branch="adopt/pr-${n}-$(slugify "$head_ref")"

  # Branch already on origin: distinguish a complete prior run from a partial one.
  local remote_heads
  remote_heads=$(git ls-remote --heads origin "refs/heads/$branch") \
    || die "git ls-remote origin failed — check network/auth to the base repo." 1
  if [ -n "$remote_heads" ]; then
    local existing
    existing=$(open_pr_url_for "$branch") \
      || die "gh pr list failed while checking for an existing adopted PR — see the gh error above; do not assume none exists." 1
    if [ -n "$existing" ]; then
      ensure_pointer_comment "$n" "$existing"   # repair a missing link from a prior partial run
      emit "already-adopted" "$branch" "$existing" "$n" "$author"
      exit 0
    fi
    # Branch was pushed but no PR exists — recover by opening it now.
    new_url=$(create_adopted_pr "$base_ref" "$branch" "$title" "$n" "$author" "$fork_owner" "$fork_repo" "$url")
    emit "adopted" "$branch" "$new_url" "$n" "$author"
    exit 0
  fi

  # Fresh adoption: needs a clean tree because gh pr checkout mutates it.
  # This is a GUARD, not a classifier — unlike preflight's classify_target_dirty,
  # it needn't distinguish diff's exit 1 (dirty) from >1 (git fault): both mean
  # "not safe to mutate the tree", and refusing on either is the safe outcome.
  if ! git diff --quiet || ! git diff --cached --quiet; then
    die "working tree has uncommitted changes (or git could not verify it) — commit or stash before adopting (gh pr checkout needs a clean tree)." 1
  fi
  orig_ref=$(git symbolic-ref --quiet --short HEAD 2>/dev/null || git rev-parse HEAD)
  trap restore_orig_ref EXIT   # restore the caller's branch even if a later step fails

  gh pr checkout "$n" >/dev/null \
    || die "gh pr checkout #$n failed — see the gh error above; the fork branch may be unavailable or your tree is not clean." 1
  git push origin "HEAD:refs/heads/$branch" >/dev/null \
    || die "push to origin/$branch failed — see the git error above; you need write access to the base repo." 1

  new_url=$(create_adopted_pr "$base_ref" "$branch" "$title" "$n" "$author" "$fork_owner" "$fork_repo" "$url")
  emit "adopted" "$branch" "$new_url" "$n" "$author"
}

if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
  main "$@"
fi

README.md

tile.json