Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/cursor-review/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ With `ledger_prior_review` on (the default), [`build-ledger.py`](build-ledger.py

A finding that could not be anchored to a line the reviewed diff carries is **demoted** to the review body, and one that lost its whole review to a failed POST is delivered as prose — neither gets a thread, so neither can be answered and neither costs a slot. But a demoted finding may itself have been a *re-raise*, and that lineage used to disappear with the trailer that was stripped out of its recovered body: the next round saw a fresh, thread-less, cap-exempt finding, so one demoted hop made every later re-raise of the same finding free. The sentinel now carries the ancestor's URL as a field, and the ledger resolves it against the PR's own comments: the trailing `discussion_r<id>` must name a **root** comment of one of *our* consolidated reviews on this PR, so a hallucinated id resolves to nothing. Only that id is read — the owner/repo/PR-number half of the URL is shape-checked but never compared — so a link naming another repo resolves whenever its id matches one of our roots; what the entry renders is always the ancestor's *own* permalink, never the relayed string, and an ancestor with no permalink yields no lineage. The round is recovered the same way, from the review that ancestor belongs to, rather than from the payload. When it resolves, the entry renders `re_raise_of: <url> (round N; ancestor_answers=<k>)` — a key deliberately distinct from the `answers_from_author_or_maintainer` on the entry's own `thread:` line, since the two describe different findings — plus a `re_raise_answer:` excerpt of the ancestor's most recent answering reply, and the repeat rule is applied to that **ancestor's** answer state: with `k >= 1` a further re-raise must carry `repeat_of` and costs a slot, and with `k == 0` nothing changes. Resolution runs over every consolidated review before the round cap, so the ancestor is still found after its own round has aged out of the ledger. A claim that does *not* resolve — a dismissed review, a deleted comment — is not silently rebuilt as a fresh cap-exempt finding: the entry says the lineage is unverified and withholds the exemption.

A `repeat_of` is adjudicated on two independent layers, and neither is the other's backstop. The **writer** ([`post-review.py`](post-review.py)) checks SHAPE — one anchored GitHub discussion permalink under 512 characters, nothing else, applied to the rendered trailer and the structural field alike — and, given `--ledger`, MEMBERSHIP: the URL must be one the ledger the judge was actually shown carried, i.e. an *anchored* entry's `discussion_url` or the `repeat_of` lineage a *demoted* re-raise entry renders on its `re_raise_of:` line, mirroring the gates the render itself applies. The **reader** ([`build-ledger.py`](build-ledger.py)) separately resolves the trailing comment id against this PR's own consolidated-review roots, as above. Membership catches precisely what resolution cannot: an id that resolves but was never SHOWN — aged past the round cap, dropped by the byte cap, or never rendered — which the judge therefore had no basis to cite. A URL failing either check is dropped whole — no trailer, no sentinel field, nothing carried into the next round — while the judge's *declaration* that the finding is a re-raise still spends its `REPEAT_CAP` slot. That asymmetry is deliberate: refunding the slot would make a link we reject strictly cheaper than an honest one, so a judge could turn a whole round into uncapped re-litigation simply by citing threads it was never shown. Under the cap a bad link costs the finding its lineage and nothing else. It is only when that declaration is the one that overruns `REPEAT_CAP` that the finding is cut whole — exactly as an honest over-cap re-raise is, since the cap counts *declarations* and so by construction cannot tell the two apart. That is the asymmetry's price, not a second penalty: a refused link never costs a finding its place while the round has slots left. An `empty`, `disabled` or `unknown` ledger carries `entries: []`, and the judge prompt permits `repeat_of` only inside a ledger block, so on those runs the shown set is legitimately empty and every `repeat_of` is dropped. A ledger that cannot be read at all is the one case that must NOT drop anything — no path, no file, unreadable, not an object, no `entries` list — so it degrades to shape-only, exactly as before, the reader's half still holds, and the run carries a `::warning::` annotation so a silently disabled guard is visible rather than buried in a step log. The judge's and the poster's ledger downloads fail independently, and only the **judge's** decides what was shown: when that one fails the workflow passes `--no-judge-ledger` and the shown set is empty, because the judge prompt was spliced an empty ledger block however intact this job's own copy of `ledger.json` may be.

### The panel

| Lab | Model (Cursor catalog) |
Expand Down
Loading
Loading