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

preflight.shskills/install-reviewer/

#!/usr/bin/env bash
# Run all install-reviewer preconditions and report them as one JSON
# result. The skill invokes this before any mutation so every preflight
# failure is surfaced together, not one-at-a-time. Checks cover: git
# worktree, GitHub CLI installation + auth, packaged template presence,
# origin remote, and (mode-dependent) branch state. In override mode it also
# refuses to overwrite uncommitted local changes to any reviewer target.
#
# Usage: preflight.sh [--override]
#   --override    Upgrade existing scaffolded reviewers in place (instead
#                 of failing on their existence per the install-mode
#                 safety gate). Skips the branch-not-local /
#                 branch-not-remote checks (the upgrade branch can
#                 legitimately already exist from a prior in-flight
#                 attempt) and adds a no-dirty-target-edits check
#                 covering four clobber-states the upgrade refuses to
#                 overwrite — uncommitted edits, untracked content,
#                 symlinks, and tracked deletions (HEAD-present,
#                 working-tree-removed) — so the consumer commits,
#                 stashes, restores, or removes the local content
#                 before the scaffold replaces their files.
# Out:   one JSON object on stdout:
#          {"ok": bool,
#           "override": bool,
#           "failures": [{"check": "<name>", "reason": "<human text>"}, ...],
#           "warnings": [{"check": "<name>", "reason": "<human text>"}, ...]}
#        When ok is false, each failure includes a concrete recovery
#        command where applicable. Warnings are informational only —
#        they surface advisory findings and never set ok to false or
#        change the exit code.
# Exit:  0 if ok is true; 1 if any check fails

set -euo pipefail

OVERRIDE_MODE=0
for arg in "$@"; do
  case "$arg" in
    --override) OVERRIDE_MODE=1 ;;
    *) echo "error: unknown argument '$arg' (only --override is recognized)" >&2; exit 2 ;;
  esac
done

# jq is required for emitting the structured JSON contract documented
# above. Without this early gate the script would die at the final jq
# invocation with `jq: command not found` and the agent parsing our
# stdout would have nothing to work with. Hand-roll the missing-jq
# diagnostic so the failure still satisfies the contract — every
# other failure mode below depends on jq being present.
if ! command -v jq >/dev/null 2>&1; then
  override_json="false"
  (( OVERRIDE_MODE == 1 )) && override_json="true"
  cat <<EOF
{"ok": false, "override": ${override_json}, "failures": [{"check": "jq-installed", "reason": "jq is not installed; install with 'brew install jq' (macOS) or 'apt install jq' (Debian/Ubuntu) and re-run"}], "warnings": []}
EOF
  exit 1
fi

# If we're inside a git worktree, run from its root so the TEMPLATE path
# below resolves the same way regardless of the caller's cwd. If we're
# NOT in a worktree, the check_in_git_worktree step below will fail
# cleanly; don't exit here — we want to surface all preflight failures
# as structured JSON, not die early.
# Capture stderr, not `2>/dev/null`: git returns 128 for BOTH the ordinary
# not-a-repo case AND real faults (corrupt/unreadable repo, an unsafe-
# ownership refusal), so the exit code alone cannot tell them apart — the
# distinguisher is git's own message. `rev-parse --show-toplevel` on no repo
# emits the stable, decades-old sentinel "not a git repository"; any other
# 128 is a fault this preflight must surface, not treat as "just not in a
# repo". Matching that one fixed token is not the regex-trap
# (rules/script-delegation.md) — it is a documented tool sentinel, not
# free-form text.
repo_root=""
git_rc=0
git_err=""
git_err_file=$(mktemp) || {
  # Emit the JSON envelope + exit 1, not a stderr-only exit 2: a bare exit
  # here breaks the documented "single JSON object on stdout" contract for
  # wrappers that parse it (jq is guaranteed by the early guard).
  reason="mktemp failed — cannot capture git stderr; check TMPDIR is writable"
  override_bool="false"; (( OVERRIDE_MODE == 1 )) && override_bool="true"
  jq -nc --argjson override "$override_bool" --arg reason "$reason" \
    '{ok: false, override: $override, failures: [{check: "tmpdir-writable", reason: $reason}], warnings: []}'
  echo "preflight.sh: ${reason}" >&2
  exit 1
}
repo_root=$(git rev-parse --show-toplevel 2>"$git_err_file") || git_rc=$?
# Read only if readable (explicit branch, not `2>/dev/null` suppression), then
# clean up with a checked rm so a failed cleanup warns rather than aborting
# under this script's `set -e`.
if [[ -r "$git_err_file" ]]; then
  git_err=$(cat "$git_err_file")
