Skip to content

Commit da9e752

Browse files
kim-emclaude
andcommitted
feat(ci): post per-PR comment summarising cross-reference tags
When a PR touches `Mathlib/**/*.lean` and adds `@[stacks ...]`, `@[kerodon ...]`, or `@[wikidata ...]` attributes, this workflow posts (or updates, or deletes) a single bot comment with one row per added tag, showing the upstream label / description fetched from the source database, the Mathlib declaration's first-line signature, and the author's optional attribute comment. CI fails when any tag cannot be resolved upstream and emits a warning (not a failure) when only transient network errors occur. Trusted-script execution pattern (same as `PR_summary.yml`): * The PR head is checked out into `pr-branch/` and used as DATA only — no code from it ever runs in this `pull_request_target` context. * The base ref is checked out into `base/`; its `scripts/extract-crossref-tags.lean` and `scripts/crossref-snippet.lean` are the trusted versions we actually invoke, and its `lean-toolchain` is what `lake env lean --run` picks up. * `mathlib-ci` is checked out via the shared `get-mathlib-ci` action; its `scripts/crossref_review/crossref-pr-comment.py` orchestrator (added in leanprover-community/mathlib-ci#39) drives the extract → snippet → format pipeline. * The resulting comment file is piped through `scripts/pr_summary/update_PR_comment.sh` for post-once / update-in-place behaviour. A startup gate exits cleanly when the base ref pre-dates the companion Lean scripts (e.g. against an older master), so the workflow doesn't fail spuriously. `docs/workflows.md` gains a new entry; `scripts/README.md` points at the mathlib-ci location for the orchestrator. Stacked on leanprover-community#39664 (LSP widget) and depends on the orchestrator landing in leanprover-community/mathlib-ci#39. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 63fd5cc commit da9e752

3 files changed

Lines changed: 158 additions & 0 deletions

File tree

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
name: Cross-reference tag review
2+
3+
# When a PR touches Mathlib/**/*.lean, run the cross-reference orchestrator
4+
# from `mathlib-ci` and post (or update, or delete) a single bot comment
5+
# summarising the `@[stacks ...]`, `@[kerodon ...]`, and `@[wikidata ...]`
6+
# tags it adds. Fails CI if any tag is missing upstream.
7+
#
8+
# Trusted-script execution pattern (same as `PR_summary.yml`):
9+
# the PR's working tree is read as DATA only. The Python orchestrator runs
10+
# from the `mathlib-ci` checkout, and the Lean scripts run from the
11+
# mathlib4 BASE checkout. Nothing from the PR checkout is ever executed in
12+
# this privileged context.
13+
on:
14+
pull_request_target:
15+
paths:
16+
- 'Mathlib/**/*.lean'
17+
18+
permissions:
19+
contents: read
20+
pull-requests: write
21+
22+
jobs:
23+
crossref-review:
24+
name: post-or-update-crossref-comment
25+
if: github.repository == 'leanprover-community/mathlib4'
26+
runs-on: ubuntu-latest
27+
env:
28+
# PR description for #39666 pins this until the mathlib-ci PR (#39) merges.
29+
# Once that merges, drop this and let the default in get-mathlib-ci win.
30+
MATHLIB_CI_REF: 5b09178e56be069f61414284367d81166e2667d1
31+
32+
steps:
33+
- name: Checkout PR head (data only)
34+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
35+
with:
36+
ref: ${{ github.event.pull_request.head.sha }}
37+
fetch-depth: 0
38+
path: pr-branch
39+
40+
- name: Checkout base ref (trusted Lean scripts + toolchain)
41+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
42+
with:
43+
ref: ${{ github.event.pull_request.base.sha }}
44+
fetch-depth: 1
45+
path: base
46+
47+
- name: Checkout local actions
48+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
49+
with:
50+
ref: ${{ github.workflow_sha }}
51+
fetch-depth: 1
52+
sparse-checkout: .github/actions
53+
path: workflow-actions
54+
55+
- name: Get mathlib-ci
56+
uses: ./workflow-actions/.github/actions/get-mathlib-ci
57+
with:
58+
ref: ${{ env.MATHLIB_CI_REF }}
59+
60+
- name: Verify base has Lean scripts
61+
id: gate
62+
run: |
63+
set -euo pipefail
64+
if [ ! -f base/scripts/extract-crossref-tags.lean ] \
65+
|| [ ! -f base/scripts/crossref-snippet.lean ]; then
66+
echo "Base ref does not have the cross-reference Lean scripts; skipping."
67+
echo "skip=yes" >> "$GITHUB_OUTPUT"
68+
else
69+
echo "skip=no" >> "$GITHUB_OUTPUT"
70+
fi
71+
72+
- name: Install elan
73+
if: steps.gate.outputs.skip == 'no'
74+
run: |
75+
set -euo pipefail
76+
curl https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh -sSf \
77+
| sh -s -- -y --default-toolchain none
78+
echo "$HOME/.elan/bin" >> "$GITHUB_PATH"
79+
# First `lake env lean` invocation against `base/` will trigger
80+
# download of the toolchain pinned in `base/lean-toolchain`. The
81+
# scripts only import Lean core, so no Mathlib build is needed.
82+
83+
- name: Fetch base ref full history
84+
if: steps.gate.outputs.skip == 'no'
85+
run: |
86+
# `--diff` mode in the extractor uses `git diff` inside `pr-branch`;
87+
# we need the base ref visible there.
88+
cd pr-branch
89+
git fetch origin "${{ github.event.pull_request.base.ref }}" --depth=200
90+
91+
- name: Compose comment body
92+
if: steps.gate.outputs.skip == 'no'
93+
id: compose
94+
run: |
95+
set +e
96+
python3 "${CI_SCRIPTS_DIR}/crossref_review/crossref-pr-comment.py" \
97+
--pr-checkout "$(pwd)/pr-branch" \
98+
--base-checkout "$(pwd)/base" \
99+
--base-ref "origin/${{ github.event.pull_request.base.ref }}" \
100+
--output "$(pwd)/comment.md"
101+
ec=$?
102+
if [ ! -f comment.md ]; then : > comment.md; fi
103+
echo "exit_code=$ec" >> "$GITHUB_OUTPUT"
104+
echo "---- comment.md ----"
105+
cat comment.md
106+
echo "---- end ----"
107+
108+
- name: Post or update or delete bot comment
109+
if: steps.gate.outputs.skip == 'no'
110+
env:
111+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
112+
GITHUB_REPOSITORY: ${{ github.repository }}
113+
run: |
114+
set -euo pipefail
115+
PR="${{ github.event.pull_request.number }}"
116+
EXIT="${{ steps.compose.outputs.exit_code }}"
117+
TITLE='### Cross-reference tags added by this PR'
118+
if [ "$EXIT" = "0" ] || [ ! -s comment.md ]; then
119+
# No tags added → delete any prior bot comment.
120+
existing=$(gh api "repos/${{ github.repository }}/issues/$PR/comments" --paginate \
121+
--jq 'map(select(.body | startswith("### Cross-reference tags added by this PR"))) | first | .id // empty')
122+
if [ -n "${existing:-}" ]; then
123+
echo "Removing stale bot comment $existing."
124+
gh api -X DELETE "repos/${{ github.repository }}/issues/comments/$existing"
125+
fi
126+
else
127+
# Post or update via the shared helper.
128+
"${CI_SCRIPTS_DIR}/pr_summary/update_PR_comment.sh" \
129+
comment.md "$TITLE" "$PR"
130+
fi
131+
132+
- name: Fail on missing tags
133+
if: steps.gate.outputs.skip == 'no'
134+
env:
135+
EXIT: ${{ steps.compose.outputs.exit_code }}
136+
run: |
137+
set -euo pipefail
138+
# Exit codes from crossref-pr-comment.py:
139+
# 0 = no tags added (no comment posted; not a failure)
140+
# 1 = tags added, all resolved
141+
# 2 = some tag missing upstream → fail
142+
# 3 = transient network failure → warn but pass
143+
case "$EXIT" in
144+
0|1) echo "OK." ;;
145+
2) echo "::error::One or more cross-reference tags are missing upstream."
146+
exit 1 ;;
147+
3) echo "::warning::Some cross-reference tags could not be resolved due to a network error."
148+
exit 0 ;;
149+
*) echo "::error::Unexpected exit code $EXIT from crossref-pr-comment.py"
150+
exit 1 ;;
151+
esac

