CtrlK
BlogDocsLog inGet started
Tessl Logo

jbaruch/coding-policy

General-purpose coding policy for Baruch's AI agents

74

Quality

93%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide
SecuritybySnyk

Medium

Suggest reviewing before use

Overview
Quality
Evals
Security
Files

prune-remote-branches.shskills/herdr-foreman/

#!/usr/bin/env bash
# Delete the spent branches on origin; report the ones holding work that no
# open pull request carries, never touch them.
#
# `prune-worktrees.sh` owns a repository's worktrees and local branches; this
# script owns its branches on origin, a separate concern (network, the GitHub
# CLI). Which branch is safe to delete is one right answer per input, so the
# decision lives here (`rules/script-delegation.md`).
#
# Decision predicate — a branch on origin, other than origin's default branch,
# is:
#   * NEVER TOUCHED OR LISTED when it is protected, or an open pull request
#     has it as its head (`gh pr list --repo <origin> --state open`);
#   * DELETED when merged into origin's default branch: immediately before the
#     deletion (and before a dry run's preview), the branch's tip, the default
#     branch's tip, which branch origin's HEAD names, the open pull requests
#     for it and its protection are read again, and any change keeps it
#     (changed). The deletion is `git push origin --delete` with
#     `--force-with-lease=<branch>:<tip>`, so a push that landed since wins;
#   * QUESTIONABLE, reported for the operator's decision, when unmerged, its
#     tip commit is at least REMOTE_IDLE_HOURS old, and the same final
#     re-read of origin finds nothing changed: commits ahead of the
#     default branch, age, last author, and the commands to open a pull
#     request or delete it, the delete leased to the tip judged here;
#   * KEPT silently when unmerged and younger than that (not-idle).
# Every gh call names origin's GitHub repository, resolved once per run by
# `skills/release/origin-repo.py`, never gh's default repository. Without the
# GitHub CLI, when origin names no GitHub repository, or when the first gh
# reads (protected branches, open pull requests) fail, nothing is deleted or
# listed: `could_not_check` says why. Each deletion stands on its own final re-read of origin, so a gh read
# that fails there keeps that branch alone (`failed`); a branch already
# deleted passed its own re-read and is reported in `deleted`. A failed git or gh command that talks to origin
# is reported by exit code and the command to rerun, never by its own
# message, which can carry the remote URL with credentials.
#
# Contract:
#   argv  : <shared-checkout> [--dry-run]
#           --dry-run reports the same decisions and deletes nothing. It
#           still fetches, so its answer is current.
#   stdout: one JSON object —
#           {"shared":"<abs>","default_branch":"<name>","dry_run":bool,
#            "deleted":["<branch>"],
#            "questionable":[{"branch","ahead","age_hours","author",
#                             "open_pr","delete"}],
#            "kept":[{"branch","reason"}],
#            "could_not_check":"<why>"|null,
#            "failed":[{"target","error"}]}
#           kept reasons: changed (its tip, the default branch or its tip,
#           its protection, or its open pull requests changed since it was
#           judged, for a deletion or a listing alike), not-idle.
#   stderr: diagnostics only.
#   exit  : 0 every decision applied (or previewed),
#           1 precondition unmet (usage, git or python3 absent, not a repo,
#             no origin, fetch or ls-remote failed) — no JSON,
#           2 could_not_check is set or `failed` is non-empty.
#   env   : PRUNE_REMOTE_IDLE_HOURS overrides REMOTE_IDLE_HOURS, PRUNE_NOW
#           (epoch seconds) the clock; the tests point PATH at a fake gh.
set -euo pipefail

#: An unmerged branch is reported only once its tip commit is this old.
REMOTE_IDLE_HOURS="${PRUNE_REMOTE_IDLE_HOURS:-24}"

WORKDIR=""
ERRFILE=""
ROWS=""
#: origin's GitHub repository as <owner>/<repo>; every gh call names it.
REPO=""

warn() { printf 'prune-remote-branches: %s\n' "$1" >&2; }

cleanup() {
  if [[ -n "$WORKDIR" ]] && ! rm -rf "$WORKDIR"; then
    warn "could not remove the temporary directory ${WORKDIR} — remove it by hand"
  fi
  return 0
}

