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
pragent .opencode/ — the review factory
pragent's review runs on opencode (the AI-coding-agent CLI). This directory is
a portable factory: the opencode.json + .opencode/ are dropped into a
checked-out copy of the target repo at the PR head sha, then opencode run is
launched there. The pragent primary agent reviews the diff with real tools
(subagents, LSP/linters via bash, webfetch references) and emits a structured
findings JSON. A thin Python shell posts that JSON back to Gitea as inline
comments + language-highlighted suggested-fix blocks + a summary (dedupe + anchor validation stay
deterministic in Python).
Layout
opencode.json provider (headroom → glm-5.2:cloud), model, lsp, permission, default_agent
.opencode/
agents/
pragent.md PRIMARY reviewer — reads .pragent/brief.md, runs linters, emits findings JSON
security.md subagent — injection/auth/secrets/supply-chain lens (dormant)
tests.md subagent — missing/weak test coverage lens (dormant)
perf.md subagent — N+1 / O(n²) / hot-path lens (dormant)
skills/
review-methodology/SKILL.md severity rubric, what to report, anchoring, trust boundary
findings-schema/SKILL.md the exact output JSON shape
attention-tiering/SKILL.md trivial/lite/full/oversized + the budget each tier gets
linter-playbook/SKILL.md per-ecosystem check commands + turning diagnostics into findings
security-lens/SKILL.md inline security checklist + the source→sink test
malicious-change/SKILL.md hostile-PR detection: injection at the reviewer, install hooks, backdoors
comment-craft/SKILL.md how to write problem/fix/suggestion so a maintainer can act
commands/
review.md /review slash command (local interactive use)
README.md this file
How a review runs
flowchart TD
WH["webhook_server.py<br/>HMAC + AI-REVIEW gate + dedupe"] --> RP["ai_review.review_pr"]
RP --> ARCH["fetch repo archive @ head sha<br/>→ /tmp/pragent-work/<repo>-<sha>"]
ARCH --> BRIEF["write .pragent/brief.md<br/>(title, body, diff, config, prior, sha)"]
BRIEF --> DROP["drop opencode.json + .opencode/ into workdir"]
DROP --> OC["opencode run --pure --agent pragent --dir <workdir><br/>--model headroom/glm-5.2:cloud"]
OC --> PR["pragent primary<br/>load skills · run linters · read code · delegate lenses"]
PR --> JSON["final message: summary + ```json findings```"]
JSON --> PARSE["ai_review.parse_review_output<br/>{summary, findings}"]
PARSE --> ANCHOR["parse_diff_anchors → split_findings"]
ANCHOR --> POST["post_inline_review<br/>summary + inline lang-tagged fix block + ref links + sha marker"]
Lean by default
attention-tiering is the cost governor: it classifies every PR as trivial /
lite / full / oversized before any file is read, and each tier caps file
reads, linter runs, and subagent fan-out. The pragent primary does the whole
review in one pass for small/medium diffs (no subagent calls) and delegates to
@security / @tests / @perf ONLY at full/oversized when the lens has
real surface. Skills are loaded conditionally for the same reason — each one is
input tokens. Subagent recursion is capped by the primary's steps budget.
pilot/cost_model.py turns those tier assumptions into a per-PR and per-month
cost figure for any provider — run it after changing the factory to see what the
change costs.
--pure is passed at runtime so the reviewer doesn't load the host user's heavy
global opencode plugins (supermemory/dcp/morph/pty) which hang cold-start. In the
deploy pod there's no global config, so --pure is a no-op there — but it keeps
host-local runs deterministic.
Extending the factory
Add a review lens (subagent)
- Create
.opencode/agents/<name>.mdwithmode: subagent,hidden: true, adescription, and a read-onlypermission(deny edit/write, allow bash/webfetch,task: denyso it can't recurse). The body is its system prompt; end it by requiring the same findings-JSON shape. - Allow it in the primary's
permission.tasklist inpragent.md:task: "*": "deny" "security": "allow" "tests": "allow" "<name>": "allow" # add this - Mention in
pragent.md's "Delegate on heavy diffs" step when to invoke it.
That's it — the primary can now @<name> it via the Task tool. It stays dormant
(the primary decides when), so adding it costs nothing for small PRs.
Add a skill
mkdir .opencode/skills/<name> && touch .opencode/skills/<name>/SKILL.md- Frontmatter:
name: <name>(kebab-case, matches dir),description:(specific enough for the agent to pick it). Body = the knowledge. - Refer to it from
pragent.md("Call theskilltool for<name>").
Per-language expertise is free: the host user already has 29 global skills
(golang-, react-, k8s, terraform, testing, typescript, …). opencode
auto-discovers them via the skill tool — the pragent primary loads a matching
one when the repo's language fits. To ship a pragent-specific one, just drop it
here.
Change the output shape
Edit .opencode/skills/findings-schema/SKILL.md (the schema doc) AND the Python
parser in pilot/ai_review.py (parse_findings) + the renderers
(inline_comment_body, summary_bullets, format_review_body). Keep them in
sync — the parser is tolerant but the agent and parser must agree on field names.
Switch model / provider
Edit opencode.json provider + model. The provider points at the on-network
headroom proxy (http://<model-proxy-host>:8789/v1, Anthropic /v1/messages format,
apiKey: ollama) → glm-5.2:cloud. To use a different model, add a provider and
reference it as <provider>/<model-id>.
Local one-shot review (no webhook)
cd ~/Projects/pragent
opencode run --pure --agent pragent --dir . \
--model headroom/glm-5.2:cloud \
"Read .pragent/brief.md if present, else review \`git diff HEAD\`, and output findings."
Or in the TUI: /review (uses .opencode/commands/review.md).
Engine flag
PRAGENT_ENGINE=opencode (default once wired) uses this factory. =ollama
falls back to the legacy direct model call in pilot/ai_review.py. The two
share all Gitea I/O, dedupe, and posting logic.