135 lines
4.2 KiB
Bash
135 lines
4.2 KiB
Bash
#!/bin/sh
|
|
# Audit open Codeberg issues for the repository's tracker-discipline invariants.
|
|
set -eu
|
|
|
|
ROOT=$(cd "$(dirname "$0")/.." && pwd)
|
|
API=${ISSUES_API:-https://codeberg.org/api/v1/repos/Mihaela/rules-swift/issues?state=open&limit=50}
|
|
LABELS_API=${LABELS_API:-https://codeberg.org/api/v1/repos/Mihaela/rules-swift/labels?limit=100}
|
|
TMP=
|
|
LABEL_TMP=
|
|
PHANTOM=$(mktemp "${TMPDIR:-/tmp}/rules-swift-phantom.XXXXXX")
|
|
trap 'rm -f "$TMP" "$LABEL_TMP" "$PHANTOM"' EXIT HUP INT TERM
|
|
|
|
if ! command -v jq >/dev/null 2>&1; then
|
|
echo "issue-body gate: jq is required" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if { [ -z "${ISSUES_JSON:-}" ] || [ -z "${LABELS_JSON:-}" ]; } \
|
|
&& ! command -v curl >/dev/null 2>&1; then
|
|
echo "issue-body gate: curl is required for live tracker data" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ -n "${ISSUES_JSON:-}" ]; then
|
|
data=$ISSUES_JSON
|
|
else
|
|
TMP=$(mktemp "${TMPDIR:-/tmp}/rules-swift-issues.XXXXXX")
|
|
curl -fsSL "$API" > "$TMP"
|
|
data=$TMP
|
|
fi
|
|
|
|
if [ -n "${LABELS_JSON:-}" ]; then
|
|
labels=$LABELS_JSON
|
|
else
|
|
LABEL_TMP=$(mktemp "${TMPDIR:-/tmp}/rules-swift-labels.XXXXXX")
|
|
curl -fsSL "$LABELS_API" > "$LABEL_TMP"
|
|
labels=$LABEL_TMP
|
|
fi
|
|
|
|
fail=0
|
|
count=$(jq 'length' "$data")
|
|
label_count=$(jq 'length' "$labels")
|
|
|
|
label_drift=$(jq -nr \
|
|
--slurpfile expected "$ROOT/.github/labels.json" \
|
|
--slurpfile actual "$labels" '
|
|
$expected[0][] as $want
|
|
| ($actual[0] | map(select(.name == $want.name)) | first) as $got
|
|
| select($got == null or (($got.color | ascii_downcase) != $want.color))
|
|
| if $got == null then
|
|
"missing: \($want.name)"
|
|
else
|
|
"wrong color: \($want.name) expected=\($want.color) actual=\($got.color)"
|
|
end
|
|
')
|
|
if [ -n "$label_drift" ]; then
|
|
echo "ISSUE BODY FAIL (repository label drift):" >&2
|
|
echo "$label_drift" >&2
|
|
fail=1
|
|
fi
|
|
|
|
missing_status=$(jq -r '
|
|
.[]
|
|
| (.body // "") as $body
|
|
| ($body | [scan("(?m)^##+ .+$")][0] // "") as $first_heading
|
|
| select((($first_heading | test("^##+ Status")) and
|
|
($body | test("(?m)^##+ Status.*\\n+20[0-9]{2}-[0-9]{2}-[0-9]{2}:"))) | not)
|
|
| .number
|
|
' "$data")
|
|
if [ -n "$missing_status" ]; then
|
|
echo "ISSUE BODY FAIL (missing dated Status block): $missing_status" >&2
|
|
fail=1
|
|
fi
|
|
|
|
line_anchors=$(jq -r '
|
|
.[]
|
|
| select((.body // "") | test("#[Ll][0-9]+|[A-Za-z0-9._/-]+:[0-9]+"))
|
|
| .number
|
|
' "$data")
|
|
if [ -n "$line_anchors" ]; then
|
|
echo "ISSUE BODY FAIL (line-number anchor): $line_anchors" >&2
|
|
fail=1
|
|
fi
|
|
|
|
stale_refs=$(jq -r '
|
|
. as $issues
|
|
| [$issues[].number] as $open
|
|
| $issues[] as $issue
|
|
| (($issue.body // "")
|
|
| scan("(?i)(?:blocked on|pending in|depends on|after|gated on|awaits|waiting on)[^\\n#]*#[0-9]+")
|
|
| capture("#(?<number>[0-9]+)").number
|
|
| tonumber) as $reference
|
|
| select(($open | index($reference)) == null)
|
|
| "\($issue.number) -> #\($reference)"
|
|
' "$data")
|
|
if [ -n "$stale_refs" ]; then
|
|
echo "ISSUE BODY FAIL (blocker reference is not open):" >&2
|
|
echo "$stale_refs" >&2
|
|
fail=1
|
|
fi
|
|
|
|
priority_drift=$(jq -r '
|
|
.[]
|
|
| ((.body // "") | test("(?mi)^##+ Priority[ \\t]*\\r?\\n+high[ \\t]*$")) as $high
|
|
| ([.labels[]?.name] | index("priority: high") != null) as $labelled
|
|
| select($high != $labelled)
|
|
| .number
|
|
' "$data")
|
|
if [ -n "$priority_drift" ]; then
|
|
echo "ISSUE BODY FAIL (priority body/label drift): $priority_drift" >&2
|
|
fail=1
|
|
fi
|
|
|
|
jq -r '.[] as $issue | (($issue.body // "") | scan("`[^`]+`") | gsub("^`|`$"; "")) as $path | [$issue.number, $path] | @tsv' "$data" \
|
|
| while IFS="$(printf '\t')" read -r number path; do
|
|
case "$path" in
|
|
*' '*|*'<'*|*'>'*|*'('*|*')'*|http:*|https:*|'' ) continue ;;
|
|
esac
|
|
case "$path" in
|
|
*/*|*.md|*.swift|*.yml|*.yaml|*.json|*.sh|*.txt|*.toml|*.mmd|*.png|*.jpg|*.jpeg|*.pdf|LICENSE|VERSION)
|
|
[ -e "$ROOT/$path" ] || printf '%s\t%s\n' "$number" "$path"
|
|
;;
|
|
esac
|
|
done > "$PHANTOM"
|
|
|
|
phantoms=$(cat "$PHANTOM")
|
|
if [ -n "$phantoms" ]; then
|
|
echo "ISSUE BODY FAIL (phantom backtick path):" >&2
|
|
echo "$phantoms" >&2
|
|
fail=1
|
|
fi
|
|
|
|
[ "$fail" = 0 ] || exit 1
|
|
echo "issue-body gate clean ($count open items, $label_count labels)"
|