feat: PM-Agent also need to look at PR comments
gate / gate (pull_request) Successful in 2m55s
images / images (pull_request) Successful in 1m10s
gate / conformance (pull_request) Has been skipped

From Gitea issue #116.

Authored by the PM agent's worker; the merge gate is the approver. Refs #116.

Agent: pm-agent (issue #116, mode implement)
This commit is contained in:
pm-agent committed 2026-11-07 05:18:24 +00:00
1 parent 5b484f6b78
commit 2fdb95393c
9 files changed
+2055 -32

No files matched your search

+536
View File
@@ -0,0 +1,536 @@
{
"_what_this_is": [
"Fixtures for the pull-request-mention trigger in infra/agent/pm-dispatch.py, driven by",
"`PM_SELFTEST=1 pm-dispatch` (and therefore by the Containerfile's verify stage and by",
"infra/agent/build-image.sh). Each case feeds normalize_pr_entries() + plan_pr_replies()",
"and asserts the EXACT set of threads that would be answered, with which comment keys.",
"",
"HOW HONEST THESE ARE, stated plainly because it matters for what a green selftest",
"proves. These objects are written to the field names and types in Gitea's published",
"swagger for the 1.24 line (Comment, PullReview, PullReviewComment, NotificationThread).",
"They were NOT recorded from a live server: the branch that wrote them held no Gitea",
"credential. So a passing case proves that this program's PARSING and TRIGGER LOGIC are",
"right for that shape -- the loop guard, the idempotency skip, the continuation policy --",
"and proves nothing about whether Gitea emits that shape. Only unused-and-wrong field",
"names can hide here, and the remedy is cheap: capture one real response with the curl",
"commands in platform/pm-agent/README.md and replace the objects below with it. Fields",
"this program never reads are kept anyway, so the diff against a real capture is small.",
"",
"Only the fields the dispatcher reads are load-bearing:",
" Comment ............. id, user.login, created_at, body",
" PullReview .......... id, user.login, submitted_at, body, comments_count",
" PullReviewComment ... id, user.login, created_at, body, path, position",
"",
"`expect.threads` is in the order plan_pr_replies returns: NEWEST-ANCHORED FIRST.",
"",
"THE CONTINUATION POLICY THESE PIN. A thread becomes a conversation the PM is part of",
"at the first mention in it (or at a marker in one of the PM's own comments), and every",
"later non-bot comment in that thread then triggers without a new mention -- the",
"operator's wording on issue #116, answer (4'). Cases (i) and (o) are the two edges:",
"comments AFTER the mention are in, comments BEFORE it are not. Case (l) pins that this",
"is per thread, not per pull request."
],
"bot_login": "pm-agent",
"cases": [
{
"name": "(a) a mention in a PR conversation comment is detected",
"pull": 120,
"comments": [
{
"id": 9001,
"user": {"login": "jakman85", "id": 1},
"created_at": "2026-11-07T09:00:00Z",
"updated_at": "2026-11-07T09:00:00Z",
"body": "@pm-agent can you look at the retry loop in cycle() here? I think it double-counts."
}
],
"reviews": [],
"review_comments": {},
"expect": {
"threads": [
{"thread": "conversation", "reason": "mention", "keys": ["comment-9001"]}
]
}
},
{
"name": "(b) a mention in an inline review comment is detected, as its own thread",
"pull": 121,
"comments": [],
"reviews": [
{
"id": 400,
"user": {"login": "jakman85", "id": 1},
"body": "",
"state": "COMMENT",
"submitted_at": "2026-11-07T09:10:00Z",
"comments_count": 1
}
],
"review_comments": {
"400": [
{
"id": 7701,
"user": {"login": "jakman85", "id": 1},
"created_at": "2026-11-07T09:10:00Z",
"body": "@pm-agent this cap looks off by one.",
"path": "infra/agent/pm-dispatch.py",
"position": 42,
"original_position": 42,
"pull_request_review_id": 400,
"diff_hunk": "@@ -1,3 +1,4 @@"
}
]
},
"expect": {
"threads": [
{
"thread": "inline:infra/agent/pm-dispatch.py@42",
"reason": "mention",
"keys": ["review-comment-7701"]
}
]
}
},
{
"name": "(c) a comment authored by the bot itself is ignored, mention and all",
"pull": 122,
"comments": [
{
"id": 9010,
"user": {"login": "pm-agent", "id": 7},
"created_at": "2026-11-07T09:20:00Z",
"body": "Worth a look: @pm-agent should re-read this once the gate is green."
},
{
"id": 9011,
"user": {"login": "pm-agent", "id": 7},
"created_at": "2026-11-07T09:21:00Z",
"body": "A plain status note from the agent, no mention at all."
}
],
"reviews": [],
"review_comments": {},
"expect": {"threads": []}
},
{
"name": "(d) an already-processed comment id is skipped",
"pull": 123,
"comments": [
{
"id": 9020,
"user": {"login": "jakman85", "id": 1},
"created_at": "2026-11-07T09:30:00Z",
"body": "@pm-agent what does the approval stage need here?"
},
{
"id": 9021,
"user": {"login": "pm-agent", "id": 7},
"created_at": "2026-11-07T09:31:00Z",
"body": "It needs a review from someone who is not the author.\n\n_Posted by the PM agent._\n\n<!-- pm-agent:replied-to=comment-9020 -->"
}
],
"reviews": [],
"review_comments": {},
"expect": {"threads": []}
},
{
"name": "(e) a follow-up with NO mention continues a thread the bot has replied in",
"pull": 124,
"comments": [
{
"id": 9030,
"user": {"login": "jakman85", "id": 1},
"created_at": "2026-11-07T09:40:00Z",
"body": "@pm-agent is this safe to merge on a green gate?"
},
{
"id": 9031,
"user": {"login": "pm-agent", "id": 7},
"created_at": "2026-11-07T09:41:00Z",
"body": "Yes, nothing here is a sensitive path.\n\n<!-- pm-agent:replied-to=comment-9030 -->"
},
{
"id": 9032,
"user": {"login": "jakman85", "id": 1},
"created_at": "2026-11-07T09:45:00Z",
"body": "And the conformance gap? No mention in this one on purpose."
}
],
"reviews": [],
"review_comments": {},
"expect": {
"threads": [
{"thread": "conversation", "reason": "continuation", "keys": ["comment-9032"]}
]
}
},
{
"name": "(f) an unrelated comment on an unrelated PR triggers nothing",
"pull": 125,
"comments": [
{
"id": 9040,
"user": {"login": "jakman85", "id": 1},
"created_at": "2026-11-07T09:50:00Z",
"body": "Rebased onto main; the cilium bump is in."
},
{
"id": 9041,
"user": {"login": "renovate", "id": 9},
"created_at": "2026-11-07T09:51:00Z",
"body": "Edited/Blocked: this PR is edited, Renovate will not update it."
}
],
"reviews": [
{
"id": 401,
"user": {"login": "jakman85", "id": 1},
"body": "Looks fine to me.",
"state": "COMMENT",
"submitted_at": "2026-11-07T09:52:00Z",
"comments_count": 0
}
],
"review_comments": {},
"expect": {"threads": []}
},
{
"name": "(g) a near-miss handle is not a mention: @pm-agent-worker and an email-like form",
"pull": 126,
"comments": [
{
"id": 9050,
"user": {"login": "jakman85", "id": 1},
"created_at": "2026-11-07T10:00:00Z",
"body": "The Job runs as @pm-agent-worker, and the log line was mailed to [email protected]. Also pm-agent without the at-sign."
}
],
"reviews": [],
"review_comments": {},
"expect": {"threads": []}
},
{
"name": "(h) a marker pasted by someone who is NOT the bot does not suppress a mention",
"pull": 127,
"comments": [
{
"id": 9060,
"user": {"login": "drive-by", "id": 4},
"created_at": "2026-11-07T10:10:00Z",
"body": "@pm-agent read this.\n\n<!-- pm-agent:replied-to=comment-9060 -->"
}
],
"reviews": [],
"review_comments": {},
"expect": {
"threads": [
{"thread": "conversation", "reason": "mention", "keys": ["comment-9060"]}
]
}
},
{
"name": "(i) a burst of three unanswered comments is ONE reply covering all three keys",
"pull": 128,
"comments": [
{
"id": 9070,
"user": {"login": "jakman85", "id": 1},
"created_at": "2026-11-07T10:20:00Z",
"body": "@pm-agent one question:"
},
{
"id": 9071,
"user": {"login": "jakman85", "id": 1},
"created_at": "2026-11-07T10:20:30Z",
"body": "actually two."
},
{
"id": 9072,
"user": {"login": "someone-else", "id": 5},
"created_at": "2026-11-07T10:21:00Z",
"body": "three, sorry."
}
],
"reviews": [],
"review_comments": {},
"expect": {
"threads": [
{
"thread": "conversation",
"reason": "mention",
"keys": ["comment-9070", "comment-9071", "comment-9072"]
}
]
}
},
{
"name": "(j) a mention in a review BODY is a conversation trigger, not an inline one",
"pull": 129,
"comments": [],
"reviews": [
{
"id": 402,
"user": {"login": "jakman85", "id": 1},
"body": "@pm-agent summarise what this changes, please.",
"state": "REQUEST_CHANGES",
"submitted_at": "2026-11-07T10:30:00Z",
"comments_count": 0
}
],
"review_comments": {},
"expect": {
"threads": [
{"thread": "conversation", "reason": "mention", "keys": ["review-402"]}
]
}
},
{
"name": "(k) two inline threads on one file are separate threads; the newest anchor comes first",
"pull": 130,
"comments": [],
"reviews": [
{
"id": 403,
"user": {"login": "jakman85", "id": 1},
"body": "",
"state": "COMMENT",
"submitted_at": "2026-11-07T10:40:00Z",
"comments_count": 2
}
],
"review_comments": {
"403": [
{
"id": 7710,
"user": {"login": "jakman85", "id": 1},
"created_at": "2026-11-07T10:40:00Z",
"body": "@pm-agent why this order?",
"path": "ci/gate.sh",
"position": 10,
"pull_request_review_id": 403
},
{
"id": 7711,
"user": {"login": "jakman85", "id": 1},
"created_at": "2026-11-07T10:41:00Z",
"body": "@pm-agent and this skip?",
"path": "ci/gate.sh",
"position": 55,
"pull_request_review_id": 403
}
]
},
"expect": {
"threads": [
{"thread": "inline:ci/gate.sh@55", "reason": "mention", "keys": ["review-comment-7711"]},
{"thread": "inline:ci/gate.sh@10", "reason": "mention", "keys": ["review-comment-7710"]}
]
}
},
{
"name": "(l) continuation is PER THREAD: a reply in the conversation does not open the inline thread",
"pull": 131,
"comments": [
{
"id": 9080,
"user": {"login": "jakman85", "id": 1},
"created_at": "2026-11-07T10:50:00Z",
"body": "@pm-agent have a look."
},
{
"id": 9081,
"user": {"login": "pm-agent", "id": 7},
"created_at": "2026-11-07T10:51:00Z",
"body": "Looked.\n\n<!-- pm-agent:replied-to=comment-9080 -->"
}
],
"reviews": [
{
"id": 404,
"user": {"login": "jakman85", "id": 1},
"body": "",
"state": "COMMENT",
"submitted_at": "2026-11-07T10:55:00Z",
"comments_count": 1
}
],
"review_comments": {
"404": [
{
"id": 7720,
"user": {"login": "jakman85", "id": 1},
"created_at": "2026-11-07T10:55:00Z",
"body": "no mention here, and the bot has never replied in THIS thread",
"path": "ci/lint.sh",
"position": 3,
"pull_request_review_id": 404
}
]
},
"expect": {"threads": []}
},
{
"name": "(m) an inline thread the bot has replied in continues without a mention",
"pull": 132,
"comments": [],
"reviews": [
{
"id": 405,
"user": {"login": "jakman85", "id": 1},
"body": "",
"state": "COMMENT",
"submitted_at": "2026-11-07T11:00:00Z",
"comments_count": 1
},
{
"id": 406,
"user": {"login": "pm-agent", "id": 7},
"body": "",
"state": "COMMENT",
"submitted_at": "2026-11-07T11:01:00Z",
"comments_count": 1
},
{
"id": 407,
"user": {"login": "jakman85", "id": 1},
"body": "",
"state": "COMMENT",
"submitted_at": "2026-11-07T11:05:00Z",
"comments_count": 1
}
],
"review_comments": {
"405": [
{
"id": 7730,
"user": {"login": "jakman85", "id": 1},
"created_at": "2026-11-07T11:00:00Z",
"body": "@pm-agent is this cap right?",
"path": "platform/pm-agent/configmap.yaml",
"position": 88,
"pull_request_review_id": 405
}
],
"406": [
{
"id": 7731,
"user": {"login": "pm-agent", "id": 7},
"created_at": "2026-11-07T11:01:00Z",
"body": "It matches the admission policy.\n\n<!-- pm-agent:replied-to=review-comment-7730 -->",
"path": "platform/pm-agent/configmap.yaml",
"position": 88,
"pull_request_review_id": 406
}
],
"407": [
{
"id": 7732,
"user": {"login": "jakman85", "id": 1},
"created_at": "2026-11-07T11:05:00Z",
"body": "and the worker cap?",
"path": "platform/pm-agent/configmap.yaml",
"position": 88,
"pull_request_review_id": 407
}
]
},
"expect": {
"threads": [
{
"thread": "inline:platform/pm-agent/configmap.yaml@88",
"reason": "continuation",
"keys": ["review-comment-7732"]
}
]
}
},
{
"name": "(o) a comment made BEFORE the mention is not pulled into the conversation",
"pull": 134,
"comments": [
{
"id": 9090,
"user": {"login": "jakman85", "id": 1},
"created_at": "2026-11-07T11:20:00Z",
"body": "Opening this for review."
},
{
"id": 9091,
"user": {"login": "someone-else", "id": 5},
"created_at": "2026-11-07T11:21:00Z",
"body": "LGTM."
},
{
"id": 9092,
"user": {"login": "jakman85", "id": 1},
"created_at": "2026-11-07T11:22:00Z",
"body": "@pm-agent what do you think?"
}
],
"reviews": [],
"review_comments": {},
"expect": {
"threads": [
{"thread": "conversation", "reason": "mention", "keys": ["comment-9092"]}
]
}
},
{
"name": "(n) a position-less inline comment still lands in a stable thread (review-id fallback)",
"pull": 133,
"comments": [],
"reviews": [
{
"id": 408,
"user": {"login": "jakman85", "id": 1},
"body": "",
"state": "COMMENT",
"submitted_at": "2026-11-07T11:10:00Z",
"comments_count": 1
}
],
"review_comments": {
"408": [
{
"id": 7740,
"user": {"login": "jakman85", "id": 1},
"created_at": "2026-11-07T11:10:00Z",
"body": "@pm-agent the whole file, not a line.",
"path": "runbooks/pm-agent.md",
"pull_request_review_id": 408
}
]
},
"expect": {
"threads": [
{
"thread": "inline:runbooks/pm-agent.md@review-408",
"reason": "mention",
"keys": ["review-comment-7740"]
}
]
}
}
],
"notification_subjects": [
{
"_note": "NotificationThread.subject shapes discover_pr_candidates must resolve to an index.",
"url": "http://gitea.gitea.svc.cluster.local:3000/api/v1/repos/Home-Network/home-network-k3s-impl/issues/120",
"expect_index": 120
},
{
"url": "http://gitea.gitea.svc.cluster.local:3000/api/v1/repos/Home-Network/home-network-k3s-impl/pulls/121",
"expect_index": 121
},
{
"_note": "A comment-anchored URL must still yield the PR index, never the comment id.",
"url": "http://gitea.gitea.svc.cluster.local:3000/api/v1/repos/Home-Network/home-network-k3s-impl/issues/122/comments/9001",
"expect_index": 122
},
{
"_note": "Nothing resolvable: discovery must skip it rather than guess.",
"url": "http://gitea.gitea.svc.cluster.local:3000/api/v1/repos/Home-Network/home-network-k3s-impl",
"expect_index": null
}
]
}