| 1 | #!/usr/bin/env bash |
| 2 | # Report current CI status for a PR head commit. |
| 3 | # |
| 4 | # Usage: |
| 5 | # ci-status.sh <pr-number> # human-readable summary |
| 6 | # ci-status.sh <pr-number> --json # raw JSON |
| 7 | # |
| 8 | # This is read-only. Use it BEFORE every push to find FAILURES that must |
| 9 | # be addressed in the same push. Do NOT wait for in-progress checks |
| 10 | # between iterations -- the next push will trigger a fresh CI run on the |
| 11 | # new code, which is what matters. See SKILL.md Step 4b for the policy. |
| 12 | |
| 13 | set -euo pipefail |
| 14 | |
| 15 | # shellcheck source=./_lib.sh |
| 16 | # shellcheck disable=SC1091 |
| 17 | source "$(dirname "$0")/_lib.sh" |
| 18 | pr_require_gh |
| 19 | |
| 20 | PR="${1:?usage: $0 <pr-number> [--json]}" |
| 21 | pr_require_numeric "${PR}" |
| 22 | JSON=0 |
| 23 | [[ "${2:-}" == "--json" ]] && JSON=1 |
| 24 | |
| 25 | SLUG="$(pr_require_slug)" |
| 26 | |
| 27 | # pr view --json statusCheckRollup gives the per-check breakdown. |
| 28 | data="$(gh pr view "${PR}" --repo "${SLUG}" --json statusCheckRollup,headRefOid,mergeStateStatus)" |
| 29 | |
| 30 | if (( JSON )); then |
| 31 | printf '%s\n' "${data}" |
| 32 | exit 0 |
| 33 | fi |
| 34 | |
| 35 | head_sha="$(jq -r '.headRefOid' <<< "${data}" | head -c 10)" |
| 36 | merge_state="$(jq -r '.mergeStateStatus' <<< "${data}")" |
| 37 | |
| 38 | echo "Head: ${head_sha} Merge state: ${merge_state}" |
| 39 | echo |
| 40 | |
| 41 | # Group checks by conclusion / status. |
| 42 | jq -r ' |
| 43 | .statusCheckRollup |
| 44 | | map(. + {key: ((.conclusion // .status) // "PENDING")}) |
| 45 | | group_by(.key) |
| 46 | | map({key: .[0].key, count: length, names: [.[].name // .[].context]}) |
| 47 | | sort_by(.key) |
| 48 | | .[] |
| 49 | | "\(.key)\t\(.count)\t\((.names | sort | unique | join(", ")[0:200]))" |
| 50 | ' <<< "${data}" | column -t -s $'\t' |
| 51 | |
| 52 | echo |
| 53 | total=$(jq '.statusCheckRollup | length' <<< "${data}") |
| 54 | fail=$(jq '[.statusCheckRollup[] | select((.conclusion // "")|test("FAILURE|TIMED_OUT|CANCELLED"))] | length' <<< "${data}") |
| 55 | running=$(jq '[.statusCheckRollup[] | select((.status // "")=="IN_PROGRESS" or (.status // "")=="QUEUED")] | length' <<< "${data}") |
| 56 | echo "Total checks: ${total} Failing: ${fail} Running: ${running}" |
| 57 | |
| 58 | if (( running > 0 )); then |
| 59 | # Exit 2 is informational, not a "do not push" signal. The next push |
| 60 | # will start a fresh CI run on top of the new code -- that's what we |
| 61 | # actually want to verify. The skill's policy is to push anyway. |
| 62 | echo -e "${PR_GRAY}Note: ${running} check(s) still running; the next push will start a fresh CI run.${PR_NC}" >&2 |
| 63 | exit 2 |
| 64 | fi |
| 65 | if (( fail > 0 )); then |
| 66 | echo -e "${PR_RED}WARNING: ${fail} check(s) failing. Address these BEFORE pushing.${PR_NC}" >&2 |
| 67 | exit 3 |
| 68 | fi |
| 69 | exit 0 |