nisse-review: later reviews, replies, rounds, stop and review (M7.5)

Back on a PR it reviewed: a new head gets a later review of the commits
since (each one's diff, through the API) and of its earlier points; what
was said since gets replies, answered where it was said or skipped with a
reason. It signs off once and stays quiet while still happy. Past
tuning.review.max_rounds it sums up for the operator and goes quiet,
until a push or someone other than the PR's author joins in. An
operator's `stop` quiets it; `review` asks for a fresh review.

Reviews carry numbered markers and replies their point's key, so
recovery posts nothing twice.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
This commit is contained in:
dongarandClaude Opus 5.5 committed 2026-10-17 07:06:06 +00:00
1 parent 7e73b8fb37
commit 37bbac7e72
14 files changed
+1268 -157

No files matched your search

+34 -8
View File
@@ -1,11 +1,13 @@
"""nisse-review against the real services (Part R, M7.3): a PR in a fresh
test repository gets one COMMENT review from the review account, by the
local model; an account that could write to a repository is refused.
local model, and after a push and a question, a later review at the new
head (M7.5); an account that could write to a repository is refused.
Needs NISSE_REVIEW_GITEA_TOKEN, the review account's token (CI secret; .env
locally), besides what every live test needs.
"""
import base64
import io
import os
import uuid
@@ -21,10 +23,14 @@ from nisse_review.build import StartupError, build
from nisse_review.config import ReviewConfig
from nisse_review.review import marker
# Smoke: every PR runs it (about a minute on the local model), the only
# Smoke: every PR runs it (two reviews on the local model), the only
# check that the review account, its token and its read-only refusal work.
pytestmark = [pytest.mark.integration, pytest.mark.e2e, pytest.mark.smoke]
TEST_SHOUT = (
"from shout import shout\n\n\ndef test_shout():\n assert shout('hi') == 'HI!'\n"
)
def review_gitea(url: str) -> GiteaClient:
token = os.environ.get("NISSE_REVIEW_GITEA_TOKEN", "")
@@ -87,12 +93,32 @@ def test_a_ready_pr_gets_one_comment_review(
try:
assert built.scheduler.round() is True
assert built.scheduler.round() is False # reviewed once, not again
first = operator.pull_request(repo.name, pr.number).head_sha
mine = [
r for r in operator.reviews(repo.name, pr.number) if r.author == account
]
assert len(mine) == 1
assert mine[0].state == "COMMENT" and mine[0].commit == first
assert marker(first) in mine[0].body
# M7.5: a push and a question get a later review of what changed.
operator._request(
"POST",
f"/repos/{repo.name}/contents/sandbox/test_shout.py",
json={
"content": base64.b64encode(TEST_SHOUT.encode()).decode(),
"message": "Test shout",
"branch": "shout",
},
)
operator.post_comment(repo.name, pr.number, "I added a test. Is it enough?")
assert built.scheduler.round() is True
finally:
built.store.close()
head = operator.pull_request(repo.name, pr.number).head_sha
mine = [
r for r in operator.reviews(repo.name, pr.number) if r.author == account
second = operator.pull_request(repo.name, pr.number).head_sha
assert second != first
later = [
r
for r in operator.reviews(repo.name, pr.number)
if r.author == account and marker(second, 2) in r.body
]
assert len(mine) == 1
assert mine[0].state == "COMMENT" and mine[0].commit == head
assert marker(head) in mine[0].body
assert len(later) == 1 and later[0].commit == second