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

check-leftover-worktrees.shhooks/

#!/usr/bin/env bash
# Clean this session's repository at session start; report what only the
# operator can decide.
#
# Worktrees and branches pile up faster than anyone removes them by hand. This
# hook runs the two owner scripts for the session's repository, live, so
# everything recoverable from origin goes without a word: idle clean worktrees
# origin holds, local branches origin holds or merged, and branches on origin
# merged with no open pull request. The decisions are the owner scripts', never
# this hook's (rules/script-as-black-box.md):
#   - skills/herdr-foreman/prune-worktrees.sh — worktrees and local branches
#   - skills/herdr-foreman/prune-remote-branches.sh — branches on origin
# What they keep because it exists nowhere else is listed for the operator:
# idle dirty or unpushed worktrees, idle local branches with unpushed commits,
# and stale branches on origin merged nowhere with no pull request. Nothing
# else is listed: no removals, no counts, no other repository.
#
# Never acts in a Herdr worker session (HERDR_ENV set, even empty, in a
# linked worktree): workers never delete (skills/herdr-foreman/references/team-operation.md
# Writers and Checkouts). In portable mode (SESSION_START_MODE=portable, set
# by hooks/session-start.sh under `tessl hook run`, which strips HERDR_ENV) a
# linked worktree may be a worker's, so neither script runs there at all, and
# a proven main checkout runs both --dry-run: the list is the same, and
# nothing is deleted.
#
# Contract:
#   stdin : consensus SessionStart JSON — not read.
#   stdout: at most one JSON object {"additionalContext": "<status>"}. The
#           status holds up to two "Session-start status — " paragraphs: the
#           items awaiting the operator's decision, each with its command, and
#           one "could not check" line naming every owner script that failed,
#           ran out of time, reported an undecided item, or could not reach
#           gh, with the command to rerun it. A missing git or python3 is
#           that line too, a fixed JSON string printed without either tool.
#           Silent when neither applies, outside a repository, in a
#           repository without an origin remote, in a bare repository, in
#           a Herdr worker session, and under tessl in a linked worktree.
#   stderr: the owner scripts' diagnostics, relayed, plus this hook's warnings.
#   exit  : always 0 (a failure is the "could not check" line, never silence
#           and never a failed session start).
#   env   : LEFTOVER_BUDGET_SEC overrides BUDGET_SEC; WORKTREE_ROOT and the
#           PRUNE_* variables pass through to the owner scripts.
set -euo pipefail

#: Wall-clock seconds for both owner scripts together. Session start waits on
#: this hook, and the network is the part that can hang.
BUDGET_SEC="${LEFTOVER_BUDGET_SEC:-40}"

warn() { printf 'check-leftover-worktrees: %s\n' "$1" >&2; }

#: The scratch directory, global so the RETURN trap can name a function
#: instead of interpolating a path into shell source.
SCRATCH=""

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

# Print a could-not-check status that needs no tool to encode. <why> is one
# of this script's own fixed ASCII sentences, never input: no character in
# it needs JSON escaping.
static_cannot_check() { # <fixed-why>
  printf '{"additionalContext": "Session-start status \\u2014 could not check this repository for leftover worktrees and branches: %s"}\n' "$1"
}

# Print the could-not-check status alone, for a failure before any result.
# When python3 cannot encode <why>, the fixed status still reaches the session.
cannot_check() { # <why>
  if ! python3 -c 'import json, sys; print(json.dumps({"additionalContext": sys.argv[1]}))' \
      "Session-start status — could not check this repository for leftover worktrees and branches: $1"; then
    warn "python3 could not encode the status (${1}) — reporting the fixed status instead"
    static_cannot_check "python3 failed; run this hook by hand to see why."
  fi
}

# Relay one owner script's stderr, each line prefixed with this hook's name.
relay() { # <file>
  local line
  [[ -s "$1" ]] || return 0
  while IFS= read -r line || [[ -n "$line" ]]; do
    if [[ -n "$line" ]]; then warn "$line"; fi
  done < "$1"
  return 0
}

