docs: onboarding uses .pr-review.json:enabled, not labels

This commit is contained in:
claude
2026-08-22 01:33:42 +00:00
parent 2432228d68
commit 3e03fb80a7
2 changed files with 58 additions and 51 deletions
+35 -33
View File
@@ -1,29 +1,31 @@
# pragent pilot — central webhook service
The CI-step pilot (`pilot/README.md`) needs a workflow file + secret + label per
repo. The **central webhook service** removes the workflow file, the secret, and
the runner dependency: a Gitea webhook posts PR events to an always-on in-cluster
service, which gates on the `AI-REVIEW` label and runs the same review core.
The CI-step pilot (`pilot/README.md`) needs a workflow file + secret per repo.
The **central webhook service** removes the workflow file, the secret, and the
runner dependency: a Gitea webhook posts PR events to an always-on in-cluster
service, which gates on `.pr-review.json:enabled = true` and runs the same review
core.
## Architecture
```
PR opened/pushed/labeled/edited/… (any repo under a covered owner)
PR opened/pushed/edited/… (any repo under a covered owner)
│ Gitea user-level webhook (events: pull_request)
Service pragent-webhook.pragent.svc.cluster.local (ClusterIP, ns pragent)
│ body-size cap → HMAC-verify (X-Gitea-Signature)
│ → gate: action ≠ closed AND pull_request.labels ∋ AI-REVIEW
│ → gate: action ≠ closed AND .pr-review.json:enabled = true on base
│ → claim (repo, index, sha) in-flight (closes the dedupe race)
│ → bounded worker (PRAGENT_MAX_CONCURRENT_REVIEWS, default 2)
ai_review.review_pr() (same core the CI-step uses)
1. fetch existing reviews → dedupe: skip if a review already carries
<!-- pragent:sha=<this sha> --> (no duplicate on label-toggle / re-fire)
2. fetch PR diff → GET .../pulls/{i}.diff
3. fetch .pr-review.json @ head ref (optional repo-local focus/config)
4. prior review bodies → fed as "already said" context (light §6.1)
5. PRAGENT_ENGINE=opencode (default):
1. opt-in .pr-review.json:enabled = true on base? if not, skip.
2. fetch existing reviews → dedupe: skip if a review already carries
<!-- pragent:sha=<this sha> --> (no duplicate on title/body-edit re-fire)
3. fetch PR diff → GET .../pulls/{i}.diff
4. fetch .pr-review.json @ base ref (the opt-in flag + repo-local focus/config)
5. prior review bodies → fed as "already said" context (light §6.1)
6. PRAGENT_ENGINE=opencode (default):
a. fetch repo archive @ head sha → /tmp/pragent-work/<repo>-<sha>
(symlink-escape + traversal rejected on untar)
a2. sanitize the workdir: delete author-controlled agent-instruction
@@ -39,8 +41,8 @@ ai_review.review_pr() (same core the CI-step uses)
diffs, and emits: {"summary":..., "findings":[{severity,path,line,
problem,fix,suggestion,reference}]}
(=ollama: legacy single POST to http://<model-proxy-host>:8789/v1/messages)
6. parse diff hunks → valid (path, new_line) anchors (RIGHT side)
7. post review → POST .../pulls/{i}/reviews (event: COMMENT) as pragent-bot
7. parse diff hunks → valid (path, new_line) anchors (RIGHT side)
8. post review → POST .../pulls/{i}/reviews (event: COMMENT) as pragent-bot
- prose summary → review body intro
- anchored findings → inline line comments, body wraps `suggestion` in a
language-tagged fenced code block (Gitea syntax-highlights it; Gitea
@@ -58,13 +60,13 @@ of repeating (light version of framework §6.1).
1. Add `pragent-bot` as collaborator with **Write** (so it can read the diff and
post the review). The bot stays a normal user — it is **not** a site admin.
2. Create the `AI-REVIEW` label on the repo (one-time; `pragent-bot`'s
`write:issue` scope can do it once it's a collaborator).
3. Label a PR `AI-REVIEW`.
2. Commit `.pr-review.json: {"enabled": true}` to the repo's default branch
(so every PR on the repo is auto-reviewed).
3. Open a PR.
No workflow file, no repo secret, no act-runner needed. (The owner must already
be covered by a user-level webhook — see below. If not, do the one-time
per-owner setup first.)
No workflow file, no repo secret, no act-runner, no label needed. (The owner
must already be covered by a user-level webhook — see below. If not, do the
one-time per-owner setup first.)
## Token-usage reporting (always on)
@@ -151,17 +153,17 @@ curl -u techspark -X PUT \
The receiver uses a **denylist**, not an allowlist: it reviews on every
`pull_request` action **except `closed`**`opened`, `reopened`,
`synchronize`/`synchronized`, `labeled`/`label_updated`, `edited` (title/body),
`ready_for_review` (draft→ready), `assigned`, `review_requested`, `milestone`,
… . This is safe because of two downstream gates:
`synchronize`/`synchronized`, `edited` (title/body), `ready_for_review`
(draft→ready), `assigned`, `review_requested`, `milestone`, … . This is safe
because of two downstream gates:
- the **AI-REVIEW label gate** — payload `labels` reflect current state, so an
`unlabeled` that *removed* AI-REVIEW fails the gate (no review); an
`unlabeled` of another label still passes;
- the **sha dedupe** — any same-sha re-fire (title edit, assignee, milestone,
a label toggle of another label…) is skipped, so the only newly-effective
actions are ones that change the head sha (`synchronize`, already covered) or
move a draft to ready (`ready_for_review`) on an un-reviewed sha.
- the **opt-in gate**`.pr-review.json:enabled = true` is read from the base
branch, so only repos that opted in get reviewed. A repo that deletes the
file between pushes opts out;
- the **sha dedupe** — any same-sha re-fire (title edit, assignee, milestone…)
is skipped, so the only newly-effective actions are ones that change the head
sha (`synchronize`, already covered) or move a draft to ready
(`ready_for_review`) on an un-reviewed sha.
## Threat model
@@ -204,8 +206,8 @@ Additionally: the repo archive is untarred with symlink-escape and
parent-traversal rejection (`_extract_tar_strip_one`), the container runs as
uid 10001, and the webhook caps request bodies (`PRAGENT_MAX_BODY_BYTES`,
default 10 MiB) and concurrent reviews (`PRAGENT_MAX_CONCURRENT_REVIEWS`,
default 2 — each review forks an opencode process, so unbounded threads were a
self-inflicted fork bomb on a label-ten-PRs burst).
default 2 — each review forks an opencode process, so unbounded threads would be
a self-inflicted fork bomb on any burst of concurrent PRs).
**Residual risk, accepted for a pilot:** the agent still *executes* hostile repo
content indirectly (running the repo's own linters on it) inside a container
@@ -222,7 +224,7 @@ so the `/tmp/pragent-work` emptyDir is writable.
## Multi-lens pipeline (5 default lenses, on by default)
Default `AI-REVIEW` runs spawn **one opencode subprocess per lens in parallel**
Default reviews spawn **one opencode subprocess per lens in parallel**
and synthesize the merged findings before posting. Cheaper than 5 sequential
reviews because the headroom proxy caches the byte-identical brief across
lens calls (lenses 2..N hit cache).