diff --git a/CHANGELOG.md b/CHANGELOG.md index 20b961d..06c74c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ marketplace `ref` at once. - `wiring.py` counts a pin as a trigger of a workflow that runs on a push to `main` when a `dorny/paths-filter` filter lists it and that filter's output gates a job's `if:`, so marola-app's `ci.yml` is listed again for `corpus.version` (#169). +- `deps-stack --include-steward` and `pr-label`'s `kind/deps` find Scala Steward's PRs as + `app/marola-pr-bot` on an `update/` branch, not as `app/scala-steward`, a login no Steward PR ever + had. `pr_label_kind` now takes the head branch, and `pr-label` has a `--self-test` (#167). ## v0.16.0 — 2026-10-17 diff --git a/scripts/deps-stack.sh b/scripts/deps-stack.sh index 288e27a..6f3e40c 100755 --- a/scripts/deps-stack.sh +++ b/scripts/deps-stack.sh @@ -6,7 +6,7 @@ # just deps-stack --dry-run # print every git/gh command; no push, no gh mutation # just deps-stack --resume # continue after a conflict (see below) # just deps-stack --skip 123 # drop PR #123 from the chain (repeatable) -# just deps-stack --include-steward # append scala-steward PRs after the dependabot ones +# just deps-stack --include-steward # append Scala Steward's update/* PRs after the dependabot ones # just deps-stack --from-json f.json # use this file instead of `gh pr list` (testing, no gh) # just deps-stack status # print the current chain + each PR's state # just deps-stack clean # delete deps/* chain branches whose stacked PR is MERGED @@ -22,8 +22,9 @@ # can't change, and force-pushing dependabot's own branches gets undone by its next rebase — so # dependabot's branches are never touched (--retarget-dependabot only explains this). # -# --include-steward's author:app/scala-steward query is UNVERIFIED with the default GITHUB_TOKEN -# (scala-steward.yml uses no PAT); check `gh pr list --search "is:pr author:app/scala-steward"`. +# --include-steward finds Scala Steward's PRs as author app/marola-pr-bot (marola-app's +# scala-steward.yml opens them with that App's token) on an update/ branch: the App also opens +# compiled-prompt, pointer-sync and skills PRs, so its login alone is not Steward. # # Needs `gh auth status` except for --dry-run/--from-json/--self-test. Run from the host, not ai-jail. set -euo pipefail @@ -171,7 +172,9 @@ order_prs() { # stdin: gh-pr-list-shaped JSON array -> stdout: same, filtered elif eco == "pip" then 1 elif eco == "scala-steward" then 2 else 3 end; + def pr_bot: (.author.login // "") == "app/marola-pr-bot"; map(select(.number as $n | ($skip | index($n)) == null)) + | map(select((pr_bot | not) or eco == "scala-steward")) | map(select($include_steward == 1 or eco != "scala-steward")) | map(. + {ecosystem: eco, rank: rank}) | sort_by([.rank, .number]) @@ -195,7 +198,7 @@ discover() { # -> ordered JSON array on stdout raw="$(cat "$from_json")" elif [ "$dry" -eq 1 ]; then echo "+ gh pr list --state open --search \"author:app/dependabot\" --json number,headRefName,title,baseRefName,mergeable,files" >&2 - [ "$include_steward" -eq 1 ] && echo "+ gh pr list --state open --search \"author:app/scala-steward\" --json number,headRefName,title,baseRefName,mergeable,files" >&2 + [ "$include_steward" -eq 1 ] && echo "+ gh pr list --state open --search \"author:app/marola-pr-bot\" --json number,headRefName,title,baseRefName,mergeable,files,author" >&2 echo "[]" return 0 else @@ -203,7 +206,7 @@ discover() { # -> ordered JSON array on stdout raw="$(gh pr list --state open --search "author:app/dependabot" --json number,headRefName,title,baseRefName,mergeable,files)" if [ "$include_steward" -eq 1 ]; then local steward - steward="$(gh pr list --state open --search "author:app/scala-steward" --json number,headRefName,title,baseRefName,mergeable,files)" + steward="$(gh pr list --state open --search "author:app/marola-pr-bot" --json number,headRefName,title,baseRefName,mergeable,files,author)" raw="$(jq -s 'add' <<<"$raw"$'\n'"$steward")" fi fi @@ -214,18 +217,21 @@ discover() { # -> ordered JSON array on stdout self_test() { local fixture="$script_dir/fixtures/deps-stack-prs.json" [ -f "$fixture" ] || { echo "deps-stack self-test: fixture not found: $fixture" >&2; exit 1; } - local include_steward=0 skip_nums=() # the fixture has no steward/skip entries to exercise - local ordered - ordered="$(order_prs <"$fixture")" - local got expected - got="$(jq -c '[.[].number]' <<<"$ordered")" - expected="[101,103,102]" # github-actions (101, 103) by number, then pip (102) - echo "order: $got" - jq -r '.[] | " #\(.number) \(.ecosystem) \(.headRefName)"' <<<"$ordered" - if [ "$got" != "$expected" ]; then - echo "deps-stack self-test: FAILED — order $got, expected $expected" >&2 - exit 1 - fi + local include_steward skip_nums=() # the fixture has no skip entries to exercise + local ordered got expected + # github-actions (101, 103) by number, then pip (102), then Steward's 104 only when asked for; + # 105 is marola-pr-bot off an update/ branch, never a Steward PR. + for include_steward in 0 1; do + ordered="$(order_prs <"$fixture")" + got="$(jq -c '[.[].number]' <<<"$ordered")" + expected="$([ "$include_steward" -eq 1 ] && echo "[101,103,102,104]" || echo "[101,103,102]")" + echo "order (include_steward=$include_steward): $got" + jq -r '.[] | " #\(.number) \(.ecosystem) \(.headRefName)"' <<<"$ordered" + if [ "$got" != "$expected" ]; then + echo "deps-stack self-test: FAILED — order $got, expected $expected" >&2 + exit 1 + fi + done local slug slug="$(slug_of "dependabot/github_actions/actions/checkout-5")" [ "$slug" = "actions-checkout-5" ] || { echo "deps-stack self-test: FAILED — slug_of gave '$slug', expected 'actions-checkout-5'" >&2; exit 1; } diff --git a/scripts/fixtures/deps-stack-prs.json b/scripts/fixtures/deps-stack-prs.json index c4c9bb2..e07fe01 100644 --- a/scripts/fixtures/deps-stack-prs.json +++ b/scripts/fixtures/deps-stack-prs.json @@ -22,5 +22,23 @@ "baseRefName": "main", "mergeable": "MERGEABLE", "files": [{ "path": ".github/workflows/ci.yml" }] + }, + { + "number": 104, + "headRefName": "update/sbt-2.0.10", + "title": "Update sbt to 2.0.10", + "baseRefName": "main", + "mergeable": "MERGEABLE", + "author": { "login": "app/marola-pr-bot" }, + "files": [{ "path": "project/build.properties" }] + }, + { + "number": 105, + "headRefName": "ml/compiled-prompt-38761019113", + "title": "feat(prompt): recompile the prompts on llama3.2 (marola-ml)", + "baseRefName": "main", + "mergeable": "MERGEABLE", + "author": { "login": "app/marola-pr-bot" }, + "files": [{ "path": "prompts/compiled.json" }] } ] diff --git a/scripts/lib/pr_labels.sh b/scripts/lib/pr_labels.sh index 13af116..aa0409e 100755 --- a/scripts/lib/pr_labels.sh +++ b/scripts/lib/pr_labels.sh @@ -107,11 +107,13 @@ pr_label_layers() { printf '%s\n' "${labels[@]}" } -# pr_label_kind — best-effort, only when certain. +# pr_label_kind — best-effort, only +# when certain. marola-pr-bot opens Scala Steward's PRs and others too; update/ tells them apart. pr_label_kind() { - local author="$1" paths="$2" + local author="$1" ref="$2" paths="$3" case "$author" in - dependabot\[bot\]|app/dependabot|scala-steward\[bot\]|app/scala-steward) echo "kind/deps"; return ;; + dependabot\[bot\]|app/dependabot) echo "kind/deps"; return ;; + app/marola-pr-bot) [[ "$ref" == update/* ]] && { echo "kind/deps"; return; } ;; esac if [ -n "$paths" ] && ! grep -qvE '\.md$|^docs/' <<<"$paths"; then echo "kind/docs-only" @@ -137,6 +139,6 @@ pr_label_classify() { printf '%s\n' $areas layers="$(pr_label_layers "$paths")" [ -n "$layers" ] && printf '%s\n' "$layers" - kind="$(pr_label_kind "$author" "$paths")" + kind="$(pr_label_kind "$author" "$ref" "$paths")" [ -n "$kind" ] && printf '%s\n' "$kind" } diff --git a/scripts/pr-label.sh b/scripts/pr-label.sh index f1e0f9e..760a882 100755 --- a/scripts/pr-label.sh +++ b/scripts/pr-label.sh @@ -5,16 +5,35 @@ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck source=scripts/lib/pr_labels.sh. source "$script_dir/lib/pr_labels.sh" -dry_run=0; pr_arg="" +dry_run=0; pr_arg=""; self_test=0 for arg in "$@"; do case "$arg" in --dry-run) dry_run=1 ;; + --self-test) self_test=1 ;; -h|--help) sed -n '2,13p' "$0"; exit 0 ;; [0-9]*|\#[0-9]*) pr_arg="${arg#\#}" ;; *) echo "pr-label: unrecognized argument: $arg" >&2; exit 1 ;; esac done +self_test() { + local fails=0 got + check() { # label, got, want + if [ "$2" = "$3" ]; then echo " ok $1"; else fails=$((fails + 1)); echo " FAIL $1 — got '$2', want '$3'" >&2; fi + } + got="$(pr_label_kind app/marola-pr-bot update/sbt-2.0.10 "project/build.properties")" + check "a marola-pr-bot update/ PR is kind/deps" "$got" "kind/deps" + got="$(pr_label_kind app/marola-pr-bot ml/compiled-prompt-1 "prompts/compiled.json")" + check "a marola-pr-bot PR on another branch is not" "$got" "" + got="$(pr_label_kind app/dependabot dependabot/pip/x "requirements.txt")" + check "a dependabot PR is kind/deps" "$got" "kind/deps" + got="$(pr_label_classify update/sbt-2.0.10 app/marola-pr-bot "Update sbt to 2.0.10" "project/build.properties" | grep '^kind/' || true)" + check "pr_label_classify passes the branch on" "$got" "kind/deps" + echo "pr-label self-test:" "$([ "$fails" -eq 0 ] && echo ok || echo "$fails FAILED")" + [ "$fails" -eq 0 ] +} +if [ "$self_test" -eq 1 ]; then self_test; exit $?; fi + if [ -z "$pr_arg" ]; then pr_arg="$(gh pr view --json number --jq .number 2>/dev/null)" || { echo "pr-label: no PR number given and no PR found for the current branch" >&2 diff --git a/tests/self-tests.sh b/tests/self-tests.sh index 7189d70..4a86278 100755 --- a/tests/self-tests.sh +++ b/tests/self-tests.sh @@ -9,7 +9,7 @@ sh_tests=( scripts/agents-check.sh scripts/mip-stack.sh scripts/docs-mip-stack.sh scripts/deps-stack.sh scripts/deps-merge.sh scripts/gha-runner.sh scripts/setup-runners.sh scripts/runner-preflight.sh scripts/temps.sh scripts/bump-consumers.sh scripts/bump-merge.sh scripts/ruleset-sync.sh scripts/api-docs-push.sh - scripts/graph.sh + scripts/graph.sh scripts/pr-label.sh plugins/marola-devkit/hooks/format.sh plugins/marola-devkit/hooks/stop-gate.sh plugins/marola-devkit/hooks/session-start.sh )