fix(cursor-review): survive a rebranded model family in the drift check (BE-4852) - #88
Conversation
…ck (BE-4852)
Two heuristics in the catalog-drift checker distorted the "unpinned same-lab
ids" review-me list. Neither could produce a false green, but both undercut the
headline feature — noticing that a newer model shipped.
1. `lab_of()` equates a lab with an id's first `-`/`.`-separated token, and labs
are derived from the pins themselves (what keeps the checker
zero-maintenance). An existing lab shipping under a NEW family prefix —
OpenAI's `o<n>` series alongside `gpt-*` is the precedent — therefore
resolves to a lab nobody pins and vanished from the review-me list. Add a
quieter catch-all section listing catalog ids whose family prefix matches no
pin at all, so a rebranded family surfaces instead of dropping out. It is a
finding (or a rebranded family would render into a body nobody sees) but
never `urgent`, and renders collapsed below the same-lab list.
2. `catalog_entries()` admitted any lowercase hyphenated first token with a
letter, so a prose line ("gpt-based models are …") parsed as a bogus id.
Require a digit as well: every id a lab has shipped carries a version number,
a prose word does not. The rule lives in the one `_is_model_id` the pin
validator and the catalog parser share, so the two cannot drift apart —
`test_pin_and_catalog_id_shape_rules_agree` stays the tripwire, and all five
live pins satisfy it.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Comment |
There was a problem hiding this comment.
🔍 Cursor Review — Consolidated panel
Triggered by @mattmillerai.
Found 4 finding(s).
| Severity | Count |
|---|---|
| 🟡 Medium | 3 |
| ⚪ Nit | 1 |
Panel: 8/8 reviewers contributed findings.
… hole (BE-4852) Review panel findings on the BE-4852 catch-all: * `_HAS_DIGIT` gated the CATALOG parser as well as the pin validator, so a real digit-less id — Cursor ships `code-supernova` — was dropped by `catalog_entries` before parsing. A newly shipped digit-less family therefore never reached the unpinned-families catch-all this PR adds and the run reported clean: exactly the "a new model shipped and nobody noticed" silence the check exists to end. It also hard-failed extraction every run if such an id were ever pinned. Removed: over-reporting a hyphenated prose word (one row in a collapsed, never-urgent list) is the cheaper failure, and the direction `present` already argues for. * `extract_judge_model` never called `_is_model_id`, though the docstring claimed it did. Its scalar/whitespace checks are strictly weaker, so a separator-less judge default (`o3`) extracted cleanly and then failed `present()` — a phantom URGENT "delisted pin" every Monday. It now applies the same shared rule, making the "both sides agree" contract true. * The unpinned-families fold had no per-section budget, so the blunt `MAX_BODY_CHARS` clamp could cut into it, leaving `<details>` unterminated and dropping the stale-audit and raw-catalog sections below. Budgeted at `MAX_FAMILY_LABS`/`MAX_FAMILY_IDS`, naming what it truncated. * Grouping now keys a dict by lab instead of a linear scan per entry (O(N)). Tests updated to match: the two that encoded the digit rule now assert the accepted trade, plus new coverage for the judge shape hole, a digit-less family reaching the catch-all, and the fold staying well-formed under truncation.
There was a problem hiding this comment.
🔍 Cursor Review — Consolidated panel
Triggered by @mattmillerai.
Found 3 finding(s).
| Severity | Count |
|---|---|
| 🟡 Medium | 1 |
| 🟢 Low | 2 |
Panel: 8/8 reviewers contributed findings.
…es ids (BE-4852) Review-panel fixes on the drift check: - Body budget (medium): cap every per-lab id list (same-lab review-me, delisted alternatives) at MAX_FAMILY_IDS, cap rendered notes at MAX_NOTE_CHARS, char-budget the families fold (row caps bound counts, not chars), and grant the raw-catalog fold only the MAX_BODY_CHARS the report has not used instead of a fixed 40K — so the blunt body[:N] clamp can no longer slice mid-markup. Worst-case test added. - Bare separator-less ids (low): `_is_model_id` now requires a `-`/`.` OR a digit (plus a letter), so a real bare id (`o3`) reaches the unpinned-families catch-all instead of being dropped before parsing — the same silence that got the digit rule reverted. - Prose-token masking (low): the test comment overclaimed that admitted prose "cannot mask a real pin"; a line whose first token equals a pin does mask its delisted finding. Comment corrected and the limitation pinned by an explicit test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
STACKED — merging lands on
matt/be-4819-cursor-review-catalog-drift(owned by @mattmillerai, PR #87), NOTmain. The file this PR edits does not exist onmainyet; #87 introduces it. Merge #87 first, then this.ELI-5
The weekly checker looks at Cursor's model list and tells us if one of our four review models disappeared, or if a newer one showed up that we might want to switch to. It figures out "which lab is this?" by taking the first chunk of the model's name —
gpt-5.6-sol-max→gpt. That works right up until a lab renames its family: when OpenAI shipso5-pronext togpt-*, the checker reads the lab aso5, sees that nobody pinso5, and quietly drops it. So the one thing the check exists for — "a newer model shipped and nobody noticed" — is exactly the thing it could miss. This PR adds a second, quieter list for models from families we pin nothing from, so a renamed family shows up instead of vanishing. It also stops the parser treating a hyphenated English word at the start of a line (gpt-based models are …) as if it were a model name.What changed
Both fixes were deferred out of #87 by the review panel (thread) — they change the design, not just a regex, so they were out of scope there.
1. Unpinned-families catch-all (
analyze→report["unpinned_labs"]). Catalog ids whose family prefix matches no pin at all now get their own section instead of being dropped. This is the ticket's first proposed option, chosen over an explicitLAB_ALIASES = {"gpt": "openai", "o": "openai", …}table: the alias table is more precise but reintroduces exactly the hand-maintained list that deriving labs from the pins exists to avoid, and it needs a human edit before the rebrand it is supposed to catch. The catch-all costs one section and stays zero-maintenance.Deliberate placement, per the ticket's "keep it out of
urgent" note:urgent— it is a standing watchlist, not breakage, so it never reddens the weekly run.has_findings— it has to be, or a rebranded family renders into a body nobody ever sees (a cleanhas_findingscloses the sticky issue). See the judgment call below on auto-close.A lab whose only pin was just delisted still counts as pinned, so its successors stay in the same-lab list (with the delisted-pin context) rather than being demoted into the quiet fold — covered by a test.
2. Model ids must carry a digit (
_is_model_id).gpt-basedis lowercase, hyphenated and has letters, so it parsed as a catalog id and added noise to the review-me list — and, with change 1 in place, would have minted a phantomgpt-basedfamily. Every id a lab has actually shipped carries a version number somewhere; a prose word does not. This is the ticket's first proposed option for (2); "require the token to appear in more than one catalog line" was rejected because a real catalog lists each id exactly once.The rule lives in the single
_is_model_idthat the pin validator and the catalog parser both call, so the two cannot drift apart — relaxing it is one edit, not two.test_pin_and_catalog_id_shape_rules_agree(the tripwire the ticket names) is extended rather than weakened: it now also pinscode-supernova/gpt-basedas rejected ando5-proas accepted, on both sides.Verification
python3 -m unittest discover -s .github/cursor-review/tests -p 'test_*.py'— 103 passed (the exact commandtest-cursor-review-scripts.ymlruns).check_agents_md.py --root .passes (one pre-existing CODEOWNERS warning). No shell changed, soshellcheckis unaffected.New tests: the rebranded-family catch-all (
o5-pro→ familyo5, invisible to the same-lab list), finding-but-never-urgent, a catalog of exactly the pins still having no unpinned families (the auto-close path is not unconditionally broken), a delisted pin's lab still counting as pinned, prose-word rejection, digit-less pin rejected loudly rather than read as delisted, the collapsed-section render, backtick-injection safety on the new section's notes, and a 4000-family catalog still fitting under GitHub's body cap.Judgment calls
_is_model_idgates extraction, so a digit-less pin (code-supernovais a real Cursor id shape) now fails extraction with an actionable message and a red run, rather than being silently reported as delisted every Monday. I checked this against the live pins rather than assuming:gpt-5.6-sol-max,claude-opus-4-8-thinking-max,gemini-3.1-pro,kimi-k2.7-codeand thejudge_modeldefault all carry digits and extract cleanly from the realcursor-review.yml(asserted bytest_extraction_works_against_the_real_cursor_review_workflow). The cost of the tightening is that a digit-less catalog id would no longer reach the review-me list — it is still reproduced verbatim in the raw-catalog fold, which is where a human reads it anyway.unpinned_labsintohas_findingsdoes not newly pin the issue open:unpinnedalone already makeshas_findingstrue on any real catalog, since Cursor lists more tiers per pinned lab than the panel pins four of. The auto-close path was already reserved for a catalog trimmed to exactly the pins, and still is. I chose not to add a separate "worth an issue" gate — it would reintroduce the miss (a rebranded family with no other drift would file nothing), and splitting the two flags is a bigger design change than this deferred follow-up should carry. Flagging it in case you'd rather the catch-all be step-summary-only.LAB_ALIASEStable (see above), andlab_ofitself is unchanged — its docstring now says plainly that it returns a family prefix, not a vendor, and points at the catch-all.