From 5d1a81ec480fac5ea77dc38f449a77cf3e18f0f9 Mon Sep 17 00:00:00 2001 From: Miguel Palhas Date: Thu, 20 Aug 2026 10:24:00 +0100 Subject: [PATCH] fix(review-pr): anchor findings to diff lines The gitea posting snippet used the issue-comment endpoint, which has no path or line, so findings named `path:line` in prose instead of landing on the code. Both forges now post one COMMENT review carrying anchored findings in comments[] and loose ones in the body. land's gitea baseline missed review-comment ids, which would replay every code comment on the first hint. Co-Authored-By: Claude Opus 5 (1M context) --- skills/land/SKILL.md | 14 +++++++--- skills/review-pr/SKILL.md | 55 ++++++++++++++++++++++++++++----------- 2 files changed, 51 insertions(+), 18 deletions(-) diff --git a/skills/land/SKILL.md b/skills/land/SKILL.md index 7e2f429..757fa3e 100644 --- a/skills/land/SKILL.md +++ b/skills/land/SKILL.md @@ -61,9 +61,11 @@ fi Gitea equivalent — issue comments plus reviews: ```bash -{ curl -sS -H "Authorization: token $GITEA_TOKEN" "$BASE/api/v1/repos/$REPO/issues/$N/comments"; \ - curl -sS -H "Authorization: token $GITEA_TOKEN" "$BASE/api/v1/repos/$REPO/pulls/$N/reviews"; } 2>/dev/null \ -| jq -r '.[]?.id' > "$seen" || : > "$seen" +{ curl -sS -H "Authorization: token $GITEA_TOKEN" "$BASE/api/v1/repos/$REPO/issues/$N/comments" | jq -r '.[]?.id' + for r in $(curl -sS -H "Authorization: token $GITEA_TOKEN" "$BASE/api/v1/repos/$REPO/pulls/$N/reviews" | jq -r '.[]?.id'); do + echo "$r" + curl -sS -H "Authorization: token $GITEA_TOKEN" "$BASE/api/v1/repos/$REPO/pulls/$N/reviews/$r/comments" | jq -r '.[]?.id' + done; } 2>/dev/null > "$seen" || : > "$seen" ``` **Request the Copilot review** (github only, once). Its comments then @@ -111,6 +113,12 @@ and act on what's left: echo "$rid" >> "$seen" ``` + Gitea has no reply endpoint, so that lands as a loose PR comment. To + answer a code comment inside its own thread, post a review instead + whose `comments[]` entry repeats the same `path` and `new_position` — + gitea groups code comments by position into one conversation. Record + the review id and its comment ids. + - **Resolve the thread** (github only — gitea has no per-thread resolve, so a short confirming reply plus the pushed fix is the signal): diff --git a/skills/review-pr/SKILL.md b/skills/review-pr/SKILL.md index dfbf91a..b0a79cd 100644 --- a/skills/review-pr/SKILL.md +++ b/skills/review-pr/SKILL.md @@ -62,43 +62,68 @@ than reviewing hunks in isolation. **Write the findings** to `/pr--findings.md` — in the git dir, not the working tree, so nothing lands in the branch under review. -One finding per entry: `path:line`, what's wrong, what to do. No praise, -no summary of what the PR does, no severity theatre. If you find -nothing, say so in one line. +One finding per entry: `path:line`, what's wrong, what to do — and mark +whether it anchors to a diff line or is a loose remark about the change +as a whole, which decides where it goes in §2. No praise, no summary of +what the PR does, no severity theatre. If you find nothing, say so in +one line. Then follow the mode: post (gitea) or report the file to the user and stop (github). ## 2. Posting +Post **one review** per pass, never a stream of separate comments. A +review carries two kinds of finding at once: + +- **Anchored** — the finding is about a specific line in the diff. It + belongs in `comments[]` with a `path` and a line, so it renders on + the code. +- **Loose** — the finding is about the change as a whole, or about code + the diff doesn't touch, or it has no single line to sit on. It goes + in the review `body`. + +Anchor whatever can be anchored. Writing `path:line` into prose when +the API would have put the comment on that line is the failure mode +this section exists to prevent. + Only after the user's go-ahead on gated repos. Record every id you post in the same step, or the next hint reads your own review as new feedback: ```bash -# gitea -rid=$(curl -sS -X POST -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" \ - "$BASE/api/v1/repos/$REPO/issues/$N/comments" -d "$(jq -nc --arg body "" '{body:$body}')" | jq -r .id) +# gitea — body is the loose findings, comments[] the anchored ones +# new_position = line in the new file; use old_position for a removed line +rid=$(jq -nc \ + --arg body "" \ + --argjson comments '[{"path":"path/to/file.ts","new_position":11,"body":""}]' \ + '{event:"COMMENT", body:$body, comments:$comments}' \ +| curl -sS -X POST -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" \ + "$BASE/api/v1/repos/$REPO/pulls/$N/reviews" -d @- | jq -r .id) echo "$rid" >> "$seen" +curl -sS -H "Authorization: token $GITEA_TOKEN" \ + "$BASE/api/v1/repos/$REPO/pulls/$N/reviews/$rid/comments" | jq -r '.[].id' >> "$seen" ``` ```bash -# github, after approval -rid=$(gh api repos///pulls//comments -f body="" \ - -f commit_id= -f path= -F line= --jq .id) -echo "$rid" >> "$seen" +# github, after approval — same shape, `line` instead of new_position +jq -nc --arg body "" \ + --argjson comments '[{"path":"path/to/file.ts","line":11,"body":""}]' \ + '{event:"COMMENT", commit_id:"", body:$body, comments:$comments}' \ +| gh api repos///pulls//reviews --input - --jq .id >> "$seen" +gh api repos///pulls//comments --jq '.[].id' >> "$seen" ``` -Prefer one review with several comments over a stream of separate -comments. **Never approve and never request changes as a review -decision** — that's the user's call on someone else's PR, and it carries -weight your findings don't. +`event: "COMMENT"` is the only event either forge should see from you. +**Never approve and never request changes as a review decision** — +that's the user's call on someone else's PR, and it carries weight your +findings don't. ## 3. Handling a hint | reason | what to do | | --- | --- | -| `comments` | Read comments not in the seen file. Someone replying to a finding gets an answer; a new comment thread may need a fresh look at that code. Record every id you handle or post. | +| `comments` | Read comments not in the seen file. Someone replying to a finding gets an answer; a new comment thread may need a fresh look at that code. Reply in the thread it came from: on github, `POST /pulls//comments//replies`; on gitea there is no reply endpoint, so post a review whose `comments[]` entry carries the same `path` and line — gitea groups code comments by position into one conversation. A loose reply goes to `POST /issues//comments`. Record every id you handle or post. | | `ci` | New head SHA: the author pushed. Re-read the diff for the new commits only, and check whether your open findings are addressed. Do not investigate their CI failures — not your PR. | | `state` | Merged or closed: write the outcome to the state file and stop. Draft flips: nothing to do. | | `conflicts` | Nothing to do. The author resolves conflicts on their own branch. |