feat(review): ADDITIONAL_CONTEXT_URL — repo-provided static context cached per review
Long agent loops re-send the brief prefix on every step; cheap reusable knowledge (architecture summary, module map, conventions, glossary) belongs in a versioned file the maintainers control so the agent doesn't re-derive it from the source tree on every PR. Two wiring paths, merged (env first): * env var PRAGENT_ADDITIONAL_CONTEXT_URL — comma-separated, deployment-wide * .pr-review.json:additional_context_urls — list[str], read from the PR's base branch (same trust boundary as the rest of the file) Implementation: * _parse_additional_context_env splits/dedupes/trims. * _resolve_additional_context_urls(config) merges env (first) + config (then, skipping env-dupes); caps at 8. * fetch_additional_context(urls) fetches each URL with urllib (5s timeout, http/https only — file://, javascript:, ftp:// rejected defensively), caches by URL in a module-level dict for the pod lifetime, truncates per-URL to 4k chars + total to 16k chars, best-effort (network errors are logged and skipped — never aborts the review). * Result injected into build_user_prompt under "## Repo-provided context" between repo config and prior reviews. In the opencode engine it lands in .pragent/brief.md under its own section. The brief explicitly labels each block's CONTENT as untrusted (same as PR description) — section heading is trustworthy, body isn't. * parse_repo_config accepts the field, caps at 8 entries, drops non-strings and empty strings. Docs: pilot/README-webhook.md "Repo-provided static context" section — env var + JSON example + Nexus raw-hosted recipe. Tests: 14 new (208 total), covering env merging + dedup, scheme rejection, per-URL cap, total cap, caching by URL, brief injection. All mock urllib with a context-manager stand-in (no real network). Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+60
-1
@@ -95,6 +95,63 @@ Without `AI-USAGE` (regression): no usage section, no 🪙 lines — behaviour
|
||||
identical to before the feature. The usage section is part of the review body,
|
||||
so it's covered by the existing sha-marker dedupe.
|
||||
|
||||
## Repo-provided static context (`ADDITIONAL_CONTEXT_URL`)
|
||||
|
||||
Long agent loops resend the brief prefix on every step; the cheap reusable
|
||||
knowledge — architecture summary, module map, conventions, glossary, past
|
||||
incident write-ups — lives in a versioned file the maintainers control, so
|
||||
the agent doesn't have to re-read the source tree to rediscover it on every
|
||||
PR. Two ways to wire it up:
|
||||
|
||||
**Env var** (Deployment-wide, useful for shared house docs):
|
||||
|
||||
```bash
|
||||
PRAGENT_ADDITIONAL_CONTEXT_URL="https://nexus.example/raw/architecture.md,https://nexus.example/raw/glossary.md"
|
||||
# comma-separated, trimmed, deduped; ≤ 8 URLs total
|
||||
```
|
||||
|
||||
**Per-repo `.pr-review.json`** (read from the PR's base branch — same trust
|
||||
boundary as the rest of `.pr-review.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"additional_context_urls": [
|
||||
"https://nexus.example/repository/raw-hosted/architecture.md",
|
||||
"https://nexus.example/repository/raw-hosted/conventions.md"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The two are merged: env first (in declared order), then config entries that
|
||||
aren't already in env. The first 8 win.
|
||||
|
||||
**Behaviour**:
|
||||
|
||||
- Fetched **once per review**, cached by URL for the lifetime of the pod.
|
||||
- **http/https only** — `file://`, `javascript:`, `ftp://`, anything else is
|
||||
silently dropped.
|
||||
- 5 s timeout per URL.
|
||||
- Per-URL truncated to **4 000 chars**, total to **16 000 chars**, then
|
||||
`…[truncated]` is appended and the next URL is skipped.
|
||||
- Best-effort: a network error or non-200 is logged to stderr and skipped —
|
||||
never aborts the review.
|
||||
- Rendered into the brief under **"Repo-provided context"**, between the
|
||||
repo config and prior reviews. The brief explicitly labels the *content*
|
||||
of each block as untrusted author-controlled data (same as the PR
|
||||
description), so the agent knows to ground findings against it but not
|
||||
take instructions from it.
|
||||
|
||||
**Self-hosted example (Nexus `raw-hosted`)**:
|
||||
|
||||
```bash
|
||||
# Upload a doc to Nexus raw-hosted (anonymous read for in-cluster pods).
|
||||
curl -u techspark -X PUT \
|
||||
--data-binary @architecture.md \
|
||||
https://nexus.example/repository/raw-hosted/architecture.md
|
||||
# Then reference it from .pr-review.json (above). Cache control is
|
||||
# browser-style: anonymous read = max-age from response headers.
|
||||
```
|
||||
|
||||
## Webhook fires on any PR update (except `closed`)
|
||||
|
||||
The receiver uses a **denylist**, not an allowlist: it reviews on every
|
||||
@@ -364,7 +421,9 @@ Env on the Deployment: `PRAGENT_ENGINE`, `OPENCODE_MODEL`,
|
||||
`OPENCODE_EXPERIMENTAL_LSP_TOOL`, `PRAGENT_FACTORY_DIR`, `PRAGENT_OPENCODE_BIN`,
|
||||
`PRAGENT_WORK_ROOT`, `PRAGENT_REVIEW_TIMEOUT`, `GITEA_API`, `OLLAMA_URL`,
|
||||
`OLLAMA_MODEL`, `OLLAMA_MAX_TOKENS`, `DIFF_MAX_CHARS`,
|
||||
`PRAGENT_MAX_CONCURRENT_REVIEWS`, `PRAGENT_MAX_BODY_BYTES` are literals;
|
||||
`PRAGENT_ADDITIONAL_CONTEXT_URL` (optional, see "Repo-provided static
|
||||
context" above), `PRAGENT_MAX_CONCURRENT_REVIEWS`, `PRAGENT_MAX_BODY_BYTES`
|
||||
are literals;
|
||||
`WEBHOOK_SECRET` + `PRAGENT_BOT_TOKEN` come from the Secret. The image now runs
|
||||
as uid 10001 — add `securityContext: {runAsNonRoot: true, runAsUser: 10001,
|
||||
fsGroup: 10001}` to the pod spec so the `/tmp/pragent-work` emptyDir is writable.
|
||||
|
||||
Reference in New Issue
Block a user