Answer where asked, wait on a merge, and let the driver title PRs
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 2m17s

From the new loop's first live run (sandbox #255, PR #256):
- A question asked on the PR is answered on the PR (an inline one quoted
  with its file and line, since the API can't reply in its thread), with
  a pointer in the item's Matrix thread; one asked in Matrix stays there.
- wait_for_review: when a review needs no change, or only the operator's
  merge is left, the item waits on the merge instead of on an answer.
- finish_milestone takes the PR's title from the driver.
The e2e test's merge waits out Gitea's brief 'try again later' after a
push.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
This commit is contained in:
dongarandClaude Opus 5.5 committed 2026-10-03 17:29:28 +00:00
1 parent bf416cad09
commit 651551aab4
6 files changed
+198 -13

No files matched your search

+93 -1
View File
@@ -80,7 +80,13 @@ class Forge:
def open_pull_request(self, repo: str, *, head, base, title, body) -> PullRequest:
n = 100 + len(self.prs)
self.prs[n] = {"head": head, "state": "open", "merged": False, "body": body}
self.prs[n] = {
"head": head,
"state": "open",
"merged": False,
"body": body,
"title": title,
}
return self._pr(n)
def pull_request(self, repo: str, index: int) -> PullRequest:
@@ -476,3 +482,89 @@ def test_abandoning_an_item_closes_its_open_pr(world: dict[str, Any]) -> None:
assert item(world).outcome == "abandoned"
assert forge.prs[pr]["state"] == "closed"
assert (pr, "abandoned: abandoned by @don:test") in forge.posted
# --- answering where asked (D5), waiting for review, PR titles -----------------
def test_a_question_on_the_pr_is_answered_on_the_pr(world: dict[str, Any]) -> None:
forge: Forge = world["forge"]
model = ScriptedModel(
RESTATE,
WORK + FINISH,
[call("ask_human", question="bool is a subclass of int; keep the check?")],
)
agent, transport = loop_agent(world, model)
agent.intake()
say(transport, world, "go")
[pr] = forge.prs
forge.notes[pr] = [ReviewNote("line-9", "don", "Is bool an int?", "text.py:9")]
agent.watch()
agent._schedule()
number, body = forge.posted[-1]
assert number == pr
assert body.startswith("> on `text.py:9`:\n> Is bool an int?\n\n")
assert body.endswith("keep the check?")
# Matrix gets a pointer, not the conversation.
assert (
transport.outbound[-1].text
== f"I answered your comment on PR #{pr}: https://g/pr/{pr}"
)
assert transport.outbound[-1].thread == loop_of(item(world)).thread
def test_after_a_matrix_reply_questions_go_back_to_matrix(
world: dict[str, Any],
) -> None:
forge: Forge = world["forge"]
model = ScriptedModel(
RESTATE,
WORK + FINISH,
[call("ask_human", question="first?")],
[call("ask_human", question="second?")],
)
agent, transport = loop_agent(world, model)
agent.intake()
say(transport, world, "go")
[pr] = forge.prs
forge.comments[pr] = [Comment(3, "don", "why this?", "t")]
agent.watch()
agent._schedule()
posted_on_pr = len(forge.posted)
say(transport, world, "answering in Matrix") # the operator moved
assert len(forge.posted) == posted_on_pr
assert transport.outbound[-1].text == "second?"
def test_wait_for_review_waits_on_the_merge_then_closes(world: dict[str, Any]) -> None:
forge: Forge = world["forge"]
model = ScriptedModel(
RESTATE,
WORK + FINISH,
[call("wait_for_review", note="no change needed; ready to merge")],
[call("close_item", summary="merged")],
)
agent, transport = loop_agent(world, model)
agent.intake()
say(transport, world, "go")
[pr] = forge.prs
forge.comments[pr] = [Comment(4, "don", "looks fine, merge it?", "t")]
agent.watch()
agent._schedule()
loop = loop_of(item(world))
assert (loop.wait, loop.note) == (MERGE, f"PR #{pr}")
assert forge.posted[-1][1].endswith("no change needed; ready to merge")
forge.merge(pr)
agent.watch()
agent._schedule()
assert item(world).status == "closed"
def test_the_driver_titles_its_pr(world: dict[str, Any]) -> None:
forge: Forge = world["forge"]
finish = [call("finish_milestone", summary="s", title="Add count_words")]
agent, transport = loop_agent(world, ScriptedModel(RESTATE, WORK + finish))
agent.intake()
say(transport, world, "go")
[pr] = forge.prs
assert forge.prs[pr]["title"] == "Add count_words"
+2
View File
@@ -53,6 +53,8 @@ class PolicyModel:
return [ToolCall(call_id=f"p{self.n}", tool="nonsense", arguments={})]
if "finish_milestone" not in offered:
return [call("restate", text=f"restatement {self.n}")]
if "wait_for_review" in offered and self.rng.random() < 0.2:
return [call("wait_for_review", note=f"waiting {self.n}")]
merged = any("merged" in t for t in facts[-6:])
if merged and self.rng.random() < 0.8:
return [call("close_item", summary=f"done {self.n}")]