This is an automated email from the ASF dual-hosted git repository.
morningman pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/doris-skills.git
The following commit(s) were added to refs/heads/main by this push:
new da8171a [opt] opt review skill to post comment once psas (#7)
da8171a is described below
commit da8171aa6fbf29753a7307ba8bd2296f2c5a4638
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Mon Aug 17 23:28:25 2026 +0800
[opt] opt review skill to post comment once psas (#7)
---
README.md | 6 +-
skills/doris-repo-review/SKILL.md | 80 ++++++-
.../references/pr-comment-format.md | 121 ++++++++++
.../doris-repo-review/scripts/post-pass-comment.sh | 252 +++++++++++++++++++++
4 files changed, 449 insertions(+), 10 deletions(-)
diff --git a/README.md b/README.md
index 598efc7..0ff7565 100644
--- a/README.md
+++ b/README.md
@@ -34,10 +34,12 @@ skill from its `description`, so in practice you describe
the problem and the ri
| Skill | What it does | Use it when |
|---|---|---|
-| [`doris-repo-review`](skills/doris-repo-review/) | Contributor-side PR
review of a local `apache/doris` clone, running the same flow as the CI Code
Review Runner: worktree alignment, risk scan, subagent fan-out, shared ledger,
anchored EN + ZH review documents | Reviewing an `apache/doris` pull request
locally, the way the pipeline would |
+| [`doris-repo-review`](skills/doris-repo-review/) | Contributor-side PR
review of a local `apache/doris` clone, running the same flow as the CI Code
Review Runner: worktree alignment, risk scan, subagent fan-out, shared ledger,
anchored EN + ZH review documents, and — on a passing review — one
machine-readable PASS comment on the PR | Reviewing an `apache/doris` pull
request locally, the way the pipeline would |
These skills read a Doris **source** checkout and never touch a cluster; the
cluster-side skills
-never touch repository source. Nothing here builds Doris, runs tests, or
writes to GitHub.
+never touch repository source. Nothing here builds Doris or runs tests. The
one GitHub write in
+the whole set is `doris-repo-review`'s PASS comment — posted only when the
review finds no
+`Blocker` and no `Major`, and only after the user has seen the exact body and
approved it.
## Install
diff --git a/skills/doris-repo-review/SKILL.md
b/skills/doris-repo-review/SKILL.md
index 6667d22..c460e38 100644
--- a/skills/doris-repo-review/SKILL.md
+++ b/skills/doris-repo-review/SKILL.md
@@ -1,6 +1,6 @@
---
name: doris-repo-review
-description: Given a PR URL (`/doris-repo-review
https://github.com/apache/doris/pull/66807`), first check whether the current
directory's branch and commit match that PR, and if they do not, align the
current directory to the PR head without disturbing local work (refuse to
switch when tracked files are modified and hand the decision back to the user);
then review it with the same pipeline apache/doris CI runs (Code Review Runner)
- main-agent risk scan, 1-3 full-review subagents plus r [...]
+description: Given a PR URL (`/doris-repo-review
https://github.com/apache/doris/pull/66807`), first check whether the current
directory's branch and commit match that PR, and if they do not, align the
current directory to the PR head without disturbing local work (refuse to
switch when tracked files are modified and hand the decision back to the user);
then review it with the same pipeline apache/doris CI runs (Code Review Runner)
- main-agent risk scan, 1-3 full-review subagents plus r [...]
---
# Local pipeline-style Doris code review
@@ -35,6 +35,7 @@ $S/align-to-pr.sh <PR> --check #
diagnosis only, changes no
$S/align-to-pr.sh <PR> --out "$CTX/align.env" # step 1: align the
current directory
$S/prepare-review-context.sh --ctx "$CTX" --align "$CTX/align.env" # step 2:
gather context
python3 $S/verify-anchors.py --ctx "$CTX" --doc <en> --doc <zh> # step 9:
verify anchors
+$S/post-pass-comment.sh --ctx "$CTX" --model <id> ... --dry-run # step
10: PASS comment
```
| File | Purpose |
@@ -42,8 +43,10 @@ python3 $S/verify-anchors.py --ctx "$CTX" --doc <en> --doc
<zh> # step 9: v
| `scripts/align-to-pr.sh` | Resolve the PR, diagnose how the current
directory relates to it, align it to the PR head |
| `scripts/prepare-review-context.sh` | Produce the authoritative diff,
new-side line ranges, required AGENTS.md list, existing comments, ledger
skeleton |
| `scripts/verify-anchors.py` | Check that every `path:line` anchor really
exists and that both documents expose the same finding IDs |
+| `scripts/post-pass-comment.sh` | Render and post the machine-readable PASS
comment; refuses everything that is not a pass |
| `references/prompts.md` | Subagent prompt templates (CI wording, carried
over verbatim) |
| `references/doc-templates.md` | Templates for both documents, anchor format,
verdict rule |
+| `references/pr-comment-format.md` | The `doris-repo-review/v1` comment
schema, field meanings, and how a program reads it back |
Requirements: `git`, an authenticated `gh` CLI, `jq`, and `python3`. The clone
must have full
history (`git fetch --unshallow` on a shallow one), because the authoritative
diff is a three-dot
@@ -59,8 +62,13 @@ diff from the merge base.
never go hunting for some other directory to work in. Every switch is
`git checkout --detach`, so branch refs stay exactly where they were.
2. **Read-only review.** Do not build, do not run tests, do not modify any
source file in the
- repository. The only writes allowed are the two documents under
`review-docs/` and the context
- directory `$CTX`. **Post nothing to GitHub.**
+ repository. The only local writes allowed are the two documents under
`review-docs/` and the
+ context directory `$CTX`.
+ **Exactly one thing may ever be written to GitHub**: the PASS comment of
step 10, only when the
+ verdict is APPROVE, only through `post-pass-comment.sh`, and only after the
user has seen the
+ rendered body and said go. A REQUEST_CHANGES review posts nothing. No
inline comments, no
+ review submission, no labels, no edits to the PR body - and never a comment
on any PR other
+ than the one being reviewed.
3. **The diff has exactly one source**: `$CTX/pr.diff` and
`$CTX/pr_changed_files.txt`. Do not
reach for `gh pr diff`, the web UI, or a hand-rolled `git diff` to get the
change list - a
different way of fetching it means a different base.
@@ -312,7 +320,50 @@ unchanged context lines - which is usually where a
miscomputed line number shows
---
-## 10. Closing report
+## 10. Post the PASS comment to the PR
+
+**Only when the verdict is APPROVE** (no `Blocker`, no `Major`) and
`verify-anchors.py` has
+passed. A REQUEST_CHANGES review posts nothing at all - say so in the closing
report and stop.
+
+Write the notes, dry-run, get a go, post:
+
+```bash
+# At most 5 bullets, each anchored where it can be. Skip the file when there
is nothing to say.
+cat > "$CTX/pr-comment-notes.md" <<'EOF'
+- `fe/fe-core/src/main/java/org/apache/doris/X.java:214` — <what the
maintainer should know>
+EOF
+
+$S/post-pass-comment.sh --ctx "$CTX" \
+ --model "<exact model id of this session>" --effort
"${CLAUDE_EFFORT:-unknown}" \
+ --findings <blocker>,<major>,<minor>,<nit> \
+ --rounds <r> --converged <true|false> \
+ --notes-file "$CTX/pr-comment-notes.md" \
+ --dry-run
+```
+
+- **`--model` is the exact model id of the session doing the review**
(`claude-opus-5[1m]`,
+ `gpt-5.6-sol`, …), taken from what this session was told about itself -
never a guess, never a
+ bare family name. `--effort` comes from `$CLAUDE_EFFORT`. The comment is a
public, signed
+ statement about who reviewed the code; both fields are what make it
auditable.
+- The dry run runs **every** precondition and prints the exact body. **Show
that body to the user
+ and wait for a go**, then re-run the identical command **without
`--dry-run`**. Never post
+ without that confirmation.
+- The script refuses to post when: any `Blocker`/`Major` is present; the live
PR head no longer
+ equals the reviewed commit; the PR is not open (`--allow-closed` overrides);
the notes are
+ malformed or more than five; `converged: false` came without a note. A
refusal is a real signal
+ - relay it, do not work around it.
+- **The PR head moved** means the author pushed during the review: the review
is stale, so re-run
+ the whole skill instead of posting.
+- An earlier v1 comment from the same account for the **same** commit is
edited in place; a new
+ commit gets a new comment, so each push leaves exactly one record.
+- Findings counts, rounds and `converged` must match the documents written in
step 9. The counts
+ are of *accepted* findings, not of candidates.
+- `references/pr-comment-format.md` holds the schema, the field meanings, and
the parser snippet.
+ **Never hand-write or hand-edit this comment** - the format is a contract
other programs read.
+
+---
+
+## 11. Closing report
Tell the user:
@@ -320,15 +371,17 @@ Tell the user:
severity, and whether the rounds converged.
2. The `branch check` / `commit check` results - especially `ahead:N`
(unpushed commits that were
not reviewed).
-3. **Where the current directory now stands**: with `ALIGN_MODE=switched` it
is detached on the PR
+3. **What happened to the PASS comment**: the URL when one was posted or
updated, or the reason
+ nothing was posted (the verdict was REQUEST_CHANGES, the PR head moved, the
user said no).
+4. **Where the current directory now stands**: with `ALIGN_MODE=switched` it
is detached on the PR
head, and `git checkout <PREV_REF>` restores it. **Do not switch back
automatically** - the user
may still want to read the code.
-4. `review-docs/` **is not gitignored in the doris repository**, so **do not
commit it
+5. `review-docs/` **is not gitignored in the doris repository**, so **do not
commit it
automatically**; leave that to the user.
---
-## 11. Mapping to the CI pipeline
+## 12. Mapping to the CI pipeline
| CI (code-review-runner.yml) | Local |
|---|---|
@@ -342,11 +395,12 @@ Tell the user:
| Single-file ledger with sections | A `ledger/` directory, one file per owner
|
| Main risk scan → 1-3 full-review subagents + risk-focused → merge → ≤3
rounds | Identical |
| `gh pr review` / Reviews API posting inline comments | **Two `review-docs/`
documents (EN + ZH) with `path:line` anchors** |
+| CI's review verdict is visible on the PR itself | On a pass, one
`doris-repo-review/v1` comment from the local account (commit sha, timestamp,
model, findings, notes); on REQUEST_CHANGES, nothing - the documents stay local
|
| 60-minute timeout | No hard timeout, but likewise do not let one round turn
into unbounded digging |
---
-## 12. Common traps
+## 13. Common traps
- **The local checkout is ahead of the PR head.** `commit check: ahead:N`
means there are unpushed
commits and **they are not part of the review**. This is the easiest thing
for a reader to
@@ -370,3 +424,13 @@ Tell the user:
dropped.
- **Do not commit `review-docs/`.** The doris repository does not ignore it,
and an automatic commit
would slip it into the PR.
+- **The PASS comment is public and signed with the user's name.** It goes to a
public Apache PR
+ from their GitHub account, so it is posted only after they have seen the
exact body. Treat a
+ script refusal as final rather than something to route around, and never
"tidy up" the rendered
+ body by hand - a program reads it.
+- **A pass is not a merge approval.** The comment states that a local pipeline
review found no
+ Blocker and no Major on one specific commit. It carries no CI signal and no
Apache sign-off, and
+ the `<sub>` disclaimer line says exactly that - keep it.
+- **Counts drift between the documents and the comment.** `--findings` must be
the accepted
+ findings of step 9, not the candidate count from the ledger; re-count from
the written documents
+ before posting.
diff --git a/skills/doris-repo-review/references/pr-comment-format.md
b/skills/doris-repo-review/references/pr-comment-format.md
new file mode 100644
index 0000000..a26488e
--- /dev/null
+++ b/skills/doris-repo-review/references/pr-comment-format.md
@@ -0,0 +1,121 @@
+# PASS comment format (`doris-repo-review/v1`)
+
+When a review passes, the skill posts **one comment** on the PR from the
locally authenticated
+`gh` account. The comment is written by `scripts/post-pass-comment.sh`, never
typed by hand: the
+agent supplies the numbers and the notes, the script owns the layout. Anything
that reads the
+comment back — a script, a dashboard, another agent — depends on that layout
being fixed.
+
+## Layout
+
+Three layers, each with one job:
+
+| Layer | Content | Consumer |
+|---|---|---|
+| HTML markers `<!-- doris-repo-review:v1:begin -->` / `:end` | anchor for
finding and updating the comment | programs |
+| fenced `yaml` block | every structured field | programs (`yaml.safe_load`),
and humans |
+| markdown body | status heading, `Notes for maintainers`, disclaimer | humans
|
+
+````markdown
+<!-- doris-repo-review:v1:begin -->
+### Local pipeline review — ✅ PASS
+
+```yaml
+schema: doris-repo-review/v1
+status: PASS
+pr: apache/doris#66807
+commit: 7f3a91c4e0b25d8a6c1f04b93e27ad5810cf6b42
+base: 0b1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f60718293
+reviewed_at: 2026-08-17T21:22+08:00
+reviewer: morningman
+model: claude-opus-5[1m]
+effort: max
+findings: {blocker: 0, major: 0, minor: 2, nit: 1}
+rounds: 2
+converged: true
+```
+
+**Notes for maintainers**
+
+-
`fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapManager.java:214`
— the
+ empty-password fast path is covered by unit tests only; an LDAP regression
case is worth adding
+ before this is backported to `branch-3.0`.
+- The behaviour change is gated by `ldap_authentication_enabled`, so no
rolling-upgrade path is
+ required.
+
+<sub>Reviewed locally with the `doris-repo-review` pipeline (a local port of
+`.github/workflows/code-review-runner.yml`). This is not a CI status
check.</sub>
+<!-- doris-repo-review:v1:end -->
+````
+
+## Fields
+
+| Field | Type | Source |
+|---|---|---|
+| `schema` | const `doris-repo-review/v1` | the script; bump it if any field
changes meaning |
+| `status` | `PASS` | the only value ever posted; `CHANGES_REQUESTED` is
reserved, not used |
+| `pr` | `owner/repo#N` | `meta.env` |
+| `commit` | 40-hex | `HEAD_SHA` — the exact commit reviewed |
+| `base` | 40-hex | `BASE_SHA` — with `commit` this reproduces the reviewed
diff |
+| `reviewed_at` | ISO-8601, minute precision, with offset | when the comment
was rendered |
+| `reviewer` | GitHub login | `gh api user`, falling back to GraphQL `viewer`
and `gh auth status` |
+| `model` | exact model id | passed with `--model`, e.g. `claude-opus-5[1m]`,
`gpt-5.6-sol` |
+| `effort` | reasoning effort | `--effort`, default `$CLAUDE_EFFORT` |
+| `findings` | inline map | counts per severity; `blocker` and `major` are
always 0 in a PASS |
+| `rounds` | int | convergence rounds actually run (1-3) |
+| `converged` | bool | `false` means the 3-round cap was hit with candidates
still open |
+
+Rules the script enforces, so they cannot drift:
+
+- **PASS only.** Any `blocker` or `major` above zero is a `REQUEST_CHANGES`
review and the script
+ refuses to post — a failing review leaves no trace on GitHub.
+- **The PR head must not have moved.** The live `head.sha` is re-read and must
equal `commit`,
+ otherwise the comment would sign off a commit nobody reviewed.
+- **`converged: false` requires at least one note** saying what was left
unexamined.
+- **At most 5 notes**, each a `- ` bullet (2-space indented continuation lines
allowed). Longer
+ material belongs in the local review documents.
+- **Same commit ⇒ update in place.** An earlier v1 comment by the same account
carrying the same
+ `commit` is edited via `PATCH`; a different commit gets a new comment, so
one push leaves one
+ record.
+- **Minute precision on `reviewed_at` is deliberate.** A YAML timestamp needs
seconds, so
+ `2026-08-17T21:22+08:00` stays a plain string in every parser instead of
turning into a date
+ object in some of them. Do not add seconds.
+
+## Reading it back
+
+```python
+import re, yaml, json, subprocess
+
+comments = json.loads(subprocess.check_output(
+ ["gh", "api", "repos/apache/doris/issues/66807/comments", "--paginate"]))
+
+for c in comments:
+ m = re.search(r"<!-- doris-repo-review:v1:begin -->.*?```yaml\n(.*?)```",
c["body"], re.S)
+ if not m:
+ continue
+ d = yaml.safe_load(m.group(1))
+ print(d["status"], d["commit"], d["reviewed_at"], d["model"], d["effort"])
+ # "has the current head been reviewed and passed?"
+ passed = d["status"] == "PASS" and d["commit"] == head_sha
+```
+
+`grep`-level check, when a full parse is overkill:
+
+```bash
+gh api repos/apache/doris/issues/66807/comments --paginate \
+ --jq '.[] | select(.body | test("doris-repo-review:v1:begin")) | .body' \
+ | grep -E '^(status|commit|model|reviewed_at):'
+```
+
+## Notes for maintainers — what belongs there
+
+The comment is not a substitute for the review documents; it is the receipt. A
note earns its
+place only when a maintainer would act differently without it:
+
+- a residual risk that did not reach `Minor`, with a `path:line` anchor;
+- coverage the review could not reach (no build, no cluster, no test run);
+- a backport or upgrade consideration the PR itself does not state;
+- with `converged: false`, what was still open when the round cap hit.
+
+Not this: restating what the PR does, listing every `Minor`/`Nit` (they live
in the documents),
+praise, or anything that reads as an official Apache sign-off. `_None._` is a
perfectly good
+notes section.
diff --git a/skills/doris-repo-review/scripts/post-pass-comment.sh
b/skills/doris-repo-review/scripts/post-pass-comment.sh
new file mode 100755
index 0000000..fcfd6a0
--- /dev/null
+++ b/skills/doris-repo-review/scripts/post-pass-comment.sh
@@ -0,0 +1,252 @@
+#!/usr/bin/env bash
+# Render and post the machine-readable PASS comment of a doris-repo-review run.
+#
+# Usage:
+# post-pass-comment.sh --ctx <dir> --model <id> [options]
+#
+# --ctx <dir> review context directory (must contain meta.env)
+# --model <id> exact model id of the reviewing agent, e.g.
claude-opus-5[1m]
+# --effort <s> reasoning effort (default: $CLAUDE_EFFORT, else
"unknown")
+# --findings b,m,mi,n blocker,major,minor,nit counts (default 0,0,0,0)
+# --rounds <n> convergence rounds actually run (default 1)
+# --converged true|false (default true)
+# --notes-file <f> markdown bullet list for "Notes for maintainers"
+# --dry-run run every precondition, render the body, post
nothing
+# --force-new always create a new comment, never update in place
+# --allow-closed allow posting on a non-open PR
+#
+# Only a PASS is ever posted: blocker and major counts must both be 0, which is
+# the same verdict rule the review documents use. The body layout is fixed here
+# on purpose - the agent supplies the notes, never the format.
+#
+# Rendered body: <ctx>/pr-comment.md Posted URL: <ctx>/pr-comment.url
+set -euo pipefail
+
+CTX=""
+MODEL=""
+EFFORT="${CLAUDE_EFFORT:-unknown}"
+FINDINGS="0,0,0,0"
+ROUNDS="1"
+CONVERGED="true"
+NOTES_FILE=""
+DRY_RUN=0
+FORCE_NEW=0
+ALLOW_CLOSED=0
+
+while [ $# -gt 0 ]; do
+ case "$1" in
+ --ctx) CTX="$2"; shift 2 ;;
+ --model) MODEL="$2"; shift 2 ;;
+ --effort) EFFORT="$2"; shift 2 ;;
+ --findings) FINDINGS="$2"; shift 2 ;;
+ --rounds) ROUNDS="$2"; shift 2 ;;
+ --converged) CONVERGED="$2"; shift 2 ;;
+ --notes-file) NOTES_FILE="$2"; shift 2 ;;
+ --dry-run) DRY_RUN=1; shift ;;
+ --force-new) FORCE_NEW=1; shift ;;
+ --allow-closed) ALLOW_CLOSED=1; shift ;;
+ -h|--help) sed -n '2,25p' "$0"; exit 0 ;;
+ *) echo "unknown argument: $1" >&2; exit 2 ;;
+ esac
+done
+
+BEGIN_MARKER='<!-- doris-repo-review:v1:begin -->'
+END_MARKER='<!-- doris-repo-review:v1:end -->'
+SCHEMA='doris-repo-review/v1'
+
+# ------------------------------------------------------------------
preconditions
+[ -n "$CTX" ] || { echo "ERROR: --ctx is required." >&2; exit 2; }
+META="$CTX/meta.env"
+[ -f "$META" ] || { echo "ERROR: $META not found - run
prepare-review-context.sh first." >&2; exit 2; }
+[ -n "$MODEL" ] || { echo "ERROR: --model is required; state the exact model
id, never a guess." >&2; exit 2; }
+command -v gh >/dev/null 2>&1 || { echo "ERROR: gh CLI is required." >&2; exit
1; }
+command -v jq >/dev/null 2>&1 || { echo "ERROR: jq is required." >&2; exit 1; }
+command -v python3 >/dev/null 2>&1 || { echo "ERROR: python3 is required."
>&2; exit 1; }
+
+read_meta() { sed -n "s/^$1=//p" "$META" | head -n 1; }
+UPSTREAM_REPO="$(read_meta UPSTREAM_REPO)"
+PR_NUMBER="$(read_meta PR_NUMBER)"
+PR_URL="$(read_meta PR_URL)"
+BASE_SHA="$(read_meta BASE_SHA)"
+HEAD_SHA="$(read_meta HEAD_SHA)"
+REPO_ROOT="$(read_meta REPO_ROOT)"
+
+[ -n "$PR_NUMBER" ] || { echo "ERROR: meta.env has no PR_NUMBER - this review
is not attached to a PR." >&2; exit 2; }
+[ -n "$UPSTREAM_REPO" ] || { echo "ERROR: meta.env has no UPSTREAM_REPO." >&2;
exit 2; }
+[ -n "$PR_URL" ] ||
PR_URL="https://github.com/${UPSTREAM_REPO}/pull/${PR_NUMBER}"
+
+case "$CONVERGED" in true|false) ;; *) echo "ERROR: --converged takes true or
false." >&2; exit 2 ;; esac
+[[ "$ROUNDS" =~ ^[0-9]+$ ]] || { echo "ERROR: --rounds takes a number." >&2;
exit 2; }
+IFS=',' read -r F_BLOCKER F_MAJOR F_MINOR F_NIT <<<"$FINDINGS"
+for v in "$F_BLOCKER" "$F_MAJOR" "$F_MINOR" "$F_NIT"; do
+ [[ "$v" =~ ^[0-9]+$ ]] || { echo "ERROR: --findings takes
blocker,major,minor,nit as four numbers." >&2; exit 2; }
+done
+
+# The verdict rule: any Blocker or Major means REQUEST_CHANGES, and a
+# REQUEST_CHANGES review posts nothing at all.
+if [ "$F_BLOCKER" -gt 0 ] || [ "$F_MAJOR" -gt 0 ]; then
+ echo "ERROR: $F_BLOCKER blocker(s) and $F_MAJOR major(s) mean
REQUEST_CHANGES." >&2
+ echo " This skill posts a comment only when the review passes.
Nothing was posted." >&2
+ exit 1
+fi
+
+# -------------------------------------------------------------------------
notes
+NOTES_BODY="_None._"
+NOTE_COUNT=0
+if [ -n "$NOTES_FILE" ]; then
+ [ -f "$NOTES_FILE" ] || { echo "ERROR: --notes-file not found:
$NOTES_FILE" >&2; exit 2; }
+ if grep -q . "$NOTES_FILE"; then
+ if grep -q 'doris-repo-review:v1' "$NOTES_FILE"; then
+ echo "ERROR: the notes file contains a v1 marker; that would break
comment detection." >&2
+ exit 2
+ fi
+ while IFS= read -r line; do
+ [ -n "${line//[[:space:]]/}" ] || continue
+ case "$line" in
+ "- "*) NOTE_COUNT=$((NOTE_COUNT + 1)) ;;
+ " "*) : ;; # continuation of the previous bullet
+ *) echo "ERROR: every note line must be a '- ' bullet or a
2-space continuation: $line" >&2; exit 2 ;;
+ esac
+ done < "$NOTES_FILE"
+ [ "$NOTE_COUNT" -le 5 ] || { echo "ERROR: at most 5 notes; got
$NOTE_COUNT. Keep the rest in the review document." >&2; exit 2; }
+ NOTES_BODY="$(cat "$NOTES_FILE")"
+ fi
+fi
+if [ "$CONVERGED" = "false" ] && [ "$NOTE_COUNT" -eq 0 ]; then
+ echo "ERROR: converged=false needs at least one note saying what was left
open." >&2
+ exit 2
+fi
+
+# ------------------------------------------------------------------ live PR
state
+PR_TSV="$(gh api "repos/${UPSTREAM_REPO}/pulls/${PR_NUMBER}" --jq '[.head.sha,
.state] | @tsv')" || {
+ echo "ERROR: cannot read ${UPSTREAM_REPO}#${PR_NUMBER}." >&2; exit 1; }
+IFS=$'\t' read -r LIVE_HEAD_SHA LIVE_STATE <<<"$PR_TSV"
+
+if [ "$LIVE_HEAD_SHA" != "$HEAD_SHA" ]; then
+ echo "ERROR: the PR head moved since the review." >&2
+ echo " reviewed : $HEAD_SHA" >&2
+ echo " PR now at: $LIVE_HEAD_SHA" >&2
+ echo " Posting would sign off a commit that was never reviewed.
Re-run the review." >&2
+ exit 1
+fi
+if [ "$LIVE_STATE" != "open" ] && [ "$ALLOW_CLOSED" = "0" ]; then
+ echo "ERROR: ${UPSTREAM_REPO}#${PR_NUMBER} is '$LIVE_STATE', not open.
Pass --allow-closed to post anyway." >&2
+ exit 1
+fi
+
+if [ -n "$REPO_ROOT" ] && [ -d "$REPO_ROOT/.git" ]; then
+ LOCAL_HEAD="$(git -C "$REPO_ROOT" rev-parse HEAD 2>/dev/null || true)"
+ if [ -n "$LOCAL_HEAD" ] && [ "$LOCAL_HEAD" != "$HEAD_SHA" ]; then
+ echo "NOTE: $REPO_ROOT is no longer parked on the reviewed commit (now
$LOCAL_HEAD)." >&2
+ echo " That is fine if you switched back after the review; the
comment still refers to $HEAD_SHA." >&2
+ fi
+fi
+
+# Three ways to name the posting account: the REST /user endpoint has been
seen answering 503
+# while the rest of the API is healthy, and on an error gh still prints the
error JSON on stdout,
+# so each candidate is validated as a real login before it is accepted.
+try_login() {
+ local out
+ out="$("$@" 2>/dev/null || true)"
+ out="$(printf '%s' "$out" | tr -d '[:space:]')"
+ [[ "$out" =~ ^[A-Za-z0-9][A-Za-z0-9-]{0,38}$ ]] || return 1
+ printf '%s' "$out"
+}
+auth_status_login() {
+ gh auth status 2>&1 | sed -n 's/.*Logged in to github.com account \([^
]*\).*/\1/p' | head -n 1
+}
+REVIEWER="$(try_login gh api user --jq .login || true)"
+[ -n "$REVIEWER" ] || REVIEWER="$(try_login gh api graphql -f
"query={viewer{login}}" --jq .data.viewer.login || true)"
+[ -n "$REVIEWER" ] || REVIEWER="$(try_login auth_status_login || true)"
+[ -n "$REVIEWER" ] || { echo "ERROR: cannot determine which GitHub account gh
is logged in as." >&2; exit 1; }
+REVIEWED_AT="$(python3 -c "import datetime;
print(datetime.datetime.now().astimezone().isoformat(timespec='minutes'))")"
+
+# ------------------------------------------------------------------ render
body
+FENCE='```'
+DISCLAIMER='<sub>Reviewed locally with the `doris-repo-review` pipeline (a
local port of `.github/workflows/code-review-runner.yml`). This is not a CI
status check.</sub>'
+BODY_FILE="$CTX/pr-comment.md"
+cat > "$BODY_FILE" <<EOF
+${BEGIN_MARKER}
+### Local pipeline review — ✅ PASS
+
+${FENCE}yaml
+schema: ${SCHEMA}
+status: PASS
+pr: ${UPSTREAM_REPO}#${PR_NUMBER}
+commit: ${HEAD_SHA}
+base: ${BASE_SHA}
+reviewed_at: ${REVIEWED_AT}
+reviewer: ${REVIEWER}
+model: ${MODEL}
+effort: ${EFFORT}
+findings: {blocker: ${F_BLOCKER}, major: ${F_MAJOR}, minor: ${F_MINOR}, nit:
${F_NIT}}
+rounds: ${ROUNDS}
+converged: ${CONVERGED}
+${FENCE}
+
+**Notes for maintainers**
+
+${NOTES_BODY}
+
+${DISCLAIMER}
+${END_MARKER}
+EOF
+
+# ------------------------------------------- find an earlier comment of this
tool
+# Own comments only - another account's comment cannot be edited anyway. A
comment for THIS
+# commit wins wherever it sits in the thread; otherwise the newest one is kept
just to report
+# which commit the previous run signed off on.
+SAME_ID=""
+LAST_ID=""
+LAST_COMMIT=""
+while IFS=$'\t' read -r cid clogin ccommit; do
+ [ -n "$cid" ] || continue
+ [ "$clogin" = "$REVIEWER" ] || continue
+ ccommit="${ccommit#commit: }"
+ LAST_ID="$cid"
+ LAST_COMMIT="$ccommit"
+ [ "$ccommit" = "$HEAD_SHA" ] && SAME_ID="$cid"
+done < <(gh api "repos/${UPSTREAM_REPO}/issues/${PR_NUMBER}/comments"
--paginate \
+ --jq '.[] | select(.body | test("doris-repo-review:v1:begin")) | [
.id, .user.login, ([.body | scan("commit: [0-9a-f]{40}")] | .[0] // "") ] |
@tsv' 2>/dev/null || true)
+
+ACTION="create"
+EXISTING_ID=""
+if [ -n "$SAME_ID" ] && [ "$FORCE_NEW" = "0" ]; then
+ ACTION="update"
+ EXISTING_ID="$SAME_ID"
+fi
+
+echo "=== doris-repo-review PASS comment ==="
+echo "pr : ${PR_URL}"
+echo "commit : ${HEAD_SHA}"
+echo "reviewer : ${REVIEWER}"
+echo "model : ${MODEL} (effort ${EFFORT})"
+echo "findings : blocker=${F_BLOCKER} major=${F_MAJOR} minor=${F_MINOR}
nit=${F_NIT}"
+echo "notes : ${NOTE_COUNT}"
+if [ "$ACTION" = "update" ]; then
+ echo "action : UPDATE the existing comment ${EXISTING_ID} (same commit)"
+elif [ -n "$LAST_ID" ]; then
+ echo "action : CREATE a new comment (the previous one, ${LAST_ID}, was
for ${LAST_COMMIT:-an unknown commit})"
+else
+ echo "action : CREATE a new comment"
+fi
+echo "body file : ${BODY_FILE}"
+echo
"--------------------------------------------------------------------------"
+cat "$BODY_FILE"
+echo
"--------------------------------------------------------------------------"
+
+if [ "$DRY_RUN" = "1" ]; then
+ echo "dry run - nothing was posted."
+ exit 0
+fi
+
+if [ "$ACTION" = "update" ]; then
+ RESP="$(jq -n --rawfile body "$BODY_FILE" '{body: $body}' \
+ | gh api --method PATCH
"repos/${UPSTREAM_REPO}/issues/comments/${EXISTING_ID}" --input -)"
+else
+ RESP="$(jq -n --rawfile body "$BODY_FILE" '{body: $body}' \
+ | gh api --method POST
"repos/${UPSTREAM_REPO}/issues/${PR_NUMBER}/comments" --input -)"
+fi
+COMMENT_URL="$(printf '%s' "$RESP" | jq -r '.html_url')"
+printf '%s\n' "$COMMENT_URL" > "$CTX/pr-comment.url"
+echo "posted : ${COMMENT_URL}"
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]