fi
if ! rm -f "$git_err_file"; then
  echo "preflight.sh: warning: could not remove temp file ${git_err_file} — remove it by hand" >&2
fi
# rc 128 whose stderr carries the not-a-repo sentinel is the expected case:
# fall through, and check_in_git_worktree below reports it as structured
# JSON. Every other non-zero rc — and a 128 WITHOUT that sentinel — is a
# git fault surfaced here.
if [[ $git_rc -ne 0 ]] && ! { [[ $git_rc -eq 128 ]] && [[ "$git_err" == *"not a git repository"* ]]; }; then
  # Build the JSON with jq, never interpolation: `reason` embeds raw git
  # stderr, which can carry quotes/newlines that would break the parse (the
  # push_failure injection class). jq is guaranteed here — the missing-jq
  # guard near the top of this file exits before we reach this line — so the
  # documented single-JSON-object contract holds (rules/script-delegation.md).
  override_bool="false"
  (( OVERRIDE_MODE == 1 )) && override_bool="true"
  reason="git rev-parse --show-toplevel failed (rc=${git_rc}): ${git_err} — not the ordinary not-a-repo case; verify git is installed and the repository is readable (e.g. 'git status', check ownership/safe.directory), then re-run"
  jq -nc --argjson override "$override_bool" --arg reason "$reason" \
    '{ok: false, override: $override, failures: [{check: "git-usable", reason: $reason}], warnings: []}'
  echo "preflight.sh: ${reason}" >&2
  # exit 1, not 2: this is a populated `failures` result like every other
  # preflight check, so it stays inside the documented "0 if ok, 1 if any
  # check fails" contract — no new exit code for callers to learn.
  exit 1
fi
if [[ -n "$repo_root" ]]; then
  cd "$repo_root"
fi

if (( OVERRIDE_MODE == 1 )); then
  BRANCH="feat/upgrade-coding-policy-review"
else
  BRANCH="feat/add-coding-policy-review"
fi
TEMPLATE_DIR=".tessl/plugins/jbaruch/coding-policy/skills/install-reviewer/templates"
# `.md` shim on the marker and trigger source: tessl packaging ships only
# .md/.sh/.json/.py, so a `.yml` or extensionless template never reaches the
# installed plugin. scaffold.sh strips the shim when it writes the targets.
TEMPLATES=(
  "${TEMPLATE_DIR}/fleet-review-enabled.md"
  "${TEMPLATE_DIR}/review-trigger.yml.md"
  "${TEMPLATE_DIR}/copilot-instructions.md"
)
TARGETS=(
  ".github/fleet-review-enabled"
  ".github/workflows/review-trigger.yml"
  ".github/copilot-instructions.md"
)

declare -a failures=()
declare -a warnings=()

# Build the JSON object with jq, never string interpolation: a reason can
# carry raw command output (a version string, a git error), and a quote,
# backslash, or newline in it would break the `jq --argjson failures` parse
# at emit time and drop the whole documented JSON contract. jq escapes it.
push_failure() {
  failures+=("$(jq -nc --arg check "$1" --arg reason "$2" '{check: $check, reason: $reason}')")
}

push_warning() {
  warnings+=("$(jq -nc --arg check "$1" --arg reason "$2" '{check: $check, reason: $reason}')")
}

check_in_git_worktree() {
  git rev-parse --git-dir >/dev/null 2>&1 || \
    push_failure "in-git-worktree" "Not inside a git worktree — run the skill from the root of the consumer repo's git checkout"
}

check_origin_remote() {
  git remote get-url origin >/dev/null 2>&1 || \
    push_failure "origin-remote" "No git remote named 'origin' — add one with 'git remote add origin <url>' before re-running (the push step assumes origin exists)"
}

check_gh_installed() {
  command -v gh >/dev/null 2>&1 || \
    push_failure "gh-installed" "GitHub CLI not found on PATH — install from https://cli.github.com/"
}

check_gh_authenticated() {
  gh auth status >/dev/null 2>&1 || \
    push_failure "gh-authenticated" "GitHub CLI not authenticated — run 'gh auth login'"
}