docs/workflows.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ Primary trigger for this section: PR/merge-queue events (`pull_request`, `pull_r
5050
| [`check_pr_titles.yaml`](../.github/workflows/check_pr_titles.yaml) | Check PR titles<br>[![passed/failed](https://img.shields.io/github/actions/workflow/status/leanprover-community/mathlib4/check_pr_titles.yaml?label=status)](https://github.com/leanprover-community/mathlib4/actions/workflows/check_pr_titles.yaml) | Low | `pull_request_target` | Validates PR titles against project conventions and maintains a sticky guidance comment. |
5151
| [`lint_and_suggest_pr.yml`](../.github/workflows/lint_and_suggest_pr.yml) | lint and suggest<br>[![passed/failed](https://img.shields.io/github/actions/workflow/status/leanprover-community/mathlib4/lint_and_suggest_pr.yml?label=status)](https://github.com/leanprover-community/mathlib4/actions/workflows/lint_and_suggest_pr.yml) | Low | `pull_request` | Runs style linting and suggests changes on pull requests. The suggested changes can be applied by calling `bot_fix_style.yaml`. |
5252
| [`PR_summary.yml`](../.github/workflows/PR_summary.yml) | Post PR summary comment<br>[![passed/failed](https://img.shields.io/github/actions/workflow/status/leanprover-community/mathlib4/PR_summary.yml?label=status)](https://github.com/leanprover-community/mathlib4/actions/workflows/PR_summary.yml) | Low | `pull_request_target` | On `pull_request_target`, computes PR summary data (imports/declarations/tech debt), manages related labels, and updates PR comments. |
53+
| [`crossref_review.yml`](../.github/workflows/crossref_review.yml) | Cross-reference tag review<br>[![passed/failed](https://img.shields.io/github/actions/workflow/status/leanprover-community/mathlib4/crossref_review.yml?label=status)](https://github.com/leanprover-community/mathlib4/actions/workflows/crossref_review.yml) | Low | `pull_request_target` | Extracts every `@[stacks]`, `@[kerodon]`, and `@[wikidata]` attribute added by the PR, fetches the upstream snippet for each, posts a once-per-PR summary comment, and fails CI if any tag is missing upstream. |
5354
| [`add_label_from_diff.yaml`](../.github/workflows/add_label_from_diff.yaml) | Autolabel PRs<br>[![passed/failed](https://img.shields.io/github/actions/workflow/status/leanprover-community/mathlib4/add_label_from_diff.yaml?label=status)](https://github.com/leanprover-community/mathlib4/actions/workflows/add_label_from_diff.yaml) | Low | `pull_request_target, push` | Applies an inferred topic label to newly opened PRs using `lake exe autolabel`. |
5455
| [`label_new_contributor.yml`](../.github/workflows/label_new_contributor.yml) | Label New Contributors<br>[![passed/failed](https://img.shields.io/github/actions/workflow/status/leanprover-community/mathlib4/label_new_contributor.yml?label=status)](https://github.com/leanprover-community/mathlib4/actions/workflows/label_new_contributor.yml) | Low | `pull_request_target` | Labels PRs from new contributors (based on the number of their PRs merged). |
5556
| [`zulip_emoji_closed_pr.yaml`](../.github/workflows/zulip_emoji_closed_pr.yaml) | Add "closed-pr" emoji in Zulip<br>[![passed/failed](https://img.shields.io/github/actions/workflow/status/leanprover-community/mathlib4/zulip_emoji_closed_pr.yaml?label=status)](https://github.com/leanprover-community/mathlib4/actions/workflows/zulip_emoji_closed_pr.yaml) | Low | `pull_request_target` | Updates Zulip emoji reactions for PR close/reopen events. |

scripts/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,12 @@ to module `Foo.Bar` (no `srcDir` indirection).
255255
declaration it decorates. A byte-level scanner skips strings, comments,
256256
and modifier keywords correctly.
257257
`lake env lean --run scripts/extract-crossref-tags.lean --file Mathlib/Foo.lean`.
258+
The orchestration script driven by `.github/workflows/crossref_review.yml`
259+
lives in the [`mathlib-ci`](https://github.com/leanprover-community/mathlib-ci)
260+
repository at `scripts/crossref_review/crossref-pr-comment.py` — it is kept
261+
there (rather than in this repo) so the workflow can execute it from a
262+
trusted external checkout in the privileged `pull_request_target` context
263+
without running any PR-modified code.
258264

259265
**Managing downstream repos**
260266
- `downstream_repos.yml` contains basic information about significant downstream repositories.

0 commit comments

Comments
 (0)