feat(ci): cross-reference tag review via post-build lint + workflow_run#39666
feat(ci): cross-reference tag review via post-build lint + workflow_run#39666kim-em wants to merge 3 commits into
Conversation
PR summary 292d3968f8Import changes for modified filesNo significant changes to the import graph Import changes for all files
|
|
This PR/issue depends on: |
3e1e9b9 to
1cb01cd
Compare
1cb01cd to
b829fbb
Compare
b829fbb to
da9e752
Compare
|
Here's what the bot's comment renders as, generated by feeding the orchestrator a synthetic file with one valid + one bogus tag from each database. (No quoting trick — this is the literal contents of Cross-reference tags added by this PRWikidata
Stacks
Kerodon
⚠ One or more tags above could not be found upstream. Please verify the tag identifier or remove the attribute. |
69c93c6 to
1eb21ea
Compare
Add `scripts/crossref.lean`, a single standalone tool to support review of PRs that add `@[stacks ...]`, `@[kerodon ...]`, or `@[wikidata ...]` attributes. It exposes two subcommands: * `lake env lean --run scripts/crossref.lean snippet <db> <tag>...` fetches a one-line label/description for each tag from the upstream database. Wikidata uses the `wbgetentities` API (with recovery when one bad QID poisons a batch); Stacks and Kerodon use the documented Gerby `/data/tag/<TAG>/content/statement` endpoint with a tolerant HTML→text strip that survives `<` inside math. Exit codes distinguish all-resolved (0) from missing (2) from transient network failure (3). When `CROSSREF_CACHE_DIR` is set, responses are memoised per `(database, tag)` so repeat lookups are instant. * `lake env lean --run scripts/crossref.lean extract --file <path>...` (or `--diff <range>`) walks Lean source and emits TSV of every cross-reference attribute it finds, paired with the declaration it decorates. A byte-level scanner correctly skips string literals and line/block comments, handles multi-attribute blocks (`@[simp, stacks 01AB]`), multi-line attribute blocks, doc comments between attribute and declaration, and modifier keywords (`private`, `noncomputable`, …). Signatures are collapsed to one line for downstream consumption. The two subcommands share a single `Database` enum, with a roundtrip theorem `Database.ofString?_name` proving `name ∘ ofString?` is a section — so adding a fourth database fails to compile until both projections are updated together. The script imports only Lean core (plus `Std.Data.HashMap` for batching), so it can be invoked directly with `lake env lean --run` without any Mathlib build, and a future CI workflow can drive it on untrusted PR files without elaborating PR code. Companion follow-ups (separate PRs) will add an LSP widget that surfaces these snippets in the editor and a GitHub Actions workflow that posts a once-per-PR comment with tag / signature / snippet tables and fails CI on unresolved tags. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a Mathlib declaration carries `@[stacks ...]`, `@[kerodon ...]`, or `@[wikidata ...]`, the info view now shows the upstream label / description when the cursor sits on the attribute. The fetch is on-demand via an RPC call from the widget to the Lean server, which shells out to `curl`; we deliberately don't fetch at attribute-elaboration time so offline builds stay clean and the only network access is when an editing session opens the panel. Two new modules and a small refactor to the existing one: * `Mathlib/Tactic/CrossRef/Fetch.lean` (new) holds the `Database` enum and the snippet-fetch logic. The companion `scripts/crossref-snippet.lean` in the previous PR reimplements the same logic standalone for CI usage; the two are kept in sync deliberately so the CI script doesn't need a Mathlib build. * `Mathlib/Tactic/Widget/CrossRefHover.lean` (new) defines the widget itself: an `RpcEncodable` props record, a `RequestM` RPC method that calls `fetchSnippet`, and a `mk_rpc_widget%` Component that renders the result as Html. * `Mathlib/Tactic/CrossRefAttribute.lean` (modified) imports the two new modules, removes the now-duplicated `Database` / `databaseURL` / `databaseLabel` definitions, and calls `Widget.savePanelWidgetInfo` in each attribute's `add` handler. Tag storage, docstring rewriting, and the trace commands are untouched. If `curl` is missing or the upstream site is unreachable, the widget renders an inline error instead of blocking the LSP. The existing `MathlibTest/CrossRefAttribute.lean` suite continues to pass — none of the behaviour visible to it changes. Stacked on leanprover-community#39662 (scripts only). A third follow-up will add a GitHub Actions workflow that posts a once-per-PR comment with tag / signature / snippet tables. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1eb21ea to
9d70518
Compare
…t step The mathlib4 cross-reference review pipeline is moving from a text-based extractor running independently in a `pull_request_target` workflow to an elaboration-based lint step that runs in the build pipeline's `post_steps` job after `lake exe cache get`. The lint step emits a machine-readable TSV (db / tag / declName / file / comment) as an artifact, and a `workflow_run`-triggered companion downloads it here. This commit rewrites `scripts/crossref_review/crossref-pr-comment.py` so it: * Takes `--tsv` (the path to the artifact) instead of two checkout paths. * Defensively parses the TSV (unknown databases dropped, missing fields padded). Nothing from PR-derived input is executed. * Fetches snippets directly via Python's `urllib` — Wikidata via the batched `wbgetentities` API, Stacks/Kerodon via the documented Gerby `/data/tag/<TAG>/content/statement` endpoint. Gerby fetches are parallelised with `ThreadPoolExecutor(max_workers=8)` to keep the workflow under a minute on PRs that touch large files. * Constructs the Markdown comment in trusted code (no longer relies on PR-derived Markdown), still using the `### Cross-reference tags added by this PR` sentinel so `update_PR_comment.sh` can dedupe. * Exits 0 / 1 / 2 / 3 with the same grammar as before. Stacked on leanprover-community/mathlib4#39666 upcoming force-push, which adds the post-build lint step and rewires `crossref_review.yml` as `workflow_run`-triggered. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9d70518 to
d49feaa
Compare
Replace the text-based cross-reference extractor with an elaboration-
based lint step that runs in the build pipeline's `post_steps` job
and a `workflow_run`-triggered companion that posts the bot comment.
**Why.** The previous text-based extractor (a ~250 LOC byte-level
scanner in `scripts/crossref.lean`) was a workaround for not having a
built Mathlib environment in the pull_request_target workflow. Now
that we run as a post-build lint step, we have a fully elaborated
environment and can read `Mathlib.CrossRef.tagExt` directly — cutting
~250 LOC and eliminating the fidelity gap with the Lean parser.
**Pieces:**
* `scripts/crossref.lean` — the `extract` subcommand is gone. New
`check --diff <range> --tsv <path>` subcommand uses
`importModules (loadExts := true)` (NOT `withImportModules`, which
passes `loadExts := false` and would leave `tagExt` empty), walks
`tagExt`, looks up each tagged declaration's source module via
`env.getModuleFor?`, and filters by `git diff --name-only` of the
PR range. Output is machine-readable TSV
(`db\ttag\tdeclName\tfile\tcomment`). No Markdown, no network,
no snippets — those are the privileged workflow's job.
* `.github/workflows/build_template.yml` — adds three new steps to
`post_steps`, after the import-graph upload:
- Run `lake env lean --run scripts/crossref.lean check ...`
against the diff between the PR base and HEAD.
- Write a `crossref-context.json` with PR number + head SHA so
the companion workflow doesn't have to rely on the unreliable
`workflow_run.pull_requests[0].number`.
- Upload both as a single `crossref-context` artifact.
* `.github/workflows/crossref_review.yml` — rewritten. Old trigger
`pull_request_target` → new trigger `workflow_run`. Downloads the
artifact, resolves the PR number through a three-stage fallback
chain (artifact JSON → workflow_run payload → `gh pr list` head
SHA match), calls the mathlib-ci orchestrator (which now consumes
the TSV and fetches snippets in Python), posts/updates/deletes
via `update_PR_comment.sh`, and fails the check on `missing` tags.
**Privilege model.** The lint step runs with the PR's permissions and
produces only machine-readable TSV. The `workflow_run` job runs in the
base ref's permission scope (with `pull-requests: write`) and never
executes anything from the artifact — it parses the TSV defensively,
fetches snippets server-side from three fixed upstream APIs, and
builds the Markdown comment in trusted code.
Companion mathlib-ci PR:
leanprover-community/mathlib-ci#39
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
d49feaa to
292d396
Compare
Cross-reference tag review for PRs adding
@[stacks ...],@[kerodon ...], or@[wikidata ...]attributes — implemented as a post-build lint step plus aworkflow_run-triggered companion that posts the bot comment.Why this shape. The previous iteration of this PR ran an independent
pull_request_targetworkflow that text-parsed PR source files. That was a workaround for not having a built Mathlib environment available in CI. With a post-build lint step we do have the elaborated environment — so we readMathlib.CrossRef.tagExtdirectly viaimportModules (loadExts := true)and cut ~250 LOC of bespoke byte-level scanner.The three pieces:
scripts/crossref.lean— theextractsubcommand is gone. Newcheck --diff <range> --tsv <path>subcommand walkstagExt, looks up each tagged declaration's source module viaenv.getModuleFor?, filters bygit diff --name-onlyof the PR range, and writes a machine-readable TSV. No Markdown, no network, no snippet fetching — that's the privileged workflow's job. Note:withImportModulespassesloadExts := falseand would leavetagExtempty for imported modules; we useimportModules (loadExts := true)instead..github/workflows/build_template.yml— three new steps inpost_steps, after the existing import-graph upload:lake env lean --run scripts/crossref.lean check --diff "${BASE_SHA}..HEAD" --tsv crossref-added.tsvcrossref-context.jsoncarrying the PR number and head SHA (so theworkflow_runjob doesn't have to rely on the unreliableworkflow_run.pull_requests[0].number).crossref-contextartifact..github/workflows/crossref_review.yml— rewritten. Trigger flips frompull_request_targettoworkflow_runon the build workflows. Downloads the artifact, resolves the PR number through a three-stage fallback (artifact JSON → workflow_run payload →gh pr listhead SHA match), calls the mathlib-ci orchestrator (which now consumes the TSV and fetches snippets in Python directly with parallelThreadPoolExecutorfor Stacks/Kerodon), and posts viaupdate_PR_comment.sh. Fails the check on anymissingtag.Privilege model. The lint step runs with the PR's permissions and produces only a machine-readable TSV. The
workflow_runjob runs in the base ref's permission scope (withpull-requests: write), never executes anything from the artifact, fetches snippets server-side from three fixed upstream APIs, and builds the Markdown comment in trusted code.🤖 Prepared with Claude Code