check_templates_present() {
  local missing=()
  for t in "${TEMPLATES[@]}"; do
    [[ -f "$t" ]] || missing+=("$t")
  done
  if [[ ${#missing[@]} -gt 0 ]]; then
    push_failure "templates-present" "Template(s) not found: ${missing[*]} — run 'tessl install jbaruch/coding-policy' first"
  fi
}

check_branch_not_local() {
  # show-ref: 0 present, 1 absent (both expected), >1 a git fault surfaced
  # as its own failure rather than read as "absent, all clear"
  # (rules/error-handling.md — expected non-result vs tool failure).
  local rc=0
  git show-ref --verify --quiet "refs/heads/${BRANCH}" || rc=$?
  case "$rc" in
    0) push_failure "branch-not-local" "Local branch '${BRANCH}' already exists — delete with: git branch -d '${BRANCH}' (refuses if unmerged); or rename with: git branch -m '${BRANCH}' '${BRANCH}.bak' before re-running" ;;
    1) ;;  # absent: clean
    *) push_failure "branch-not-local" "'git show-ref refs/heads/${BRANCH}' failed (rc=${rc}) — cannot verify branch state; the repository may be unreadable, check 'git status' and re-run" ;;
  esac
}

check_branch_not_remote() {
  # ls-remote --exit-code: 0 present, 2 absent (both expected), other a
  # network/auth/tool fault surfaced rather than read as "absent".
  local rc=0
  git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null 2>&1 || rc=$?
  case "$rc" in
    0) push_failure "branch-not-remote" "Remote branch 'origin/${BRANCH}' already exists — delete with 'git push origin --delete ${BRANCH}' or rename before re-running" ;;
    2) ;;  # absent on remote: clean
    *) push_failure "branch-not-remote" "'git ls-remote origin ${BRANCH}' failed (rc=${rc}) — cannot verify remote branch state; likely a network or auth issue, resolve and re-run" ;;
  esac
}

# Override-mode safety check: refuse to upgrade if the consumer has dirty
# working-tree state on any reviewer target the upgrade overwrites and stages
# (the review-codex.yml workflow, the .github/codex-review/* scripts, and
# .github/copilot-instructions.md). Each is overwritten wholesale, so a
# consumer's unrelated pending edits would be swept into the reviewer-upgrade
# commit. A never-tracked, not-yet-created target (fresh install/upgrade) has
# nothing to clobber and is not flagged. Mirrors how `git pull` refuses to
# overwrite uncommitted changes — forces the consumer to commit, stash, or
# remove the local content before the scaffold replaces their files. "Dirty"
# here covers four states the override could clobber:
#   - symlink at the target path (working or broken); refuse outright so
#     `cp`/compile/append never follows or replaces an unexpected link
#   - tracked file with staged or unstaged edits relative to HEAD
#   - untracked regular file at the target path (consumer hand-rolled a
#     reviewer that was never staged); without this case the override
#     would silently clobber an intentional local file
#   - tracked file deleted from the working tree (`rm` or `git rm`) but
#     still present at HEAD; without this case scaffold.sh re-creates
#     the file and silently clobbers the consumer's intentional removal.
# Classify a single target's working-tree state, echoing a short reason
# when it is one the scaffold/commit flow could clobber or wrongly stage,
# and nothing for a clean tracked file or a path that neither exists nor
# is tracked at HEAD. Pure inspection — no mutation, always exits 0.
classify_target_dirty() {
  local t="$1"
  # `-e` follows symlinks, so a broken symlink (target nonexistent)
  # returns false; `-L` is true for any symlink, broken or not. The OR
  # catches every form of "something is at this path".
  if [[ ! -e "$t" && ! -L "$t" ]]; then
    # Nothing at this path in the working tree. If HEAD still tracks one
    # there the consumer either `rm`'d it (missing from working tree,
    # present in index + HEAD) or `git rm`'d it (missing from working tree
    # AND index, still in HEAD). `git diff --diff-filter=D HEAD` catches
    # both because it compares HEAD against the working tree. `--quiet`
    # exits 0 when there's no diff and 1 when there is. Branch on the code:
    # 1 is "deleted vs HEAD" (the finding), but >1 is a real git fault, and
    # `if ! ...` would collapse both and mislabel the fault "tracked
    # deletion" (rules/error-handling.md — distinguish an expected
    # non-result from a tool failure).
    local d_rc=0 d_err
    d_err=$(git diff --quiet --diff-filter=D HEAD -- "$t" 2>&1) || d_rc=$?
    case "$d_rc" in
      0) ;;                       # no diff: not deleted
      1) echo "tracked deletion" ;;
      # rc >1: capture and surface git's own message; the reason disclaims
      # the dirty-target framing the caller's recovery text assumes, since a
      # git fault is not something you commit or stash away
      *) echo "git fault (not a local edit) — 'git diff' failed rc=${d_rc} on ${t}: ${d_err}; inspect the repo (git status, safe.directory/ownership)" ;;
    esac
    return 0
  fi
  if [[ -L "$t" ]]; then
    # Symlinks (working or broken) get their own diagnostic — falling
    # through to "untracked" would mislabel a broken symlink. scaffold.sh
    # refuses symlinks too; this just surfaces it earlier.
    echo "symlink target"
  elif git ls-files --error-unmatch -- "$t" >/dev/null 2>&1; then
    # Tracked: flag uncommitted edits vs HEAD. Same rc branch as the
    # deletion probe above — 1 is dirty (the finding), >1 is a git fault
    # that `if ! ...` would mislabel "uncommitted edits".
    local e_rc=0 e_err
    e_err=$(git diff --quiet HEAD -- "$t" 2>&1) || e_rc=$?
    case "$e_rc" in
      0) ;;                       # clean
      1) echo "uncommitted edits" ;;
      *) echo "git fault (not a local edit) — 'git diff' failed rc=${e_rc} on ${t}: ${e_err}; inspect the repo (git status, safe.directory/ownership)" ;;
    esac
  else
    # Untracked regular file at the target path.
    echo "untracked"
  fi
}

check_no_dirty_target_edits() {
  local dirty=() t reason
  for t in "${TARGETS[@]}"; do
    reason=$(classify_target_dirty "$t")
    [[ -n "$reason" ]] && dirty+=("$t ($reason)")
  done
  if [[ ${#dirty[@]} -gt 0 ]]; then
    push_failure "no-dirty-target-edits" "--override refuses to overwrite local changes in: ${dirty[*]} — commit, stash, restore, or remove these first, then re-run"
  fi
}

main() {
  check_in_git_worktree
  check_gh_installed
  # gh-cli-dependent checks only make sense if gh is present — otherwise they
  # emit follow-on failures that can't succeed until gh is installed first.
  if command -v gh >/dev/null 2>&1; then
    check_gh_authenticated
  fi
  check_templates_present
  # Remaining checks depend on a git worktree with origin; skip if either is missing
  # so we don't leak confusing git-error diagnostics on top of the real failures.
  if git rev-parse --git-dir >/dev/null 2>&1; then
    check_origin_remote
    if (( OVERRIDE_MODE == 1 )); then
      # Override mode: the upgrade branch may legitimately exist locally
      # (from a prior in-flight upgrade) or remotely (from an open
      # upgrade PR). Skip the branch-clear checks and instead refuse if
      # the consumer's working tree has uncommitted changes to the
      # target files we're about to replace.
      check_no_dirty_target_edits
    else
      # Install mode: the install branch must NOT already exist locally or
      # remotely — Step 2's overwrite refusal in the skill assumes a fresh
      # branch, and scaffold.sh refuses any pre-existing reviewer target.
      check_branch_not_local
      if git remote get-url origin >/dev/null 2>&1; then
        check_branch_not_remote
      fi
    fi
  fi

  local failures_json
  if [[ ${#failures[@]} -eq 0 ]]; then
    failures_json='[]'
  else
    failures_json="[$(IFS=,; echo "${failures[*]}")]"
  fi

  local warnings_json
  if [[ ${#warnings[@]} -eq 0 ]]; then
    warnings_json='[]'
  else
    warnings_json="[$(IFS=,; echo "${warnings[*]}")]"
  fi

  local ok="true"
  local rc=0
  if [[ ${#failures[@]} -gt 0 ]]; then
    ok="false"
    rc=1
  fi

  local override_json="false"
  (( OVERRIDE_MODE == 1 )) && override_json="true"

  jq -n --argjson ok "$ok" --argjson override "$override_json" --argjson failures "$failures_json" --argjson warnings "$warnings_json" \
    '{ok: $ok, override: $override, failures: $failures, warnings: $warnings}'

  # Per rules/script-delegation.md ("self-error-handling: exit non-zero on
  # failure, write a diagnostic message to stderr"), on failure also emit a
  # short diagnostic to stderr so a caller that only watches stderr notices
  # the failure rather than relying on structured-stdout parsing.
  if [[ $rc -ne 0 ]]; then
    echo "preflight: ${#failures[@]} precondition(s) failed — see the 'failures' array in stdout for recovery commands" >&2
  fi
  exit "$rc"
}

[[ "${BASH_SOURCE[0]}" == "${0}" ]] && main "$@"

README.md

tile.json