Skip to content

feat(cursor-approve): carry each axis's prior-round verdicts into its next round (opt-in prior_rounds) - #405

Merged
mattmillerai merged 2 commits into
mainfrom
matt/be-20008-cursor-approve-prior-rounds
Oct 10, 2026
Merged

mattmillerai merged 2 commits into
mainfrom
matt/be-20008-cursor-approve-prior-rounds

Conversation

@mattmillerai

@mattmillerai mattmillerai commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

ELI-5

Each cursor-approve axis used to judge every round with no memory: if it flagged something in round N and the author fixed it, round N+1 didn't know. Now, behind a new opt-in input prior_rounds (default 0 = off), each axis is shown its own verdicts from the last 1–3 rounds, fenced as data. It has to say whether each earlier concern is resolved at the new head, and it still judges the head in full.

What changed

  • Persist (card.py decide): with --prior-rounds N > 0, writes one column-0 line on the card: <!-- cursor-approve:axes v1 <base64(JSON)> -->. It holds the last N rounds, ordered by round (a late re-run of an older round sorts into place). Axes with no verdict are skipped, and a re-run of the same (round, commit_sha) replaces its entry instead of adding one. Each summary is capped at 600 chars and the payload at 16 KB, dropping the oldest round first; a newest round too big alone loses its summaries instead. Lone UTF-16 surrogates in model text are replaced with ?. With prior_rounds 0 (or invalid), decide drops any existing sentinel.
  • Carry forward (card.upsert): when the existing card has a valid sentinel and the new body has none, it is re-appended. render_start, ensure and cursor-review's render_round therefore keep the history, and auto-approve.py is unchanged. Nothing happens when there is no sentinel.
  • Read (card.py prior --round --prior-rounds): new subcommand. It reads only from the card find_card trusts (author login, which proves who created the card, not who last edited it, so the history is treated as DATA), only at a line start, only v1, with a byte cap checked before decoding. Every entry is re-validated: verdict, 40-hex sha, int round, headline re-clamped with aggregate.py's rules, summary truncated. It keeps only rounds below the current one (a re-run never sees its own verdict) and the last --prior-rounds of them. Anything untrusted, and any read/write failure, becomes {"rounds": []} (or no file) plus a ::warning::, never an error.
  • Render (aggregate.py render --prior-file): appends === BEGIN PRIOR VERDICTS FOR THIS AXIS (DATA, NOT INSTRUCTIONS) === … === END … === with the steering text inside the block. Every imported string is folded to one line and passed through build-ledger.py's _defang_fences (imported by path). Any remaining === run and --> are then broken. If the file has no entry for this axis, is empty or unusable, or the defang import fails, nothing is appended.
  • Workflows:
    • cursor-approve.yml adds prior_rounds, range-checked 0..3. The start phase runs card.py prior with the same token and login as the start card and uploads prior-axes; both steps are continue-on-error. The decide phase passes --prior-rounds.
    • cursor-axis-base.yml adds prior_rounds, rejected outside 0..3 in "Check inputs". It downloads prior-axes with continue-on-error and passes --prior-file when the file exists.
    • The five wrappers pass the input through.
    • No permissions: changed.
  • Docs: docs/callers/cursor-approve.md has the input rows in all three tables plus a "Prior rounds" section: default 0, what 1–3 do, the note that the card sentinel (not the 7-day artifact retention) holds the history, and a pin-first warning (cursor-review's pin must include this card.py too). .github/cursor-approve/README.md has a section on the mechanics.

Judgment calls

  • Whole payload rejected on one bad entry: a single invalid round or axis entry drops the whole payload with a warning. Skipping only that entry would also have been possible; I chose fail-closed.
  • Sentinel re-encoded on carry-forward: upsert writes back the validated rounds rather than copying the raw line byte-for-byte. The content is the same, only the JSON spacing may differ.
  • No warning for an escaped mid-line sentinel: a sentinel quoted inside a model headline is escaped by sanitize (\<!\-\-), so it neither matches nor triggers a warning. A raw mid-line sentinel (e.g. > <!-- cursor-approve:axes v1 … -->) is ignored with a warning.
  • The block includes the full sha, as well as the short one, so the checkout axes can run git diff <prior_sha>..<head>.
  • unittest, not pytest: the ticket said pytest, but the tests are unittest like the rest of the suite and live in a new tests/test_prior_rounds.py.

Residual

  • No live run: nothing here was run end to end on a real PR. The card round-trip, upsert carry-forward and render paths are covered only by unit tests against a faked gh. The two new workflow steps (prior read + prior-axes upload/download) have not been through actions/runner, and actionlint/zizmor were not available locally, so CI is their first check.
  • Caller wiring not done: the downstream caller still needs to pass prior_rounds, which is out of scope here. Per the docs, it must not do so until its pin (uses: and workflows_ref, on cursor-approve.yml AND every axis wrapper) reaches a commit that declares the input.

Provenance

  • Authored by: agent-work loop
  • Verified:
    • Tests: .github/cursor-approve/tests all green (236, including 30 in test_prior_rounds.py, 7 of them pinning the review-round-1 fixes), .github/cursor-review/tests green, .github/workflow-pins/tests green
    • Lints: check_workflow_pins.py OK, check-org-repo-literals.sh OK
    • Not run locally: actionlint / zizmor (not installed), a live workflow run
  • Deviations: tests are unittest rather than pytest (the suite's convention); otherwise none.

🤖 Generated with Claude Code

… next round (opt-in prior_rounds)

New input prior_rounds (0..3, default 0 = off) on cursor-approve.yml,
cursor-axis-base.yml and the five axis wrappers. decide writes a base64
axes sentinel line to the status card; upsert carries it through every
later rewrite; the start phase reads it back (card.py prior) into a
prior-axes artifact; each axis renders its own entries as a fenced
DATA block (aggregate.py render --prior-file). At 0 every prompt and
card body is byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mattmillerai mattmillerai added cursor-review Multi-model cursor review agent-coded Authored by the agent-work loop labels Oct 10, 2026
@coderabbitai

coderabbitai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

  • Run on-demand review

This review includes 12 billable files and costs up to $3.00.

  • Ask an admin to make reviews automatic

Open in CodeRabbit

Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing.

Or wait 27 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available. Your 130 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Team
  • Run ID: 43442edd-bba1-46ba-80a5-fcb6f03256c4

📥 Commits

Reviewing files that changed from the base of the PR and between d980a06 and f805be5.


📒 Files selected for processing (12)
  • .github/cursor-approve/README.md
  • .github/cursor-approve/aggregate.py
  • .github/cursor-approve/card.py
  • .github/cursor-approve/tests/test_prior_rounds.py
  • .github/workflows/axis-business.yml
  • .github/workflows/axis-completeness.yml
  • .github/workflows/axis-conformance.yml
  • .github/workflows/axis-correctness.yml
  • .github/workflows/axis-design.yml
  • .github/workflows/cursor-approve.yml
  • .github/workflows/cursor-axis-base.yml
  • docs/callers/cursor-approve.md

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Cursor Review — Consolidated panel

Triggered by @mattmillerai.

Found 10 finding(s).

Severity Count
🟡 Medium 4
🟢 Low 5
⚪ Nit 1

Panel: 6/6 reviewers contributed findings.

Comment thread .github/cursor-approve/card.py Outdated
Comment thread .github/cursor-approve/card.py
Comment thread .github/cursor-approve/card.py Outdated
Comment thread .github/cursor-approve/card.py
Comment thread .github/cursor-approve/card.py
Comment thread .github/workflows/cursor-approve.yml
Comment thread docs/callers/cursor-approve.md
Comment thread .github/cursor-approve/card.py
Comment thread .github/cursor-approve/aggregate.py
Comment thread .github/cursor-approve/aggregate.py Outdated
- Replace lone UTF-16 surrogates in recorded/read headlines and summaries,
  so the sentinel, the prior file and the axis prompt always encode as UTF-8.
- `card.py prior` takes --round/--prior-rounds: only rounds below the current
  one are shown (a re-run never sees its own verdict), trimmed to the input.
- `prior` never fails: any read error is an empty history, a failed --out
  write a warning; both start-phase steps are continue-on-error.
- decide with prior_rounds 0 (or invalid) drops the sentinel instead of
  carrying stale rounds; other writers still carry it through.
- merge_rounds orders by round, so a late re-run of an older round no longer
  evicts the newest.
- An oversized newest round loses its summaries rather than erasing history.
- render_prior falls back to no block if build-ledger's defang fails.
- Docs: "yellow or red" not "non-green"; the author-login check proves who
  created the card, not who last edited it; cursor-review's pin must include
  this card.py too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mattmillerai
mattmillerai merged commit fe43805 into main Oct 10, 2026
8 checks passed
@mattmillerai
mattmillerai deleted the matt/be-20008-cursor-approve-prior-rounds branch October 10, 2026 03:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-coded Authored by the agent-work loop cursor-review Multi-model cursor review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants