CtrlK
BlogDocsLog inGet started
Tessl Logo

jbaruch/coding-policy

General-purpose coding policy for Baruch's AI agents

73

Quality

91%

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

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