Skip to main content

pr-gap-scan

Scan recent llvm/llvm-project PRs touching the AMDGPU/SPIRV backends for fuzz-fill coverage gaps, and draft review comments for the ones that have real uncovered lines. Use when asked to "scan PRs for gaps", "run the PR gap scan", or similar.

Source facts

Repository
ROCm/fuzz-fill
Last source activity
October 28, 2026 at 19:04
Detected SKILL.md language
English
Stars
1
Forks
2

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
2 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
pr-gap-scan
description
Scan recent llvm/llvm-project PRs touching the AMDGPU/SPIRV backends for fuzz-fill coverage gaps, and draft review comments for the ones that have real uncovered lines. Use when asked to "scan PRs for gaps", "run the PR gap scan", or similar.
# PR gap scan Finds recently-active llvm/llvm-project PRs touching the AMDGPU/SPIRV backends, runs fuzz-fill's PR gap-finding CI job against each, and drafts a review comment (as a local file, never posted automatically) for every PR that has real uncovered lines. All of the logic lives in `scripts/scan.py` (stdlib-only, no venv needed). It's a resumable, multi-hour pipeline — dispatched CI runs build LLVM from scratch and can each take tens of minutes — so it's driven by a JSON state file rather than kept in the conversation. Re-running the same command is always safe; completed phases are skipped. ## Pipeline 1. **discover** — for each requested backend, searches open PRs in `llvm/llvm-project` carrying that backend's auto-applied label (`backend:AMDGPU` / `backend:SPIR-V` — see `.github/new-prs-labeler.yml` in that repo), most-recently-updated first, and keeps the first `--limit` (default 20) **per backend** that `--auto` would actually analyze — i.e. up to 20 AMDGPU PRs and up to 20 SPIRV PRs, not 20 total. Searching by label avoids paying for a `/pulls/<n>/files` call on every open PR in the repo — only PRs GitHub already labeled as touching the backend get one, which is what makes discovery fast and is what keeps the scan aimed at AMDGPU and SPIRV. The label's glob is broad (e.g. `backend:AMDGPU` fires on any `*amdgpu*` path anywhere in the repo), so the files call still runs per labeled PR. It no longer requires `llvm/lib/Target/AMDGPU/` or `llvm/lib/Target/SPIRV/`. A PR is kept when any changed file outside the test trees sits under `llvm/lib/` or `clang/lib/` (the same rule `--auto` uses to decide to run: gap-scope rules 1–3). Test-only, docs-only, and other rule-4 PRs are dropped, because CI would classify them as skip. When a kept PR does edit `llvm/lib/Target/AMDGPU/` or `llvm/lib/Target/SPIRV/`, those paths decide which backend quotas it counts against. Otherwise it counts against the backend label(s) it carries, so a `clang/lib` or other `llvm/lib` change on an AMDGPU- or SPIRV-labeled PR is still found. Pass one or more `--backend {amdgpu,spirv}` to scan only that backend (default: both). A PR touching both backends counts against both quotas, but only lists the backend(s) that still had room when it was found (so a backend past its limit doesn't get analyzed for that PR); a PR found under one backend's label search is only processed once even if it also carries the other's label. A PR is dropped if fuzz-fill already left a comment on it (body contains "fuzz-fill", case-insensitive) and no commit has landed since that comment. Each kept candidate records its head SHA and which backend(s) it touches. 2. **dispatch** — for each candidate PR not forced (see [pr-gap-check](../pr-gap-check/SKILL.md)), first checks whether a prior successful run already analyzed this exact PR/commit: it looks up past `pr-gap-analysis.yml` runs matching `llvm#<N> by` in the run title, and for each checks its `pr-head-pr<N>-<run_id>` artifact (published early in that run) against the candidate's current head SHA. A match means the existing `gap-finding-results-pr<N>-<run_id>` artifact is reused — no new job is scheduled (optional `gap-finding-raw-…` is ignored for reuse). If a matching run's artifacts don't fit the expected `gap-finding-results-pr<N>-<run_id>` / `pr-head-pr<N>-<run_id>` naming (e.g. the workflow's artifact names changed), this logs a warning and falls back to scheduling a fresh run rather than silently skipping the check. Otherwise, triggers `pr-gap-analysis.yml` on `ROCm/fuzz-fill` via `gh workflow run` once per PR (the workflow is gap-finding only and classifies scope with `--auto`) and resolves/records the new run ID. 3. **process** — polls all dispatched runs, but doesn't wait for the whole batch: each polling cycle it refreshes run statuses, downloads (collects) the user-facing `gap-finding-results-pr<N>-<run_id>` artifact (falls back to opt-in `gap-finding-raw-pr<N>-<run_id>` only if results is missing) for any run that just completed successfully, and immediately drafts the comment for any PR whose run has now reached a terminal state — without waiting on any other PR's still-running jobs. It reads `commit_lines_report/target_lines_uncovered_pruned.csv` from the artifact (already pruned by gap-pruner in CI). If anything remains, re-checks the PR's *current* head against the `pr_head` the artifact was actually analyzed against (CI can take a long time — the PR may have gained new commits since it was dispatched); if they differ, prepends a drift warning to the file. It also checks the artifact for a `README-WARNING` file (written by the workflow when LIT tests failed during baseline coverage) and prepends a warning block if so. It also re-fetches the PR's current base branch and, if it isn't the repo's default branch, prepends a stacked-PR warning (see "Stacked PRs" below). Then writes the draft comment for that PR to `--out-dir/pr-<N>.md` using the template below. PRs with no uncovered lines get no file. The loop keeps polling until every dispatched run is terminal. Run all three phases in one go with `run`, or step through them individually (useful for resuming or debugging). `collect` and `comment` also remain available standalone for manual/debugging use (e.g. re-running `comment` without re-running CI) — `process` is just the two of them driven incrementally off the polling loop instead of a one-shot pass after everything finishes. To force the analysis on one specific PR regardless of discovery filters (e.g. it was already commented on, or doesn't touch a backend path), use the [pr-gap-check](../pr-gap-check/SKILL.md) skill instead — same script, `target` subcommand. ## Usage ```bash STATE=./data/pr-gap-scan/$(date -u +%Y%m%d)/state.json ARTIFACTS=./data/pr-gap-scan/$(date -u +%Y%m%d)/artifacts COMMENTS=./data/pr-gap-scan/$(date -u +%Y%m%d)/comments python3 .claude/skills/pr-gap-scan/scripts/scan.py run \ --state "$STATE" \ --artifacts-dir "$ARTIFACTS" \ --out-dir "$COMMENTS" ``` With no `--category` / `--backend` / `--group`, discovery defaults to `amdgpu` and `spirv` (20 each). To process several categories in one run — each with its own `--limit`, overlapping PRs kept once — pass repeatable `--category` flags: ```bash python3 .claude/skills/pr-gap-scan/scripts/scan.py run \ --state "$STATE" \ --limit 20 \ --category amdgpu --category spirv --category llvm --category clang \ --artifacts-dir "$ARTIFACTS" \ --out-dir "$COMMENTS" ``` `--backend` and `--group` remain aliases (`--backend amdgpu` ≡ `--category amdgpu`; `--group llvm` ≡ `--category llvm`) and may be mixed. For llvm-only or clang-only scans, prefer [pr-gap-scan-llvm](../pr-gap-scan-llvm/SKILL.md) / [pr-gap-scan-clang](../pr-gap-scan-clang/SKILL.md). The `$(date ...)` above is just so a fresh invocation lands in a new directory; to resume a specific in-flight scan, reuse its existing `--state` path instead of generating a new one. Step-by-step equivalent (same effect, useful when you want to inspect `$STATE` between phases): ```bash SCAN=.claude/skills/pr-gap-scan/scripts/scan.py python3 "$SCAN" discover --state "$STATE" --limit 20 # Or, to scan only one category (e.g. asked to "just check AMDGPU"): python3 "$SCAN" discover --state "$STATE" --limit 20 --category amdgpu python3 "$SCAN" dispatch --state "$STATE" python3 "$SCAN" process --state "$STATE" --artifacts-dir "$ARTIFACTS" \ --out-dir "$COMMENTS" --poll-interval 60 ``` Since `dispatch`/`process` are idempotent against `$STATE`, it's safe to run `process` again later (e.g. in a follow-up session) if CI is still running — it picks up the same run IDs and keeps polling/drafting as remaining PRs finish (PRs already ready get their comment file re-written, which is harmless). ### After the run The comment files under `--out-dir` are **local drafts only**. The agent does not post them to GitHub, does not run `gh pr comment`, and does not offer to publish them. Check whether the file has a drift warning, a LIT test failure warning, or a stacked-PR warning at the top first (see below); if it does, re-run the analysis (drift), double-check the failing tests (LIT), or confirm the base branch/PR is settled (stacked) before trusting the findings. Otherwise, review each `pr-<N>.md` and leave the drafts for the user. Separately, a successful `pr-gap-analysis.yml` run may open or comment on a `pr-analysis-result` issue on fuzz-fill (internal tracking with artifact links). That issue is not an LLVM review comment and does not satisfy the already-reviewed filter on llvm/llvm-project. ## Comment template Each `pr-<N>.md` starts with a title line (not part of the comment itself — just so opening the file lets you jump straight to the PR), then the message: ``` # [#218649](https://github.com/llvm/llvm-project/pull/218649) — [AMDGPU] MC support for indexed VBUFFER resources for gfx13 Hi! This comment was generated using [fuzz-fill](https://github.com/ROCm/fuzz-fill), a new tool we're building to help improve LLVM test coverage by spotting lines introduced in a PR that don't yet appear to be exercised by the full test suite, including any tests added in this PR. After manually reviewing the findings, they look worth sharing. Looking at PR head 9f967d998a20, we found 1 line added or modified here that we couldn't find a test for: 1. https://github.com/llvm/llvm-project/blob/9f967d998a2073e2e3c3c527a49f2e2bf26c391d/llvm/lib/Target/AMDGPU/AMDGPUISelDAGToDAG.cpp#L4405 If it makes sense, adding a test that validates these lines would be great. And if any of them are already tested and we missed it, we'd love to hear so we can improve the tool. Feel free to give us some feedback on whether this review was useful from your perspective. Thanks! ``` The prose uses the short (12-char) SHA, deliberately without backticks — GitHub autolinks bare hex commit SHAs to the commit page on its own, and backticks would suppress that. Permalinks use the full 40-char SHA (the PR's head commit, from `README-finding.txt`'s `pr_head`, not the squashed commit `gap-finding-pr.sh` builds against). The title line above the message is generated from the PR's own title/URL for whoever opens the draft; it is not part of the review body itself. If the PR has picked up new commits since the artifact's `pr_head` (checked live at comment-writing time), the file gets an extra block prepended above the message, wrapped in `<!-- -->` so it stays out of a rendered GitHub comment if the raw file is ever used elsewhere: ``` <!-- fuzz-fill internal note: strip this block before using the body elsewhere. --> > [!WARNING] > PR #<N> has drifted since this analysis ran. It was analyzed against commit `<short>` (`<full>`), but the PR's current tip is `<short>` (`<full>`). The uncovered lines below may be stale -- re-run the analysis (pr-gap-check's `target` subcommand forces a fresh run) before trusting these findings. <!-- end internal note --> ``` Similarly, if any backend's CI run reported LIT test failures during its baseline coverage pass (the workflow tolerates these — `LIT_ALLOW_FAILURES` — rather than aborting the job, and stages the resulting `README-WARNING` file into the artifact if so), the file gets another hidden block, one `[!WARNING]` per affected backend: ``` <!-- fuzz-fill internal note: strip this block before using the body elsewhere. --> > [!WARNING] > **amdgpu**: > WARNING: 3 LIT test(s) failed during the baseline run (e.g., failed a check, timed out, unresolved, or unexpectedly passed). > Failed tests may lead to an incomplete coverage profile. As a result, > target_lines_uncovered_pruned.csv may report lines as uncovered even though they are > actually covered by a (failing) test. > Review baseline/lit_failures.json for the list of failing tests. <!-- end internal note --> ``` This means the coverage baseline may be incomplete — some of the "uncovered" lines below could actually be covered by a test that failed for an unrelated reason. Treat findings alongside this warning with extra skepticism; the artifact's `out/baseline/lit_failures.json` has the full list of failing tests if you want to check further. Finally, if the PR is **stacked** (see below), the file gets a third hidden block, placed above the other two since it's a structural fact about the PR rather than an analysis-run caveat: ``` <!-- fuzz-fill internal note: strip this block before using the body elsewhere. --> > [!WARNING] > PR #<N> looks stacked: its base branch is `<base_ref>`, not the repository's default branch (`<default_branch>`), which is itself still-open PR #<M> ('<title>'). The gap analysis only considered lines unique to this PR (diffed against its merge-base), but that merge-base will move if the base branch gets amended or rebased before it merges -- which can invalidate this analysis without this PR's own head commit ever changing, so the drift check (which only compares this PR's own head commit) wouldn't catch it. Confirm the base PR/branch is settled before trusting these findings. <!-- end internal note --> ``` ### Stacked PRs A PR is considered **stacked** if its base branch (re-fetched live, at comment-writing time) isn't the repository's default branch (`main` for `llvm/llvm-project`) — i.e. it's opened against another branch rather than trunk, which on GitHub is the standard way to build one PR on top of another still-unmerged one. This is detected with two API calls per candidate at comment time: `GET /repos/{repo}` for the default branch (cached per repo for the run), and `GET /repos/{repo}/pulls?head={owner}:{base_ref}` to see if that base branch is itself another open PR's head (named in the warning if so; otherwise it's noted as unmatched — could be a maintenance branch, or the base PR already merged without this one being retargeted yet). The reason this matters: `gap-finding-pr.sh` computes "added lines" as a diff against this PR's own merge-base with its base branch, so it correctly isolates just this PR's unique changes even when stacked — that part isn't the problem. The problem is that the merge-base itself is unstable: if the (still open, still-being-reviewed) base branch gets amended or rebased before it merges, the analysis silently goes stale without this PR's own head SHA ever changing, so the existing drift check (which only compares this PR's head commit) has no way to catch it. ## Assumptions (revisit if wrong) - Only **open** PRs are scanned (draft PRs included) — merged/closed PRs aren't actionable for a "please add a test" comment. - AMDGPU vs SPIRV membership comes from the auto-applied labels, not from a path prefix. The files check only asks whether `--auto` would run: some non-test path under `llvm/lib/` or `clang/lib/`. Test directories are ignored, same as gap-scope. `llvm/lib/Target/AMDGPU/` and `llvm/lib/Target/SPIRV/` still attribute quota when those dirs are actually edited; other analyzable files fall back to the PR's labels. - Discovery relies on `llvm/llvm-project`'s auto-labeler (`.github/new-prs-labeler.yml`) having already applied `backend:AMDGPU` / `backend:SPIR-V` to a PR before it's scanned. The labeler's globs are broader than the Target directories (e.g. `backend:AMDGPU` also fires on tests and on `clang/lib` paths), so the files check still runs on every labeled candidate to drop rule-4 skips — but a PR could in theory be missed for one scan pass if the labeler hasn't run yet (it's typically near-instant, and a later scan picks it up once the label lands). - The already-commented filter matches any comment (any author) whose body contains "fuzz-fill" case-insensitively — adjust `already_reviewed_without_new_commits()` in `scan.py` if fuzz-fill comments should instead be identified by a specific bot account. A `pr-analysis-result` issue on the fuzz-fill repo is not such a comment. - The workflow currently only exists on the `users/mgcarrasco/manual-workflow` branch of `ROCm/fuzz-fill` (`--ref` default). Update the default (or pass `--ref main`) once `pr-gap-analysis.yml` merges to `main`. - "Uncovered lines" always come from the gap-**finding** artifact (`out/commit_lines_report/target_lines_uncovered_pruned.csv`). The CI workflow is finding-only (no gap filling). ## Prerequisites - `gh` CLI authenticated with access to both `llvm/llvm-project` (read) and `ROCm/fuzz-fill` (`actions:write` to dispatch workflows, and read access to runs/artifacts). - No Python packages beyond the standard library.
View on GitHub