Skip to content
Merged
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
6 changes: 6 additions & 0 deletions .github/cursor-review/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ a fresh runner with a fresh pinned checkout there is nothing tampered left for t
minted token to meet. `tests/test_workflow_job_isolation.py` pins the property, and
[`pr-size.yml`](../workflows/pr-size.yml) uses the identical split for its comment job.

### The prior-review ledger and the repeat policy

With `ledger_prior_review` on (the default), [`build-ledger.py`](build-ledger.py) rebuilds what earlier rounds raised on this PR — each finding, its thread, and the author's replies — and splices it into the panel and judge prompts as untrusted DATA. The rule it enforces is that only an **answered** finding costs a repeat slot: a finding the author or a maintainer replied to may be raised again only if the judge emits `repeat_of` (that thread's permalink) and `repeat_round`, and at most `REPEAT_CAP` such re-raises survive per review, so a round can never be all re-litigation. A finding nobody answered is free to raise again.

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.

### The panel

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