Add per-review + per-comment token accounting, surfaced only when a PR carries
the new AI-USAGE label (on top of the existing AI-REVIEW trigger).
opencode_review:
- run_opencode now uses `--format json`; parse_opencode_events reconstructs the
assistant text from `text` events and sums tokens/cost/steps from every
`step_finish` event (tolerant of noise / missing fields).
- run() measures duration_s around the opencode call and returns (text, usage).
- changed_files(diff) extracts the `+++ b/` paths; the brief now lists them
under a "Changed files" focus block so the agent grounds findings in the
diff's neighbourhood instead of unbounded whole-repo walks.
ai_review:
- format_usage_section renders a `## AI usage` block: measured totals
(in/out/reasoning/cache/cost/steps/duration), the whole-repo scope note, and
an attributed per-finding table. Per-comment counts are output tokens split by
each finding's body weight — labelled "attributed" since one model pass
produces all findings.
- inline_comment_body appends `🪙 ~N tok (X% · attributed output)` when
attribution is present.
- review_pr gains report_usage; compute_attribution stashes _tok_attrib/_tok_pct.
- format_review_body inserts the usage section between summary and findings.
webhook_server:
- Fire on every pull_request action except `closed` (denylist, was an allowlist)
— the AI-REVIEW gate + sha dedupe keep this safe.
- AI-USAGE label detection + PRAGENT_USAGE_ALWAYS env drive report_usage.
.opencode factory + review-methodology skill: new "Ground findings in context"
step — read callers/imports/sibling functions per changed file (1-3 files per
finding), no unbounded walks.
Tests: parse_opencode_events (text+usage sum, malformed tolerance, none-usage),
changed_files, compute_attribution math, inline 🪙 line, format_usage_section
totals/table/cost, format_review_body ordering. 68 passing.
Co-Authored-By: Claude <noreply@anthropic.com>
- pilot/webhook_server.py: stdlib HTTP receiver. HMAC-verifies X-Gitea-Signature,
gates on pull_request action + AI-REVIEW label, runs review_pr in a background
thread (responds 202 immediately so Gitea's delivery timeout never fires).
Accepts both GitHub-style (labeled/synchronize) and Gitea event-type-style
(label_updated/synchronized) action names.
- pilot/ai_review.py: extract review_pr() core so both the CI run() and the
webhook server share one review path. run() is now an env-driven wrapper.
- pilot/README-webhook.md: architecture, onboarding, one-time per-owner
user-webhook setup, the Gitea 1.26.1 system-webhook bug, the SSRF
ALLOWED_HOST_LIST change, K8s deploy + script-update recipe.
- README.md + design doc: note the webhook service as the preferred delivery
path (partially reverses 'central webhook = non-goal', pilot only).
Gitea 1.26.1 system webhooks broken (POST /admin/hooks -> 201 but never
persists); user-level webhooks (one per repo-owner) are the working fallback.
Gitea SSRF allow-list blocks in-cluster webhook targets by default; required a
scoped [webhook] ALLOWED_HOST_LIST addition + gitea restart.
E2E verified 2026-08-17: pragent-bot reviewed gitea_admin/pragent PR #2 and
masi/portfolio PR #3 via the webhook service (glm-5.2:cloud).
Co-Authored-By: Claude <noreply@anthropic.com>