main() {
  if ! command -v git >/dev/null; then
    warn "git not found on PATH — install it so session start can clean this repository"
    static_cannot_check "git is not on PATH; install it, then start a new session."
    return 0
  fi
  if ! command -v python3 >/dev/null; then
    warn "python3 not found on PATH — install it so session start can clean this repository"
    static_cannot_check "python3 is not on PATH; install it, then start a new session."
    return 0
  fi
  # `rev-parse` exits 128 both outside a repository and for one it cannot
  # read; only the walked-up-and-found-nothing message is silent.
  local err rc=0
  err="$(git rev-parse --git-dir 2>&1 >/dev/null)" || rc=$?
  if (( rc != 0 )); then
    case "$err" in
      *"or any of the parent directories"*) ;;
      *) cannot_check "git cannot read it (${err:-git exited ${rc} silently}); run \`git status\` here to see why." ;;
    esac
    return 0
  fi

  local git_dir common_dir
  rc=0
  git_dir="$(git rev-parse --path-format=absolute --git-dir 2>&1)" || rc=$?
  if (( rc == 0 )); then common_dir="$(git rev-parse --path-format=absolute --git-common-dir 2>&1)" || rc=$?; fi
  if (( rc != 0 )); then
    cannot_check "\`git rev-parse --git-common-dir\` exited ${rc}; run it here to see why."
    return 0
  fi
  # A worker session: acting here would break the rule the worker runs under.
  if [[ -n "${HERDR_ENV+x}" && "$git_dir" != "$common_dir" ]]; then
    return 0
  fi
  # Under tessl the environment is stripped, so an unset HERDR_ENV proves
  # nothing: a linked worktree may be a worker's, and a worker neither fetches
  # nor deletes. Only a proven main checkout runs the owner scripts there,
  # and only --dry-run (the rule hooks/check-git-sync.sh applies).
  if [[ "${SESSION_START_MODE:-native}" == portable && "$git_dir" != "$common_dir" ]]; then
    return 0
  fi

  # Exit 2 is git's "no such remote": nothing is recoverable from origin, so
  # nothing is judged.
  rc=0
  err="$(git remote get-url origin 2>&1 >/dev/null)" || rc=$?
  case "$rc" in
    0) ;;
    2) return 0 ;;
    *) cannot_check "\`git remote get-url origin\` exited ${rc} (${err}); run it here to see why."; return 0 ;;
  esac

  # The shared checkout is the first entry of the worktree list, whichever
  # worktree this session sits in; a bare repository has no checkout to clean.
  local shared list list_err
  rc=0
  list="$(mktemp -d)" || { cannot_check "mktemp failed; make ${TMPDIR:-/tmp} writable."; return 0; }
  # Captured to a file and git's own exit checked before anything is parsed,
  # as hooks/stop-handoff-hygiene.sh reads it: a git failure is named as one,
  # never folded into the parser's verdict.
  git worktree list --porcelain -z >"${list}/out" 2>"${list}/err" || rc=$?
  list_err="$(cat "${list}/err")"
  if (( rc != 0 )); then
    if ! rm -rf "$list"; then warn "could not remove ${list} — delete it by hand"; fi
    cannot_check "\`git worktree list --porcelain -z\` exited ${rc} (${list_err}); run it here to see why."
    return 0
  fi
  shared="$(python3 -c '
import sys
first = []
with open(sys.argv[1], "rb") as handle:
    fields = handle.read().split(b"\0")
for field in fields:
    if not field:
        break
    first.append(field)
if not first or not first[0].startswith(b"worktree "):
    sys.exit(3)
if b"bare" in first:
    sys.exit(4)
