diff --git a/docs/Development-Plan.md b/docs/Development-Plan.md index b34e6f9..e898f71 100644 --- a/docs/Development-Plan.md +++ b/docs/Development-Plan.md @@ -693,7 +693,8 @@ of earlier points, replies on threads, the round cap and its reset, `stop` and change). A settled reply carries ``. Being read-only (R3), the reviewer can't resolve the thread, so the PR's author does: nisse-dev resolves every comment on that thread's line when it next - watches the PR and takes the settled reply as no point to answer; + watches the PR and takes the settled reply as no point to answer (nor one + posted top-level, which has no thread to resolve); `scripts/resolve_settled.py` does the same for PRs made by hand. - **Sign-off:** `sign_off` posts a short review when there is nothing to raise; while it stays happy, a later review offers `still_happy`, which posts nothing diff --git a/nisse-core/src/nisse_core/gitea.py b/nisse-core/src/nisse_core/gitea.py index 0ea75a3..568be97 100644 --- a/nisse-core/src/nisse_core/gitea.py +++ b/nisse-core/src/nisse_core/gitea.py @@ -97,7 +97,9 @@ SETTLED_MARKER = "" def to_resolve(notes: list[ReviewNote]) -> list[int]: """The ids of the unresolved line comments in threads a settled reply - closed: every comment on that path:line, as Gitea groups a thread.""" + closed: every comment on that path:line, as Gitea groups a thread. + Gitea resolves one comment at a time (seen on 28.2: the rest of the + thread still reports no resolver), and resolving one again is a 204.""" settled = { n.where for n in notes if n.key.startswith("line-") and SETTLED_MARKER in n.body } diff --git a/nisse-dev/src/nisse_dev/loop/agent.py b/nisse-dev/src/nisse_dev/loop/agent.py index df57215..329e6d9 100644 --- a/nisse-dev/src/nisse_dev/loop/agent.py +++ b/nisse-dev/src/nisse_dev/loop/agent.py @@ -1111,6 +1111,8 @@ class LoopAgent: loop = replace(loop, comments=comment.id) if comment.author == self._account: continue # its own replies + if SETTLED_MARKER in comment.body: + continue # a settled reply posted top-level: nothing to answer if comment.author not in self._gitea_operators: point = f"c{comment.id}" loop = self._peer_point( diff --git a/nisse-dev/tests/test_loop_peers.py b/nisse-dev/tests/test_loop_peers.py index ec333d0..690c91d 100644 --- a/nisse-dev/tests/test_loop_peers.py +++ b/nisse-dev/tests/test_loop_peers.py @@ -293,3 +293,20 @@ def test_a_refused_resolve_leaves_the_thread_and_is_tried_again( forge.refuse_resolve = False agent.watch() assert forge.resolved == [11, 12, 13] + + +def test_a_settled_reply_posted_top_level_is_not_a_point( + world: dict[str, Any], +) -> None: + """One quoting a top-level point, or Gitea refused on its line: there's + no thread to resolve, and answering it would start the loop again.""" + forge: Forge = world["forge"] + agent, _, pr = published(world) + forge.comments[pr] = [ + Comment( + 15, REVIEWER, f"> nisse-dev:\n> Done.\n\nThanks.\n{SETTLED_MARKER}", "t" + ) + ] + agent.watch() + loop = loop_of(the_item(world)) + assert (the_item(world).position, loop.points) == (WAITING, ())