f59b906395
Audited the working tree and all 26 commits of history for credentials: none
found. No API keys, no private keys, no tokens — the live bot token, webhook
secret and admin token appear nowhere in the repo or its history.
What was there was infrastructure disclosure, which is recon material rather
than a leak, but has no business in a public repo:
- Tailnet addresses and cluster-internal hostnames in code, docs and the CI
template. The model endpoint is now supplied at runtime via
PRAGENT_MODEL_BASE_URL and patched into opencode.json by install_config();
the committed config carries a placeholder, guarded by a test.
- A host path (/home/marcos) as the default rtk directory — now unset.
- Real usernames in the onboarding docs — now alice/acme.
- A standing list of one-time setup tokens that were never revoked, named
individually. Removed. Note that removing the list does not revoke the
tokens: they should still be revoked in the Gitea admin UI.
The substitution happens in Python rather than via opencode's {env:VAR} config
templating, because the reviewer subprocess runs with an allow-listed
environment — resolving it before the process starts keeps that allow-list from
having to grow.
README rewritten for a reader who has never seen the project: what it does and
what that output looks like, honest status (pilot works, framework designed but
unbuilt), the security model up front given what this thing is, and the measured
cost numbers including the two effects that make naive estimates wrong.
History still contains the old addresses. They are tailnet-only and not
credentials, so no rewrite.
Tests: 131 -> 137.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B11e8TZZxJyzHW7jj7KWUN
100 lines
3.8 KiB
Markdown
100 lines
3.8 KiB
Markdown
# pragent pilot — AI Review bot
|
||
|
||
A minimal AI code-review bot for Gitea, running as a CI step on the existing
|
||
`act-runner`. This is the **pilot** — a small, self-contained reviewer that
|
||
predates the full `pragent` framework (whose design lives in
|
||
`docs/plans/2026-08-04-pragent-design.md`). The framework will later absorb
|
||
this; until then, this is what runs.
|
||
|
||
## How it works
|
||
|
||
1. You add `pragent-bot` to a repo and commit `.gitea/workflows/ai-review.yml`.
|
||
2. On a PR, you add the **`AI-REVIEW`** label.
|
||
3. Gitea Actions runs the workflow on the `act-runner`; it fetches the PR diff,
|
||
asks `glm-5.2:cloud` (on-network via the headroom proxy) to review it, and
|
||
posts the findings back as a PR review authored by `pragent-bot`.
|
||
4. Remove the label to stop re-reviews on further pushes.
|
||
|
||
Fail-open: the job always exits 0 and never blocks CI. Errors become a short
|
||
"review failed" comment.
|
||
|
||
## Onboard a repo (3 steps)
|
||
|
||
### 1. Add `pragent-bot` as collaborator
|
||
|
||
Repo → Settings → Collaborators → Add → `pragent-bot` → permission **Write**.
|
||
(Write is required to post reviews/comments.)
|
||
|
||
Or via API (with an admin/owner token):
|
||
|
||
```bash
|
||
curl -X PUT -H "Authorization: token $OWNER_TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"permission":"write"}' \
|
||
"http://<gitea-host>:3000/api/v1/repos/OWNER/REPO/collaborators/pragent-bot"
|
||
```
|
||
|
||
### 2. Add the `PRAGENT_BOT_TOKEN` secret
|
||
|
||
Repo → Settings → Actions → Secrets → New secret → name `PRAGENT_BOT_TOKEN`,
|
||
value = the bot's access token (ask the platform admin; stored mode-600 at
|
||
`~/.claude/.pragent-bot-token` on the admin host).
|
||
|
||
### 3. Commit the workflow
|
||
|
||
Copy `pilot/workflow-template.yml` into the target repo as
|
||
`.gitea/workflows/ai-review.yml` and commit it. That's it.
|
||
|
||
## Use it
|
||
|
||
Open a PR (or push to an open one), add the **`AI-REVIEW`** label. The review
|
||
appears within ~30–90s depending on diff size and model latency.
|
||
|
||
## What's intentionally NOT in the pilot
|
||
|
||
Deferred to the full framework (by design, see the design doc):
|
||
|
||
- Attention tiering (trivial/lite/full/oversized) and per-tier cost control.
|
||
- Multiple analyzer fan-out over a shared cached prompt prefix.
|
||
- Prior-comment synthesis (so each push re-posts; the latest review is tagged
|
||
with the head SHA so it's easy to spot).
|
||
- Inline line comments and status checks.
|
||
- `pragent explain` / `replay` / analytics JSONL.
|
||
- A second forge (GitLab) and the provider matrix.
|
||
|
||
## Pieces
|
||
|
||
| File | Role |
|
||
|---|---|
|
||
| `pilot/ai_review.py` | The reviewer script (stdlib only). Single source of truth — fetched at runtime by each repo's workflow. |
|
||
| `pilot/workflow-template.yml` | The Gitea Action consumers copy into `.gitea/workflows/ai-review.yml`. |
|
||
| `tests/pilot/test_ai_review.py` | Unit tests for the pure helpers (no network). |
|
||
|
||
## Run the tests
|
||
|
||
```bash
|
||
cd ~/Projects/pragent
|
||
PYTHONPATH=pilot python3 -m pytest tests/pilot/ # if pytest available
|
||
# or, without pytest:
|
||
python3 - <<'PY'
|
||
import os, sys, importlib.util
|
||
sys.path.insert(0, os.path.abspath("pilot"))
|
||
import ai_review # noqa: F401
|
||
spec = importlib.util.spec_from_file_location("t", "tests/pilot/test_ai_review.py")
|
||
m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m)
|
||
fails = 0
|
||
for n in sorted(x for x in dir(m) if x.startswith("test_")):
|
||
try: getattr(m, n)(); print("PASS", n)
|
||
except Exception as e: fails += 1; print("FAIL", n, e)
|
||
print("failed:", fails)
|
||
PY
|
||
```
|
||
|
||
## Configuration knobs (env in the workflow)
|
||
|
||
| Env | Default | Purpose |
|
||
|---|---|---|
|
||
| `OLLAMA_MODEL` | `glm-5.2:cloud` | Model id passed to the headroom proxy. |
|
||
| `OLLAMA_MAX_TOKENS` | `6000` | Output token cap. |
|
||
| `DIFF_MAX_CHARS` | `150000` | Diff truncation cap (with a noted truncation marker). |
|
||
| `OLLAMA_URL` | `http://<model-proxy-host>:8789` | headroom proxy (tailnet). If the act-runner can't reach the tailnet IP, expose 8789 as an in-cluster Service+Endpoints and set this to the cluster DNS name. | |