release-kit 0.2.1

A canonical release workflow: a technology-agnostic method, per-technology bindings, and the rk CLI that lands and serves them.
Documentation
#!/usr/bin/env sh
# Protect the release lines, for the projects that keep older lines: while a
# line is alive, release/* can be neither force-pushed nor deleted by a git
# client. Maintainers keep pushing, because a cherry-pick lands on a line by
# push. This step is optional — a project with no older lines never runs it —
# and when a line dies its tags pin the commits, so the operator removes the
# protection together with the last line. A protected wildcard exposes no
# update endpoint for its levels, so a rerun that must change them is
# delete-then-create.
set -eu
: "${RK_REPO:?rk sets this; run this script through rk setup}"

project="$(printf '%s' "$RK_REPO" | sed 's|/|%2F|g')"

# The forge CLI may pretty-print JSON; stripping whitespace makes every
# pattern below hold for the compact and the pretty form alike, and no
# value a pattern touches can carry whitespace of its own.
compact() { tr -d ' \t\r\n'; }

if glab api "projects/$project/protected_branches/release%2F%2A" >/dev/null 2>&1; then
  current="$(glab api "projects/$project/protected_branches/release%2F%2A" | compact)"
  levels="$(printf '%s' "$current" | grep -o '"push_access_levels":\[[^]]*\]' || true)"
  merge_levels="$(printf '%s' "$current" | grep -o '"merge_access_levels":\[[^]]*\]' || true)"
  # Exactly one grant at maintainer level on each half: a push level of 0
  # would block the cherry-pick-by-push path this protection exists to keep.
  grants="$(printf '%s' "$levels" | grep -o '{' | grep -c . || true)"
  merge_grants="$(printf '%s' "$merge_levels" | grep -o '{' | grep -c . || true)"
  if [ "$grants" = "1" ] && printf '%s' "$levels" | grep -q '"access_level":40' \
    && [ "$merge_grants" = "1" ] && printf '%s' "$merge_levels" | grep -q '"access_level":40'; then
    glab api -X PATCH "projects/$project/protected_branches/release%2F%2A" \
      -F allow_force_push=false >/dev/null
    echo 'ok release/* protection re-asserted'
  else
    glab api -X DELETE "projects/$project/protected_branches/release%2F%2A" >/dev/null
    glab api -X POST "projects/$project/protected_branches" \
      -f 'name=release/*' -F push_access_level=40 -F merge_access_level=40 \
      -F allow_force_push=false >/dev/null
    echo 'ok release/* protection re-created with the owned grants'
  fi
else
  glab api -X POST "projects/$project/protected_branches" \
    -f 'name=release/*' -F push_access_level=40 -F merge_access_level=40 \
    -F allow_force_push=false >/dev/null
  echo 'ok release/* protected'
fi

echo 'check: prints the protected pattern with force pushes off'
glab api "projects/$project/protected_branches/release%2F%2A" | compact | grep -o '"name":"release/\*"'
glab api "projects/$project/protected_branches/release%2F%2A" | compact | grep -o '"allow_force_push":[a-z]*'