# One NUL-delimited decision row: <kind> <branch> <a> <b> <c> <d>.
row() {
  printf '%s\0%s\0%s\0%s\0%s\0%s\0' "$1" "$2" "${3:-}" "${4:-}" "${5:-}" "${6:-}" >> "$ROWS"
  if [[ "$1" == failed ]]; then warn "${2}: ${3} — inspect it by hand; nothing else was skipped on its account"; fi
}

# A command that talks to origin failed: keep only its exit code and the
# command to rerun; its own message can carry the remote URL with credentials.
network_failure() { # <exit> <dir> <command...>
  local rc="$1" dir="$2"
  shift 2
  # shellcheck disable=SC2016  # The backticks are literal text in the message.
  printf '`%s` exited %s; run it in %s to see why (its output is not relayed: it can carry the remote URL with credentials)\n' \
    "$*" "$rc" "$dir" > "$ERRFILE"
}

# Echo "<sha>" origin holds for refs/heads/<branch> now, empty when none.
remote_tip() { # <shared> <branch>
  local out rc=0
  out="$(git -C "$1" ls-remote origin "refs/heads/$2" 2>"$ERRFILE")" || rc=$?
  if (( rc != 0 )); then network_failure "$rc" "$1" git ls-remote origin "refs/heads/$2"; return 1; fi
  printf '%s' "${out%%[[:space:]]*}"
}

# Echo the open pull requests' head branch names, one per line.
open_pr_heads() { # <shared> [branch]
  local rc=0 out
  local -a args=(pr list --repo "$REPO" --state open --limit 1000 --json headRefName --jq '.[].headRefName')
  [[ -n "${2:-}" ]] && args+=(--head "$2")
  out="$(GH_PROMPT_DISABLED=1 gh "${args[@]}" 2>"$ERRFILE")" || rc=$?
  if (( rc != 0 )); then network_failure "$rc" "$1" gh "${args[@]}"; return 1; fi
  printf '%s' "$out"
}

# Echo the branch origin's HEAD names now.
remote_default() { # <shared>
  local out rc=0 name
  out="$(git -C "$1" ls-remote --symref origin HEAD 2>"$ERRFILE")" || rc=$?
  if (( rc != 0 )); then network_failure "$rc" "$1" git ls-remote --symref origin HEAD; return 1; fi
  name="$(printf '%s\n' "$out" | sed -n 's#^ref: refs/heads/\(.*\)[[:space:]]HEAD$#\1#p' | head -n 1)"
  if [[ -z "$name" ]]; then printf 'origin reports no default branch\n' > "$ERRFILE"; return 1; fi
  printf '%s' "$name"
}

# Echo "true" or "false": whether GitHub protects <branch> now.
branch_protected() { # <shared> <branch>
  local out rc=0 segment
  # One path segment: a branch named feat/add-auth would otherwise route as
  # two, and the lookup would fail for every such branch.
  segment="$(python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "$2")"
  out="$(GH_PROMPT_DISABLED=1 gh api "repos/${REPO}/branches/${segment}" --jq .protected 2>"$ERRFILE")" || rc=$?
  if (( rc != 0 )); then network_failure "$rc" "$1" gh api "repos/${REPO}/branches/${segment}"; return 1; fi
  printf '%s' "$out"
}

