From d6e49dee1f14debe6c17ab9aa25ba653d6885916 Mon Sep 17 00:00:00 2001 From: Matt Miller Date: Mon, 19 Oct 2026 14:54:24 +0000 Subject: [PATCH 1/3] ci(cursor-review): move the Cursor CLI pin in cursor-axis-base.yml alongside cursor-review.yml Since cursor-axis-base.yml started carrying the same CURSOR_CLI_VERSION / CURSOR_CLI_SHA256 pair, every automated bump PR failed the cursor-approve pin-equality test until a human edited the second file. The bumper now reads the pin from both files (failing loud if they disagree), moves both, and stages both. A new test asserts WORKFLOW_FILES (both steps) and the `git add` line list every workflow carrying the pin, and the test workflow now runs when the bumper changes. --- .github/cursor-approve/tests/test_card.py | 22 ++++ .github/cursor-review/README.md | 2 +- .github/workflows/bump-cursor-cli-pin.yml | 101 +++++++++++------- .../workflows/test-cursor-review-scripts.yml | 6 ++ 4 files changed, 94 insertions(+), 37 deletions(-) diff --git a/.github/cursor-approve/tests/test_card.py b/.github/cursor-approve/tests/test_card.py index 8c31035e..1c27329c 100644 --- a/.github/cursor-approve/tests/test_card.py +++ b/.github/cursor-approve/tests/test_card.py @@ -879,6 +879,28 @@ def pins(name): self.assertEqual(pins("cursor-axis-base.yml"), pins("cursor-review.yml")) self.assertEqual(len(pins("cursor-review.yml")), 2) + def test_cli_pin_bumper_edits_every_file_carrying_the_pin(self): + pin_re = re.compile(r"^ CURSOR_CLI_VERSION: ", re.MULTILINE) + carrying = [] + for name in sorted(os.listdir(WORKFLOWS)): + if not name.endswith((".yml", ".yaml")): + continue + with open(os.path.join(WORKFLOWS, name), encoding="utf-8") as fh: + if pin_re.search(fh.read()): + carrying.append(f".github/workflows/{name}") + self.assertIn(".github/workflows/cursor-review.yml", carrying) + with open(os.path.join(WORKFLOWS, "bump-cursor-cli-pin.yml"), encoding="utf-8") as fh: + bumper = fh.read() + hint = ("every workflow carrying the Cursor CLI pin must be listed in bump-cursor-cli-pin.yml's " + "WORKFLOW_FILES (both the Discover and Move-the-pin steps) and its `git add` line") + listed = re.findall(r"^\s+WORKFLOW_FILES: (.+)$", bumper, re.MULTILINE) + self.assertEqual(len(listed), 2, "both the Discover and Move-the-pin steps declare WORKFLOW_FILES") + for value in listed: + self.assertEqual(sorted(value.split()), carrying, hint) + added = re.search(r"^\s+git add (.+)$", bumper, re.MULTILINE) + self.assertIsNotNone(added) + self.assertEqual(sorted(added.group(1).split()), carrying, hint) + class GuardParity(unittest.TestCase): diff --git a/.github/cursor-review/README.md b/.github/cursor-review/README.md index 397592a7..51327c6f 100644 --- a/.github/cursor-review/README.md +++ b/.github/cursor-review/README.md @@ -252,7 +252,7 @@ the consolidated review's panel table. See [Panel integrity](#panel-integrity). | [`auto-approve.py`](auto-approve.py) | Opt-in auto-approve (`approve_max_severity`): `decide` (in `post-review`) approves / requests changes / reaches no decision (withdrawing its own earlier approvals and leaving one standing REQUEST_CHANGES with the reasons and the next step, replaced by the next no-decision round and dismissed by the next approving one; none on a `capped` round, which withdraws the earlier one) per its fail-closed rules, then writes cursor-approve's status card for the outcome (via [`../cursor-approve/card.py`](../cursor-approve/card.py); no card for an author `approve_authors` does not list), and every review it posts links that card (on a PR's first round it creates a pending card just before the review, since a review cannot be edited to add the link later); `dismiss-stale` (the **Dismiss stale auto-approval** job) withdraws the bot's own approvals that are stale against the PR's live head and base (a push, a reopen, a retarget) — and every one of them while the PR carries `skip-cursor-review` or `needs-human-review` — plus its own standing REQUEST_CHANGES while the PR carries either label (re-read after the reviews are listed) or `approve_max_severity` is empty (then only once none of its approvals still stands), on every event of an open PR, whether or not `approve_max_severity` is still set (with `carry_forward_unchanged_diff`, it first re-approves the live head of a PR whose own changes are still the ones a round reviewed, from the merge base that approval recorded: `decide` and `approve-external` record it only when the compare API and the round's sentinel agree, `approval_merge_base`); `round-cap` (the **Round cap** job, ahead of the panel) enforces `max_rounds`. On an APPROVE that survived its post-write re-check, `decide` also resolves (re-reading each first) and then replies on the posting identity's own at-or-below-threshold threads nobody else commented in (nothing, if any live thread is above the threshold); [`build-ledger.py`](build-ledger.py) never counts that reply as an answer. `decide` also writes the `approve_gate` output, and `round-cap` feeds the `round` and `max_rounds` outputs. Reads open-thread severity through `gate-unresolved.py`'s query. See [`docs/callers/cursor-review.md`](../../docs/callers/cursor-review.md#auto-approve). | | [`gate-unresolved.py`](gate-unresolved.py) | The opt-in blocking gate (`blocking: true` → the **Blocking gate** job): queries the PR's review threads and exits non-zero while any cursor-review finding thread is unresolved and non-outdated. Dropped from `cursor-review.yml` by accident in #31 and restored by BE-4691 — see [the blocking section](#optional-make-the-review-blocking). Double-billed: [`build-ledger.py`](build-ledger.py) also imports it for `CONSOLIDATED_MARKER`, the paging `reviewThreads` GraphQL query and the `iter_threads` / `is_cursor_thread` helpers, so the gate and the ledger can never disagree about which threads are ours. | | [`slack-notify.sh`](slack-notify.sh) | Sends the start/complete Slack DMs to the triggerer (no-ops without a token). | -| [`install-cursor-cli.sh`](install-cursor-cli.sh) | Installs the Cursor agent CLI from the versioned, sha256-pinned release artifact — not `curl cursor.com/install \| bash`. Used by all three CLI-using jobs; the pin (`CURSOR_CLI_VERSION` / `CURSOR_CLI_SHA256`) lives in `cursor-review.yml`'s top-level `env:`. | +| [`install-cursor-cli.sh`](install-cursor-cli.sh) | Installs the Cursor agent CLI from the versioned, sha256-pinned release artifact — not `curl cursor.com/install \| bash`. Used by all three CLI-using jobs; the pin (`CURSOR_CLI_VERSION` / `CURSOR_CLI_SHA256`) lives in `cursor-review.yml`'s top-level `env:`, mirrored in `cursor-axis-base.yml`'s (cursor-approve's suite asserts they match); the weekly `bump-cursor-cli-pin.yml` moves both together. | | [`build-ledger.py`](build-ledger.py) | Builds the **prior-review ledger** — what earlier rounds raised on this PR and how the author answered — and splices it into the panel/judge prompts. Untrusted prose is defanged twice over: fence-opening lines are rewritten, and continuation lines of quoted prose are prefixed ` |` so no imported text can sit at a field's indent and forge one. Also the prompt splicer, so the no-ledger path is byte-identical to the pre-ledger prompt. | | [`fence-diff.py`](fence-diff.py) | Wraps the reviewed diff (plus the incremental hunks, and the judge's panel-findings block) in `=== BEGIN/END DIFF ===` fences. The diff is attacker-authored PR bytes, so static literal fences are not a control; the nonce is what a PR cannot forge. Each prompt-build step mints its OWN nonce (`mint`), into a shell variable rather than a step `env:` or job output — Actions dumps a step's env map into the public run log, and a per-prompt value means a leak in one job cannot forge a fence in another. Copies the body through **byte for byte** — it never defangs or normalizes the payload. | | [`incremental-diff.py`](incremental-diff.py) | Builds the incremental "hunks new since the last reviewed round" block as the difference between two PR patches (`build`), and verifies the result really is a subset of the reviewed diff (`check`) so the workflow can discard it if it is not. Never a commit range: `git diff LAST_REVIEWED...HEAD` carries every commit a merge of the base branch brought in — see [the ledger section](#the-prior-review-ledger-and-the-repeat-policy). | diff --git a/.github/workflows/bump-cursor-cli-pin.yml b/.github/workflows/bump-cursor-cli-pin.yml index 56629632..c9d0310f 100644 --- a/.github/workflows/bump-cursor-cli-pin.yml +++ b/.github/workflows/bump-cursor-cli-pin.yml @@ -1,6 +1,7 @@ name: Bump Cursor CLI pin -# Keep the Cursor CLI pin in cursor-review.yml from going stale silently. +# Keep the Cursor CLI pin in cursor-review.yml and cursor-axis-base.yml from +# going stale silently. # # BE-5869 replaced `curl https://cursor.com/install | bash` with a versioned, # sha256-pinned artifact (see .github/cursor-review/install-cursor-cli.sh). That @@ -8,13 +9,21 @@ name: Bump Cursor CLI pin # one: nothing moves the pin, so the panel would run an ever-older CLI until # someone noticed. This job notices instead — weekly it reads the version Cursor # currently ships, and when it differs from the pin it downloads that version's -# linux/x64 artifact, computes its digest, and opens a two-line PR moving -# CURSOR_CLI_VERSION + CURSOR_CLI_SHA256. Nothing auto-merges: a human reviews +# linux/x64 artifact, computes its digest, and opens a PR moving +# CURSOR_CLI_VERSION + CURSOR_CLI_SHA256 in BOTH files that carry the pin +# (cursor-review.yml, and cursor-axis-base.yml since the cursor-approve axes +# install the same CLI). Nothing auto-merges: a human reviews # the digest, which is the whole point (see the trust-anchor note below). # -# Merging that PR touches .github/workflows/cursor-review.yml, which is exactly -# the path filter bump-cursor-review-callers.yml watches — so the SHA-pinned -# caller fleet rolls forward on its own. No new fleet plumbing here. +# Merging that PR touches .github/workflows/cursor-review.yml and +# .github/workflows/cursor-axis-base.yml, both already in +# bump-cursor-review-callers.yml's single `push.paths` filter — so the one +# SHA-pinned caller fleet rolls forward on its own, exactly as before (there is +# deliberately no separate cursor-approve fleet; see that file's header). No new +# fleet plumbing here. The two pins must stay equal (cursor-approve's +# test_card.py asserts it), so the bumper reads and moves them together; the +# same suite asserts WORKFLOW_FILES and the `git add` below list every file +# carrying the pin. # # Modelled on bump-cursor-review-callers.yml: same main-only job guard, same # non-cancelling concurrency around a stable shared branch, same App-token @@ -73,7 +82,7 @@ jobs: - name: Discover the version Cursor currently ships id: discover env: - WORKFLOW_FILE: .github/workflows/cursor-review.yml + WORKFLOW_FILES: .github/workflows/cursor-review.yml .github/workflows/cursor-axis-base.yml run: | set -euo pipefail @@ -112,10 +121,27 @@ jobs: # Anchored read of the pin: `^ CURSOR_CLI_VERSION: ` matches the # top-level env key and nothing else (the in-job references to - # $CURSOR_CLI_VERSION are indented far deeper). - old_version=$(sed -nE 's/^ CURSOR_CLI_VERSION: (.*)$/\1/p' "$WORKFLOW_FILE" | head -1 || true) + # $CURSOR_CLI_VERSION are indented far deeper). Every file carrying the + # pin must agree: a divergence means someone hand-moved one of them, + # and the bumper fails loud rather than papering over it. + read -ra files <<<"$WORKFLOW_FILES" + old_version="" + for f in "${files[@]}"; do + v=$(sed -nE 's/^ CURSOR_CLI_VERSION: (.*)$/\1/p' "$f" | head -1 || true) + if [ -z "$v" ]; then + echo "::error::No ' CURSOR_CLI_VERSION: ' line in ${f} — the pin moved or was removed; this workflow needs updating." + exit 1 + fi + echo "pinned in ${f}: ${v}" + if [ -z "$old_version" ]; then + old_version="$v" + elif [ "$v" != "$old_version" ]; then + echo "::error::Pinned versions disagree across ${WORKFLOW_FILES} ('${old_version}' vs '${v}' in ${f}) — reconcile by hand before the bumper can move them." + exit 1 + fi + done if [ -z "$old_version" ]; then - echo "::error::No ' CURSOR_CLI_VERSION: ' line in ${WORKFLOW_FILE} — the pin moved or was removed; this workflow needs updating." + echo "::error::WORKFLOW_FILES is empty — nothing to read the pin from." exit 1 fi @@ -250,40 +276,43 @@ jobs: - name: Move the pin if: steps.discover.outputs.changed == 'true' env: - WORKFLOW_FILE: .github/workflows/cursor-review.yml + WORKFLOW_FILES: .github/workflows/cursor-review.yml .github/workflows/cursor-axis-base.yml NEW_VERSION: ${{ steps.discover.outputs.new_version }} NEW_SHA256: ${{ steps.digest.outputs.new_sha256 }} run: | set -euo pipefail # Both substitutions are anchored to the top-level env keys, and each - # must match EXACTLY one line — anything else means cursor-review.yml + # must match EXACTLY one line per file — anything else means that file # was restructured and a blind sed could corrupt it. - for key in CURSOR_CLI_VERSION CURSOR_CLI_SHA256; do - n=$(grep -cE "^ ${key}: " "$WORKFLOW_FILE" || true) - if [ "$n" != "1" ]; then - echo "::error::Expected exactly one ' ${key}: ' line in ${WORKFLOW_FILE}, found ${n}." - exit 1 - fi - done + read -ra files <<<"$WORKFLOW_FILES" + for f in "${files[@]}"; do + for key in CURSOR_CLI_VERSION CURSOR_CLI_SHA256; do + n=$(grep -cE "^ ${key}: " "$f" || true) + if [ "$n" != "1" ]; then + echo "::error::Expected exactly one ' ${key}: ' line in ${f}, found ${n}." + exit 1 + fi + done - before="${RUNNER_TEMP}/cursor-review.before.yml" + before="${RUNNER_TEMP}/$(basename "$f").before" - cp "$WORKFLOW_FILE" "$before" - sed -i -E "s|^ CURSOR_CLI_VERSION: .*\$| CURSOR_CLI_VERSION: ${NEW_VERSION}|" "$WORKFLOW_FILE" - if cmp -s "$before" "$WORKFLOW_FILE"; then - echo "::error::The CURSOR_CLI_VERSION substitution changed nothing." - exit 1 - fi + cp "$f" "$before" + sed -i -E "s|^ CURSOR_CLI_VERSION: .*\$| CURSOR_CLI_VERSION: ${NEW_VERSION}|" "$f" + if cmp -s "$before" "$f"; then + echo "::error::The CURSOR_CLI_VERSION substitution changed nothing in ${f}." + exit 1 + fi - cp "$WORKFLOW_FILE" "$before" - sed -i -E "s|^ CURSOR_CLI_SHA256: .*\$| CURSOR_CLI_SHA256: ${NEW_SHA256}|" "$WORKFLOW_FILE" - if cmp -s "$before" "$WORKFLOW_FILE"; then - echo "::error::The CURSOR_CLI_SHA256 substitution changed nothing." - exit 1 - fi + cp "$f" "$before" + sed -i -E "s|^ CURSOR_CLI_SHA256: .*\$| CURSOR_CLI_SHA256: ${NEW_SHA256}|" "$f" + if cmp -s "$before" "$f"; then + echo "::error::The CURSOR_CLI_SHA256 substitution changed nothing in ${f}." + exit 1 + fi + done - git --no-pager diff -- "$WORKFLOW_FILE" + git --no-pager diff -- "${files[@]}" - name: Generate Cloud Code Bot token id: token @@ -320,7 +349,7 @@ jobs: git config user.name 'cursor-cli-pin bumper' git config user.email 'cursor-cli-pin-bumper@users.noreply.github.com' git checkout -B "$BRANCH" - git add .github/workflows/cursor-review.yml + git add .github/workflows/cursor-review.yml .github/workflows/cursor-axis-base.yml git commit -m "ci(cursor-review): bump Cursor CLI pin to ${NEW_VERSION}" # Force-reset the stable branch so its diff is rebuilt from the current @@ -338,14 +367,14 @@ jobs: # command substitutions — that is exactly why they are single-quoted. # shellcheck disable=SC2016 { - printf '%s\n\n' 'Moves the Cursor CLI pin `cursor-review.yml` installs from (BE-5869).' + printf '%s\n\n' 'Moves the Cursor CLI pin that `cursor-review.yml` and `cursor-axis-base.yml` install from (BE-5869).' printf '| | |\n|---|---|\n' printf '| Version | `%s` → `%s` |\n' "$OLD_VERSION" "$NEW_VERSION" printf '| sha256 (linux/x64 `agent-cli-package.tar.gz`) | `%s` |\n' "$NEW_SHA256" printf '| nixpkgs cross-check | %s |\n' "$NIXPKGS_STATUS" printf '| smoke test | passed — `install-cursor-cli.sh` installed this artifact and the binary self-reported `%s` |\n\n' "$NEW_VERSION" printf '**You are the trust anchor for this digest.** Cursor publishes no checksums for these artifacts, so the sha256 above is simply what the CDN served during [this run](%s) — trust-on-first-use. Corroborate it before merging (the nixpkgs row above, or a second independent download); nothing downstream can.\n\n' "$RUN_URL" - printf '%s\n\n' 'Merging this bumps `cursor-review.yml`, which triggers `bump-cursor-review-callers.yml` and rolls the SHA-pinned caller fleet forward.' + printf '%s\n\n' 'Merging this bumps `cursor-review.yml` and `cursor-axis-base.yml`; both are in the path filter of `bump-cursor-review-callers.yml`, so the single SHA-pinned caller fleet rolls forward once, as before.' printf '%s\n' '_Opened by the `bump-cursor-cli-pin` workflow._' } > "$body" diff --git a/.github/workflows/test-cursor-review-scripts.yml b/.github/workflows/test-cursor-review-scripts.yml index 41c22e73..76751c8f 100644 --- a/.github/workflows/test-cursor-review-scripts.yml +++ b/.github/workflows/test-cursor-review-scripts.yml @@ -40,6 +40,9 @@ on: # (secret isolation, the guard byte-copy, the CLI pin). - '.github/workflows/cursor-approve.yml' - '.github/workflows/cursor-axis-base.yml' + # The cursor-approve suite asserts the bumper lists every file carrying + # the CLI pin. + - '.github/workflows/bump-cursor-cli-pin.yml' - '.github/workflows/axis-*.yml' # test_caller_doc_pattern.py reads the caller snippets out of these # pages and pins their `if:` guards; the regression it guards is a @@ -58,6 +61,9 @@ on: # (secret isolation, the guard byte-copy, the CLI pin). - '.github/workflows/cursor-approve.yml' - '.github/workflows/cursor-axis-base.yml' + # The cursor-approve suite asserts the bumper lists every file carrying + # the CLI pin. + - '.github/workflows/bump-cursor-cli-pin.yml' - '.github/workflows/axis-*.yml' # test_caller_doc_pattern.py reads the caller snippets out of these # pages and pins their `if:` guards; the regression it guards is a From ede3f173f871a2285d8913923dd07fb337dbf78a Mon Sep 17 00:00:00 2001 From: Matt Miller Date: Mon, 19 Oct 2026 15:06:24 +0000 Subject: [PATCH 2/3] ci(cursor-review): bumper also requires the CLI pin digests to agree; widen the carrier test Discover now compares CURSOR_CLI_SHA256 across every pin file as well as the version, before the no-change exit, and fails loud on a mismatch rather than auto-picking a digest. The completeness test asserts every carrier shares one (version, sha256) pair, checks every `git add` line, and its suite now runs on any .github/workflows/*.yml change so a new pin carrier triggers it. Co-Authored-By: Claude Opus 5.5 --- .github/cursor-approve/tests/test_card.py | 16 +++++++++++--- .github/workflows/bump-cursor-cli-pin.yml | 21 ++++++++++++------- .../workflows/test-cursor-review-scripts.yml | 14 +++++++------ 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/.github/cursor-approve/tests/test_card.py b/.github/cursor-approve/tests/test_card.py index 1c27329c..a64c9193 100644 --- a/.github/cursor-approve/tests/test_card.py +++ b/.github/cursor-approve/tests/test_card.py @@ -897,9 +897,19 @@ def test_cli_pin_bumper_edits_every_file_carrying_the_pin(self): self.assertEqual(len(listed), 2, "both the Discover and Move-the-pin steps declare WORKFLOW_FILES") for value in listed: self.assertEqual(sorted(value.split()), carrying, hint) - added = re.search(r"^\s+git add (.+)$", bumper, re.MULTILINE) - self.assertIsNotNone(added) - self.assertEqual(sorted(added.group(1).split()), carrying, hint) + added = re.findall(r"^\s+git add (.+)$", bumper, re.MULTILINE) + self.assertEqual(len(added), 1, "the bumper stages the pin files in one `git add`") + for value in added: + self.assertEqual(sorted(value.split()), carrying, hint) + # And every carrier holds the SAME (version, sha256) pair, not just the + # two test_cli_pin_matches_cursor_review names. + pin_pair = re.compile(r"^ (CURSOR_CLI_(?:VERSION|SHA256)): (\S+)$", re.MULTILINE) + pairs = set() + for path in carrying: + with open(os.path.join(WORKFLOWS, os.path.basename(path)), encoding="utf-8") as fh: + pairs.add(tuple(pin_pair.findall(fh.read()))) + self.assertEqual(len(pairs), 1, f"Cursor CLI pins diverge across {carrying}: {pairs}") + self.assertEqual(len(next(iter(pairs))), 2) diff --git a/.github/workflows/bump-cursor-cli-pin.yml b/.github/workflows/bump-cursor-cli-pin.yml index c9d0310f..d3e3216a 100644 --- a/.github/workflows/bump-cursor-cli-pin.yml +++ b/.github/workflows/bump-cursor-cli-pin.yml @@ -122,21 +122,28 @@ jobs: # Anchored read of the pin: `^ CURSOR_CLI_VERSION: ` matches the # top-level env key and nothing else (the in-job references to # $CURSOR_CLI_VERSION are indented far deeper). Every file carrying the - # pin must agree: a divergence means someone hand-moved one of them, - # and the bumper fails loud rather than papering over it. + # pin must agree on BOTH the version and the digest: a divergence means + # someone hand-moved one of them, and the bumper fails loud rather than + # papering over it. A digest mismatch on an equal version is checked + # here, before the no-change exit, because it is otherwise invisible + # to a run whose version is already current. Never auto-reconciled: + # which digest is right is the reviewer's call (see the trust anchor). read -ra files <<<"$WORKFLOW_FILES" old_version="" + old_sha256="" for f in "${files[@]}"; do v=$(sed -nE 's/^ CURSOR_CLI_VERSION: (.*)$/\1/p' "$f" | head -1 || true) - if [ -z "$v" ]; then - echo "::error::No ' CURSOR_CLI_VERSION: ' line in ${f} — the pin moved or was removed; this workflow needs updating." + d=$(sed -nE 's/^ CURSOR_CLI_SHA256: (.*)$/\1/p' "$f" | head -1 || true) + if [ -z "$v" ] || [ -z "$d" ]; then + echo "::error::No ' CURSOR_CLI_VERSION: ' / ' CURSOR_CLI_SHA256: ' line in ${f} — the pin moved or was removed; this workflow needs updating." exit 1 fi - echo "pinned in ${f}: ${v}" + echo "pinned in ${f}: ${v} (sha256 ${d})" if [ -z "$old_version" ]; then old_version="$v" - elif [ "$v" != "$old_version" ]; then - echo "::error::Pinned versions disagree across ${WORKFLOW_FILES} ('${old_version}' vs '${v}' in ${f}) — reconcile by hand before the bumper can move them." + old_sha256="$d" + elif [ "$v" != "$old_version" ] || [ "$d" != "$old_sha256" ]; then + echo "::error::Pins disagree across ${WORKFLOW_FILES} ('${old_version}' / ${old_sha256} vs '${v}' / ${d} in ${f}) — reconcile by hand before the bumper can move them." exit 1 fi done diff --git a/.github/workflows/test-cursor-review-scripts.yml b/.github/workflows/test-cursor-review-scripts.yml index 76751c8f..8fa1672b 100644 --- a/.github/workflows/test-cursor-review-scripts.yml +++ b/.github/workflows/test-cursor-review-scripts.yml @@ -40,9 +40,10 @@ on: # (secret isolation, the guard byte-copy, the CLI pin). - '.github/workflows/cursor-approve.yml' - '.github/workflows/cursor-axis-base.yml' - # The cursor-approve suite asserts the bumper lists every file carrying - # the CLI pin. - - '.github/workflows/bump-cursor-cli-pin.yml' + # The cursor-approve suite asserts bump-cursor-cli-pin.yml lists EVERY + # workflow carrying the CLI pin, so any workflow gaining (or losing) the + # pin must trigger it — hence all of them, not just the bumper. + - '.github/workflows/*.yml' - '.github/workflows/axis-*.yml' # test_caller_doc_pattern.py reads the caller snippets out of these # pages and pins their `if:` guards; the regression it guards is a @@ -61,9 +62,10 @@ on: # (secret isolation, the guard byte-copy, the CLI pin). - '.github/workflows/cursor-approve.yml' - '.github/workflows/cursor-axis-base.yml' - # The cursor-approve suite asserts the bumper lists every file carrying - # the CLI pin. - - '.github/workflows/bump-cursor-cli-pin.yml' + # The cursor-approve suite asserts bump-cursor-cli-pin.yml lists EVERY + # workflow carrying the CLI pin, so any workflow gaining (or losing) the + # pin must trigger it — hence all of them, not just the bumper. + - '.github/workflows/*.yml' - '.github/workflows/axis-*.yml' # test_caller_doc_pattern.py reads the caller snippets out of these # pages and pins their `if:` guards; the regression it guards is a From 519e535fdad2abed9c158eb53dc878e94d4118b0 Mon Sep 17 00:00:00 2001 From: Matt Miller Date: Mon, 19 Oct 2026 15:16:09 +0000 Subject: [PATCH 3/3] ci(cursor-review): trigger the pin-coverage test on .yaml workflows too The carrier scan in test_card.py reads both .yml and .yaml workflows, so both extensions belong in the path filters that run it. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/test-cursor-review-scripts.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-cursor-review-scripts.yml b/.github/workflows/test-cursor-review-scripts.yml index 8fa1672b..909108f7 100644 --- a/.github/workflows/test-cursor-review-scripts.yml +++ b/.github/workflows/test-cursor-review-scripts.yml @@ -42,8 +42,10 @@ on: - '.github/workflows/cursor-axis-base.yml' # The cursor-approve suite asserts bump-cursor-cli-pin.yml lists EVERY # workflow carrying the CLI pin, so any workflow gaining (or losing) the - # pin must trigger it — hence all of them, not just the bumper. + # pin must trigger it — hence all of them (both extensions the suite + # scans), not just the bumper. - '.github/workflows/*.yml' + - '.github/workflows/*.yaml' - '.github/workflows/axis-*.yml' # test_caller_doc_pattern.py reads the caller snippets out of these # pages and pins their `if:` guards; the regression it guards is a @@ -64,8 +66,10 @@ on: - '.github/workflows/cursor-axis-base.yml' # The cursor-approve suite asserts bump-cursor-cli-pin.yml lists EVERY # workflow carrying the CLI pin, so any workflow gaining (or losing) the - # pin must trigger it — hence all of them, not just the bumper. + # pin must trigger it — hence all of them (both extensions the suite + # scans), not just the bumper. - '.github/workflows/*.yml' + - '.github/workflows/*.yaml' - '.github/workflows/axis-*.yml' # test_caller_doc_pattern.py reads the caller snippets out of these # pages and pins their `if:` guards; the regression it guards is a