nisse-review marks a reply that settles its thread; the PR's author resolves it
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m58s
CI / e2e (pull_request) Successful in 7m41s

The reviewer is read-only (R3), so Gitea refuses it resolving a thread.
Now each reply says whether it settles its thread, and a settled reply
carries <!-- nisse-review: settled -->. nisse-dev, as the author, resolves
every comment on that thread's line when it watches the PR, and doesn't
take the settled reply as a point to answer. scripts/resolve_settled.py
does the same for PRs made by hand.

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-19 22:06:56 +00:00
1 parent 6b85e5a039
commit 33d030590d
11 files changed
+384 -12

No files matched your search

+50 -1
View File
@@ -18,7 +18,7 @@ from test_loop_milestones import item as the_item
from test_loop_milestones import world as world # noqa: PLC0414 -- the fixture
from test_loop_trust import agent_for
from nisse_core.gitea import Comment, LineComment, ReviewNote
from nisse_core.gitea import SETTLED_MARKER, Comment, LineComment, ReviewNote
from nisse_core.log.canonical import Fact
from nisse_dev.loop.items import MERGE, READY, WAITING, loop_of
@@ -244,3 +244,52 @@ def test_a_peer_neither_merges_nor_instructs(world: dict[str, Any]) -> None:
]
assert not any("merge it" in getattr(e.event, "text", "") for e in operator)
assert loop_of(the_item(world)).wait == MERGE
# --- settled threads ------------------------------------------------------------
def settled_thread(forge: Forge, pr: int) -> None:
"""A point on a line, nisse-dev's answer, and the reviewer conceding."""
forge.notes[pr] = [
ReviewNote("line-11", REVIEWER, "Split on any whitespace?", "text.py:3"),
ReviewNote("line-12", "nisse-dev", "split() already does.", "text.py:3"),
ReviewNote("line-13", REVIEWER, f"Conceded.\n\n{SETTLED_MARKER}", "text.py:3"),
]
def test_a_settled_thread_is_resolved_not_answered(world: dict[str, Any]) -> None:
forge: Forge = world["forge"]
agent, _, pr = published(world)
settled_thread(forge, pr)
forge.notes[pr].append(ReviewNote("line-14", REVIEWER, "Still: a test?", "a.py:1"))
agent.watch()
assert forge.resolved == [11, 12, 13]
keys = [p[1] for p in loop_of(the_item(world)).points]
assert "line-13" not in keys and "line-14" in keys # the concession needs no answer
def test_a_resolved_thread_is_not_resolved_again(world: dict[str, Any]) -> None:
forge: Forge = world["forge"]
agent, _, pr = published(world)
settled_thread(forge, pr)
forge.notes[pr] = [
ReviewNote(n.key, n.author, n.body, n.where, resolved=True)
for n in forge.notes[pr]
]
agent.watch()
assert forge.resolved == []
def test_a_refused_resolve_leaves_the_thread_and_is_tried_again(
world: dict[str, Any],
) -> None:
forge: Forge = world["forge"]
agent, _, pr = published(world)
settled_thread(forge, pr)
forge.refuse_resolve = True
agent.watch()
assert forge.resolved == []
forge.refuse_resolve = False
agent.watch()
assert forge.resolved == [11, 12, 13]