Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 27 additions & 6 deletions .claude/hooks/main-ci-status.sh
Original file line number Diff line number Diff line change
Expand Up @@ -158,32 +158,53 @@ checks_jsonl=$(gh api \
# that each define a `detect-changes` job). Mirrors the Ruby dedup in
Comment thread
justin808 marked this conversation as resolved.
# `validate_main_ci_status!` (rakelib/release.rake). Keep the two in sync.
checks_json=$(echo "${checks_jsonl}" | jq -s '
[.[] | {id, name, status, conclusion, html_url, suite_id: (.check_suite.id // .id)}]
| group_by([.suite_id, .name])
[.[] | {id, name, status, conclusion, html_url, suite_id: (.check_suite.id // .id), app_id: (.app.id // null)}]
| group_by([.suite_id, .name, .app_id])
| map(max_by(.id))
' 2>/dev/null) || fail_open "jq slurp failed"

required_json=$(gh api \
"repos/${repo_slug}/branches/main/protection/required_status_checks" \
--jq '(.contexts // []) + (.checks // [] | map(.context)) | unique' \
--jq '{contexts: (.contexts // []), checks: (.checks // [] | map({context, app_id}))}' \
2>/dev/null || echo "null")
case "${required_json}" in
\[*\]) ;;
\{*\}) ;;
*) required_json="null" ;;
esac

# Aggregate counts with jq. `success`, `skipped`, `neutral` are all "passing".
# Anything completed with another conclusion is a failure. Anything not yet
# completed is in_progress.
summary=$(echo "${checks_json}" | jq -r --argjson required_names "${required_json}" '
def app_wildcard($app_id): $app_id == null or $app_id == -1;
def check_matches($required):
Comment thread
justin808 marked this conversation as resolved.
.name == $required.context and (app_wildcard($required.app_id) or .app_id == $required.app_id);
def required_check_label:
if app_wildcard(.app_id) then .context else "\(.context) (app_id: \(.app_id))" end;
def modern_contexts: ($required_names.checks // []) | map(.context);

. as $all
| ($all | map(.name)) as $observed_names
| {
total: length,
Comment thread
justin808 marked this conversation as resolved.
passed: [.[] | select(.status == "completed" and (.conclusion | IN("success", "skipped", "neutral")))] | length,
failed: [.[] | select(.status == "completed" and (.conclusion | IN("success", "skipped", "neutral") | not))],
in_progress: [.[] | select(.status != "completed")],
missing_required: (if $required_names == null then [] else ($required_names - $observed_names) end)
missing_required: (
if $required_names == null then []
else
# NOTE: legacy contexts are intentionally app-agnostic here. Commit
# statuses are not fetched here; this hook is a fail-open display
# tool. The Ruby release gate owns app-pinned legacy enforcement.
# GitHub mirrors modern required checks into legacy `contexts`; remove
# those mirror names here so the display count matches the release gate.
((($required_names.contexts // []) - modern_contexts)
| map(select(. as $context | ($all | any(.name == $context) | not))))
+
(($required_names.checks // [])
| map(select(. as $required | ($all | any(check_matches($required)) | not)))
Comment thread
justin808 marked this conversation as resolved.
Comment thread
justin808 marked this conversation as resolved.
| map(required_check_label))
end
Comment thread
cursor[bot] marked this conversation as resolved.
)
}
| "TOTAL=\(.total)",
"PASSED=\(.passed)",
Expand Down
Loading
Loading