sys.stdout.buffer.write(first[0][len(b"worktree "):] + b"x")
' "${list}/out")" || rc=$?
  # The sentinel keeps a trailing newline in the path through the command
  # substitution; it comes off only here.
  shared="${shared%x}"
  if ! rm -rf "$list"; then warn "could not remove ${list} — delete it by hand"; fi
  case "$rc" in
    0) ;;
    4) return 0 ;;
    *) cannot_check "\`git worktree list --porcelain -z\` gave no worktree (parse exit ${rc}; ${list_err}); run it here to see why."; return 0 ;;
  esac

  local src dir here
  # Command substitution strips every trailing newline, so the hooks directory
  # never passes through one bare: parameter expansion derives it (#487), and a
  # sentinel carries `pwd` across the strip (#466).
  src="${BASH_SOURCE[0]}"
  case "$src" in
    */*) dir="${src%/*}" ;;
    *) dir=. ;;
  esac
  if ! here="$(CDPATH='' cd -- "${dir:-/}" && pwd && printf x)"; then
    cannot_check "the hooks directory ${dir:-/} cannot be entered; reinstall the plugin."
    return 0
  fi
  here="${here%x}"
  here="${here%$'\n'}"
  local runner="${here}/../skills/herdr-foreman/bounded-run.sh" prune="${here}/../skills/herdr-foreman/prune-worktrees.sh"
  local remote="${here}/../skills/herdr-foreman/prune-remote-branches.sh" f
  for f in "$runner" "$prune" "$remote"; do
    if [[ ! -f "$f" || ! -r "$f" ]]; then
      cannot_check "${f} is not readable; reinstall the plugin."
      return 0
    fi
  done

  SCRATCH="$(mktemp -d "${TMPDIR:-/tmp}/leftover-hook.XXXXXX")" || {
    cannot_check "no temporary directory could be created under ${TMPDIR:-/tmp}; make it writable."
    return 0
  }
  trap discard RETURN

  local -a mode_args=()
  if [[ "${SESSION_START_MODE:-native}" == portable ]]; then mode_args=(--dry-run); fi
  # Nothing may stop to ask for a credential: an unattended prompt is a hang.
  export GIT_TERMINAL_PROMPT=0 GH_PROMPT_DISABLED=1
  # Appended, never a default: a user-set command keeps BatchMode too.
  export GIT_SSH_COMMAND="${GIT_SSH_COMMAND:-ssh} -o BatchMode=yes"

  local start=$SECONDS left prune_rc=0 remote_rc=0
  bash "$runner" "$BUDGET_SEC" bash "$prune" "$shared" ${mode_args[@]+"${mode_args[@]}"} \
    >"${SCRATCH}/prune.json" 2>"${SCRATCH}/prune.err" || prune_rc=$?
  relay "${SCRATCH}/prune.err"
  left=$(( BUDGET_SEC - (SECONDS - start) ))
  if (( left > 0 )); then
    bash "$runner" "$left" bash "$remote" "$shared" ${mode_args[@]+"${mode_args[@]}"} \
      >"${SCRATCH}/remote.json" 2>"${SCRATCH}/remote.err" || remote_rc=$?
    relay "${SCRATCH}/remote.err"
  else
    remote_rc=124
  fi

  rc=0
  python3 - "$shared" "$prune" "$prune_rc" "${SCRATCH}/prune.json" \
      "$remote" "$remote_rc" "${SCRATCH}/remote.json" "${mode_args[*]-}" <<'PY' || rc=$?
import json
import os
import shlex
import sys

shared, prune, prune_rc, prune_out, remote, remote_rc, remote_out, flags = sys.argv[1:9]
items, failures = [], []


# The result shapes are the prune scripts' own; one module checks them for
# every reader (skills/herdr-foreman/foreman/prune_result.py).
sys.path.insert(0, os.path.dirname(prune))
from foreman.prune_result import prune_schema_error, remote_schema_error

SCHEMA = {prune: prune_schema_error, remote: remote_schema_error}


def load(path, rc, script):
    """The script's JSON when it answered (exit 0, or 2 with its JSON) in its
    documented shape; anything else is a failure, never a clean result."""
    rerun = "`bash {} {}{}`".format(shlex.quote(script), shlex.quote(shared), " " + flags if flags else "")
    name = script.rsplit("/", 1)[-1]
    if rc == "124":
        failures.append("{} ran past its time budget (run {})".format(name, rerun))
        return None
    try:
        with open(path, encoding="utf-8", errors="surrogateescape") as handle:
            doc = json.load(handle)
    except (OSError, ValueError):
        doc = None
    if rc not in ("0", "2") or not isinstance(doc, dict):
        failures.append("{} exited {} (run {})".format(name, rc, rerun))
        return None
    why = SCHEMA[script](doc)
    if why:
        failures.append("{} exited {} with a result holding {} (run {})".format(name, rc, why, rerun))
        return None
    if doc.get("failed"):
        failures.append("{} could not decide {} item(s) (run {})".format(name, len(doc["failed"]), rerun))
    if doc.get("could_not_check"):
        failures.append("{}: {}".format(name, doc["could_not_check"]))
    return doc


local = load(prune_out, prune_rc, prune)
if local is not None:
    for kept in local.get("worktrees_kept", []):
        where = "{} ({})".format(kept["path"], kept.get("branch") or "detached")
        if kept.get("reason") == "dirty":
            items.append("worktree {}: {} uncommitted file(s), idle {}h — `{}`".format(
                where, kept["dirty_files"], kept["age_hours"], kept["command"]))
        elif kept.get("reason") == "unpushed":
            items.append("worktree {}: {} commit(s) origin does not hold, idle {}h — `{}`".format(
                where, kept["unpushed_commits"], kept["age_hours"], kept["command"]))
    for kept in local.get("branches_kept", []):
        if kept.get("reason") == "unpushed":
            items.append("local branch {}: {} unpushed commit(s), idle {}h — `{}`".format(
                kept["branch"], kept["unpushed_commits"], kept["age_hours"], kept["command"]))

origin = load(remote_out, remote_rc, remote)
if origin is not None:
    for q in origin.get("questionable", []):
        items.append("origin branch {}: {} commit(s) not in {}, no open pull request, last commit {}h ago by {} — `{}` or `{}`".format(
            q["branch"], q["ahead"], origin.get("default_branch"), q["age_hours"], q["author"], q["open_pr"], q["delete"]))

paragraphs = []
if items:
    lines = ["Session-start status — {} item(s) in this repository hold work only the operator can decide on; "
             "nothing was touched:".format(len(items))]
    lines += ["  - " + item for item in items]
    lines.append("Raise each with the user, one at a time: push, commit, open a pull request, delete or keep.")
    paragraphs.append("\n".join(lines))
if failures:
    paragraphs.append("Session-start status — could not check this repository for leftover worktrees and branches: "
                      + "; ".join(failures) + ".")
if paragraphs:
    print(json.dumps({"additionalContext": "\n\n".join(paragraphs)}))
PY
  if (( rc != 0 )); then
    cannot_check "the owner scripts' results could not be read (exit ${rc}); run \`bash ${prune} ${shared} --dry-run\` to see them."
  fi
  return 0
}

if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
  if ! main "$@"; then
    warn "internal error — no leftover report this session; run 'bash ${BASH_SOURCE[0]}' directly to see why"
    static_cannot_check "the hook failed internally; run hooks/check-leftover-worktrees.sh by hand to see why."
  fi
  exit 0
fi

README.md

tile.json