From 0694b47c34f7452654796d050f723c217dd98f71 Mon Sep 17 00:00:00 2001 From: Don Garrett Date: Mon, 19 Oct 2026 23:32:43 +0000 Subject: [PATCH] nisse-dev: a settled reply posted top-level is no point to answer either nisse-review's review of #169: a reply quoting a top-level point, or one Gitea refused on its line, arrives through list_comments, so answering it would start the thank-you loop again. Also records what the live Gitea showed: resolving is per comment, and resolving again is a 204. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk --- docs/Development-Plan.md | 3 ++- nisse-core/src/nisse_core/gitea.py | 4 +++- nisse-dev/src/nisse_dev/loop/agent.py | 2 ++ nisse-dev/tests/test_loop_peers.py | 17 +++++++++++++++++ 4 files changed, 24 insertions(+), 2 deletions(-) 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, ())