# Set REPO to origin's GitHub repository, or return 1 with ERRFILE saying why.
resolve_repo() { # <shared>
  local src dir here helper out rc=0
  src="${BASH_SOURCE[0]}"
  case "$src" in
    */*) dir="${src%/*}" ;;
    *) dir=. ;;
  esac
  # A sentinel carries the directory across command substitution's newline strip.
  if ! here="$(CDPATH='' cd -- "${dir:-/}" && pwd && printf x)"; then
    printf 'the script directory %s cannot be entered — reinstall the plugin\n' "${dir:-/}" > "$ERRFILE"; return 1
  fi
  here="${here%x}"; here="${here%$'\n'}"
  helper="${here}/../release/origin-repo.py"
  if [[ ! -f "$helper" || ! -r "$helper" ]]; then
    printf '%s is not readable, so origin cannot be matched to a GitHub repository — reinstall the plugin\n' "$helper" > "$ERRFILE"; return 1
  fi
  out="$(python3 "$helper" "$1" 2>"$ERRFILE")" || rc=$?
  if (( rc != 0 )); then return 1; fi
  if ! REPO="$(printf '%s' "$out" | python3 -c 'import json, sys; print(json.load(sys.stdin)["repo"])' 2>"$ERRFILE")" || [[ -z "$REPO" ]]; then
    # shellcheck disable=SC2016  # The backticks are literal text in the message.
    printf 'origin-repo.py answered without a repository — run `python3 %s %s` to inspect it\n' "$helper" "$1" > "$ERRFILE"; return 1
  fi
}

main() {
  local shared="" dry=0 arg
  for arg in "$@"; do
    case "$arg" in
      --dry-run) dry=1 ;;
      -*) warn "unknown flag '${arg}' — usage: prune-remote-branches.sh <shared-checkout> [--dry-run]"; return 1 ;;
      *) if [[ -n "$shared" ]]; then warn "usage: prune-remote-branches.sh <shared-checkout> [--dry-run]"; return 1; fi; shared="$arg" ;;
    esac
  done
  if [[ -z "$shared" ]]; then warn "usage: prune-remote-branches.sh <shared-checkout> [--dry-run]"; return 1; fi
  local tool
  for tool in git python3; do
    if ! command -v "$tool" >/dev/null; then warn "${tool} not found on PATH — install it"; return 1; fi
  done
  if ! WORKDIR="$(mktemp -d "${TMPDIR:-/tmp}/prune-remote-branches.XXXXXX")"; then
    WORKDIR=""
    warn "cannot create a temporary directory under ${TMPDIR:-/tmp} — make it writable, then re-run"
    return 1
  fi
  trap cleanup EXIT
  ERRFILE="${WORKDIR}/err"; ROWS="${WORKDIR}/rows"
  : > "$ERRFILE"; : > "$ROWS"
  if [[ ! -d "$shared" ]] || ! git -C "$shared" rev-parse --is-inside-work-tree >/dev/null 2>"$ERRFILE"; then
    warn "'${shared}' is not a git work tree ($(tr '\n' ' ' < "$ERRFILE")) — pass the shared checkout's path"; return 1
  fi
  # A sentinel past git's own newline: command substitution strips both, and a
  # path ending in a newline would lose its own.
  shared="$(git -C "$shared" rev-parse --show-toplevel && printf x)"
  shared="${shared%x}"; shared="${shared%$'\n'}"
  if ! git -C "$shared" remote get-url origin >/dev/null 2>"$ERRFILE"; then
    warn "${shared} has no origin remote ($(tr '\n' ' ' < "$ERRFILE")) — nothing to judge; add one with \`git -C ${shared} remote add origin <url>\`, then re-run"; return 1
  fi
  # Origin's branches are listed before the fetch, so the fetch brings in
  # every commit the listing names.
  local rc=0 sym db heads
  rc=0; sym="$(git -C "$shared" ls-remote --symref origin HEAD 2>"$ERRFILE")" || rc=$?
  if (( rc != 0 )); then network_failure "$rc" "$shared" git ls-remote --symref origin HEAD; warn "$(cat "$ERRFILE")"; return 1; fi
  db="$(printf '%s\n' "$sym" | sed -n 's#^ref: refs/heads/\(.*\)[[:space:]]HEAD$#\1#p' | head -n 1)"
  if [[ -z "$db" ]]; then warn "origin reports no default branch — run \`git ls-remote --symref origin HEAD\` in ${shared}"; return 1; fi
  rc=0; heads="$(git -C "$shared" ls-remote --heads origin 2>"$ERRFILE")" || rc=$?
  if (( rc != 0 )); then network_failure "$rc" "$shared" git ls-remote --heads origin; warn "$(cat "$ERRFILE")"; return 1; fi
  local -a fetch_args=(fetch --quiet origin)
  (( dry )) || fetch_args=(fetch --quiet --prune origin)
  rc=0; git -C "$shared" "${fetch_args[@]}" 2>"$ERRFILE" || rc=$?
  if (( rc != 0 )); then network_failure "$rc" "$shared" git "${fetch_args[@]}"; warn "$(cat "$ERRFILE")"; return 1; fi

  local could_not_check="" protected="" prs=""
  if ! command -v gh >/dev/null; then
    could_not_check="the GitHub CLI (gh) is not on PATH, so open pull requests and protected branches cannot be checked — install gh and run \`gh auth login\`"
  elif ! resolve_repo "$shared"; then
    could_not_check="$(cat "$ERRFILE")"
  else
    rc=0
    protected="$(GH_PROMPT_DISABLED=1 gh api "repos/${REPO}/branches?protected=true&per_page=100" --paginate --jq '.[].name' 2>"$ERRFILE")" || rc=$?
    if (( rc != 0 )); then
      network_failure "$rc" "$shared" gh api "repos/${REPO}/branches?protected=true"
      could_not_check="$(cat "$ERRFILE")"
    elif ! prs="$(open_pr_heads "$shared")"; then
      could_not_check="$(cat "$ERRFILE")"
    fi
  fi

  if [[ -z "$could_not_check" ]]; then
    local default_tip line branch tip
    default_tip="$(printf '%s\n' "$heads" | awk -v r="refs/heads/$db" '$2 == r {print $1}')"
    while IFS=$'\t' read -r tip line; do
      [[ -n "$tip" ]] || continue
      branch="${line#refs/heads/}"
      [[ "$branch" == "$db" ]] && continue
      rc=0; listed "$branch" "$protected" || rc=$?
      case "$rc" in
        0) continue ;;
        1) ;;
        *) row failed "$branch" "cannot check it against the protected branches, so it was kept"; continue ;;
      esac
      rc=0; listed "$branch" "$prs" || rc=$?
      case "$rc" in
        0) continue ;;
        1) ;;
        *) row failed "$branch" "cannot check it against the open pull requests, so it was kept"; continue ;;
      esac
      decide_remote "$shared" "$db" "$default_tip" "$branch" "$tip" "$dry"
    done <<<"$heads"
  fi

  rc=0
  python3 - "$shared" "$db" "$dry" "$ROWS" "$could_not_check" "$REPO" <<'PY' || rc=$?
import json, shlex, sys
shared, db, dry, rows_path, cannot, repo = sys.argv[1], sys.argv[2], sys.argv[3] == "1", sys.argv[4], sys.argv[5], sys.argv[6]
out = {"shared": shared, "default_branch": db, "dry_run": dry, "deleted": [], "questionable": [], "kept": [],
       "could_not_check": cannot or None, "failed": []}
with open(rows_path, "rb") as handle:
    fields = handle.read().decode("utf-8", "surrogateescape").split("\0")
if fields and fields[-1] == "":
    fields.pop()
for i in range(0, len(fields), 6):
    kind, branch, a, b, c, d = fields[i:i + 6]
    if kind == "deleted":
        out["deleted"].append(branch)
    elif kind == "kept":
        out["kept"].append({"branch": branch, "reason": a})
    elif kind == "questionable":
        where = shlex.quote(shared)
        out["questionable"].append({"branch": branch, "ahead": int(a), "age_hours": int(b), "author": c,
                                    "open_pr": "gh pr create --repo {} --head {}".format(shlex.quote(repo), shlex.quote(branch)),
                                    "delete": "git -C {} push --force-with-lease={} origin --delete {}".format(
                                        where, shlex.quote("refs/heads/{}:{}".format(branch, d)),
                                        shlex.quote("refs/heads/" + branch))})
    else:
        out["failed"].append({"target": branch, "error": a})
print(json.dumps(out, sort_keys=True))
sys.exit(2 if out["failed"] or out["could_not_check"] else 0)
PY
  return "$rc"
}

# Is <name> a whole line of <list>? grep's own exit: 0 listed, 1 not listed,
# anything else a failure the caller keeps the branch on.
listed() { # <name> <list>
  grep -qxF -e "$1" <<<"$2"
}

# The final gate, before a deletion (or its preview) and before a listing:
# origin as it is now. 0 when the branch's tip, the default branch's name,
# its open pull requests and its protection are as judged, and the default's
# tip still settles the verdict (a merged tip still contained; an unmerged
# one against an unmoved default). 1 when anything changed, 2 when a read
# failed (ERRFILE says why).
origin_gate() { # <shared> <default> <default-tip> <branch> <tip> <merged|unmerged>
  local shared="$1" db="$2" default_tip="$3" branch="$4" tip="$5" verdict="$6"
  local now_tip now_default now_prs now_protected now_db mrc=0
  if ! now_tip="$(remote_tip "$shared" "$branch")" || ! now_default="$(remote_tip "$shared" "$db")" \
    || ! now_prs="$(open_pr_heads "$shared" "$branch")" || ! now_protected="$(branch_protected "$shared" "$branch")" \
    || ! now_db="$(remote_default "$shared")"; then
    return 2
  fi
  if [[ "$now_tip" != "$tip" || -n "$now_prs" || "$now_protected" != false || "$now_db" != "$db" ]]; then
    return 1
  fi
  [[ "$now_default" == "$default_tip" ]] && return 0
  [[ "$verdict" == merged ]] || return 1
  # The moved default may hold commits the fetch did not bring in.
  git -C "$shared" fetch --quiet origin "refs/heads/${db}" 2>"$ERRFILE" || mrc=$?
  if (( mrc != 0 )); then network_failure "$mrc" "$shared" git fetch --quiet origin "refs/heads/${db}"; return 2; fi
  git -C "$shared" merge-base --is-ancestor "$tip" "$now_default" 2>"$ERRFILE" || mrc=$?
  case "$mrc" in
    0) return 0 ;;
    1) return 1 ;;
    *) return 2 ;;
  esac
}

# Decide one branch on origin; delete it (unless dry-run) or record why not.
decide_remote() { # <shared> <default> <default-tip> <branch> <tip> <dry 0|1>
  local shared="$1" db="$2" default_tip="$3" branch="$4" tip="$5" dry="$6" rc=0
  git -C "$shared" merge-base --is-ancestor "$tip" "$default_tip" 2>"$ERRFILE" || rc=$?
  case "$rc" in
    0)
      rc=0; origin_gate "$shared" "$db" "$default_tip" "$branch" "$tip" merged || rc=$?
      case "$rc" in
        0) ;;
        1) row kept "$branch" changed; return 0 ;;
        *) row failed "$branch" "cannot re-read origin before deleting it, so it was kept: $(cat "$ERRFILE")"; return 0 ;;
      esac
      if (( dry )); then row deleted "$branch"; return 0; fi
      rc=0
      git -C "$shared" push --quiet "--force-with-lease=refs/heads/${branch}:${tip}" origin --delete "refs/heads/${branch}" 2>"$ERRFILE" || rc=$?
      if (( rc != 0 )); then
        # A lease refused because the branch moved is the safety working, not
        # a failure: origin's tip, read again, tells the two apart.
        local after_tip
        if after_tip="$(remote_tip "$shared" "$branch")" && [[ "$after_tip" != "$tip" ]]; then
          row kept "$branch" changed; return 0
        fi
        network_failure "$rc" "$shared" git push "--force-with-lease=refs/heads/${branch}:${tip}" origin --delete "refs/heads/${branch}"
        row failed "$branch" "deleting it on origin failed: $(cat "$ERRFILE")"; return 0
      fi
      row deleted "$branch" ;;
    1)
      local committed author age ahead
      if ! committed="$(git -C "$shared" log -1 --format=%ct "$tip" 2>"$ERRFILE")" \
        || ! author="$(git -C "$shared" log -1 --format=%an "$tip" 2>"$ERRFILE")" \
        || ! ahead="$(git -C "$shared" rev-list --count "${default_tip}..${tip}" 2>"$ERRFILE")"; then
        row failed "$branch" "cannot read its history: $(tr '\n' ' ' < "$ERRFILE")"; return 0
      fi
      age="$(python3 -c 'import sys, time; now = float(sys.argv[1]) if sys.argv[1] else time.time(); print(int(max(0.0, now - int(sys.argv[2])) // 3600))' "${PRUNE_NOW:-}" "$committed")"
      if (( age < REMOTE_IDLE_HOURS )); then row kept "$branch" not-idle; return 0; fi
      rc=0; origin_gate "$shared" "$db" "$default_tip" "$branch" "$tip" unmerged || rc=$?
      case "$rc" in
        0) row questionable "$branch" "$ahead" "$age" "$author" "$tip" ;;
        1) row kept "$branch" changed ;;
        *) row failed "$branch" "cannot re-read origin before listing it, so it was kept: $(cat "$ERRFILE")" ;;
      esac ;;
    *) row failed "$branch" "git merge-base failed: $(tr '\n' ' ' < "$ERRFILE")" ;;
  esac
}

# Entry-point guard (rules/file-hygiene.md Standalone Scripts).
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
  main "$@"
fi

skills

herdr-foreman

bounded-run.sh

compose-briefs.sh

config.example.json

foreman-tier-check.py

foreman.sh

label-workspaces.sh

provision-worktree.sh

prune-remote-branches.sh

prune-report-caches.py

prune-worktrees.sh

resolve-gates.sh

resolve-policy-paths.sh

review-package.sh

roster.sh

round-preflight.sh

SKILL.md

start-judge-worker.sh

state-schema.md

sweep-worktrees.sh

verify-authority.sh

wait-report.sh

README.md

tile.json