# 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 ```mermaid flowchart TD WH["webhook_server.py
HMAC + AI-REVIEW gate + dedupe"] --> RP["ai_review.review_pr"] RP --> ARCH["fetch repo archive @ head sha
→ /tmp/pragent-work/-"] ARCH --> BRIEF["write .pragent/brief.md
(title, body, diff, config, prior, sha)"] BRIEF --> DROP["drop opencode.json + .opencode/ into workdir"] DROP --> OC["opencode run --pure --agent pragent --dir
--model headroom/glm-5.2:cloud"] OC --> PR["pragent primary
load skills · run linters · read code · delegate lenses"] PR --> JSON["final message: summary + ```json findings```"] JSON --> PARSE["ai_review.parse_review_output
{summary, findings}"] PARSE --> ANCHOR["parse_diff_anchors → split_findings"] ANCHOR --> POST["post_inline_review
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) Multi-lens orchestration is now Python-side (`pilot/opencode_review.py`). Each lens is just a `.md` file; the Python side spawns one subprocess per lens in parallel and synthesises the merged findings. 1. Create `.opencode/agents/.md` with frontmatter: ```yaml --- description: One line that names what this lens catches. mode: subagent hidden: true model: headroom/glm-5.2:cloud temperature: 0.1 permission: edit: deny write: deny bash: "allow" # or narrow: "": "allow" webfetch: allow task: deny --- ``` Body = the system prompt. End it with the **strict JSON contract** in `.opencode/skills/lens-orchestration/SKILL.md` (findings shape, `severity_floor`, no writes outside workdir, prompt-injection reporting). A real lens has target paths + output schema + tool budget + example findings — not just a different system prompt. 2. Add one entry to the repo's `.pr-review.json:reviewers[]`: ```jsonc { "id": "", "severity_floor": "low", "max_findings": 8 } ``` No Python change. No image rebuild. The orchestrator picks it up next run. 3. (Optional) Tighter defaults: `activation: "off"` to ship-disabled, `skip_if_all_changed_paths: "docs/**"` to skip when only docs changed, `hotpath_globs: ["**/queries/**"]` to help triage recognise the lens. **Built-in lenses** (you can override any of them): `security`, `docs`, `code-quality`, `tests`, `perf`. Set `reviewers: []` to opt out. ### Add a skill 1. `mkdir .opencode/skills/ && touch .opencode/skills//SKILL.md` 2. Frontmatter: `name: ` (kebab-case, matches dir), `description:` (specific enough for the agent to pick it). Body = the knowledge. 3. Refer to it from `pragent.md` ("Call the `skill` tool for ``"). 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://:8789/v1`, Anthropic `/v1/messages` format, `apiKey: ollama`) → `glm-5.2:cloud`. To use a different model, add a provider and reference it as `/`. ## Local one-shot review (no webhook) ```bash 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.