Files
pragent/.opencode/skills/findings-schema/SKILL.md
T
Marcos 6e3a9eb5b0 feat: opencode review engine + .opencode factory
Replace the single Python model-call reviewer with an opencode agent
factory. A primary 'pragent' agent reads a brief (title/body/diff/config/
prior reviews), inspects the checked-out repo, runs the repo's own linters
via bash, loads review-methodology + findings-schema skills, and emits a
{summary, findings} JSON with per-finding severity/path/line/problem/fix/
suggestion/reference. Dormant security/tests/perf subagent lenses fan out
only on large/risky diffs (lean by default).

pilot/opencode_review.py: fetches the repo archive at the head sha into a
temp workdir, writes .pragent/brief.md, drops the factory, runs
'opencode run --pure --agent pragent --dir <workdir>' headlessly. Isolates
HOME (shared, warmed), strips ANTHROPIC_* env (leaked host vars caused
ProviderModelNotFoundError), stdin=DEVNULL (opencode blocks on stdin),
maps the bare OLLAMA_MODEL to the provider-prefixed ref. No Gitea I/O —
ai_review.review_pr parses + anchors + posts (reuses all v2 logic/tests).

PRAGENT_ENGINE=opencode (default) selects it; =ollama keeps the legacy
direct-call path. Verified end-to-end: posts a real review with a summary
section, inline [CRITICAL]/[HIGH] comments + apply-able suggestions +
reference links, and the sha dedupe marker. 49 tests pass.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-17 23:01:59 +00:00

2.3 KiB

name, description
name description
findings-schema The exact JSON output shape pragent must emit at the end of a review. Load this before producing findings.

pragent findings schema

The review's FINAL message is a short prose summary followed by ONE fenced json code block. The Python shell parses the LAST json fenced block in the message — so the JSON must be the last thing, and it must be valid.

Shape

{
  "summary": "One-paragraph overview of the change and its risk, plus severity counts.",
  "findings": [
    {
      "severity": "critical|high|medium|low",
      "path": "path exactly as it appears in the diff's `+++ b/` side",
      "line": 12,
      "problem": "one line: what is wrong",
      "fix": "one line: how to fix it",
      "suggestion": "exact replacement lines, indented as in the file, or \"\"",
      "reference": "https://... or \"\""
    }
  ]
}

Field rules

  • severity — one of critical, high, medium, low. Anything else is coerced to medium by the parser.
  • path — the post-change path, exactly as in the diff (+++ b/foo/bar.tsfoo/bar.ts). Required; a finding without a real path is dropped.
  • line — a post-change line number (int ≥ 1) that exists in path after the PR. Required; bad/missing line → the finding becomes a summary bullet instead of an inline comment.
  • problem — one line, concrete: what is wrong and why it matters.
  • fix — one line, the remedy. Empty string if the fix is architectural.
  • suggestion — the literal new code replacing the flagged line(s). Minimal, just the changed lines, indented as they appear in the file. Empty string when no safe textual replacement exists (missing test, architectural note, a fix that needs context beyond one hunk). This is wrapped in a ```suggestion fence → Gitea renders an apply button.
  • reference — a URL (CVE, library docs, spec) backing the finding, or "". Only link authoritative sources; don't fabricate URLs.

Clean diff

If there's nothing to report: {"summary":"<what it does, why it's fine>","findings":[]}.

Don't

  • No prose after the closing ``` of the JSON block.
  • No extra keys — unknown keys are ignored by the parser, so don't rely on them.
  • Don't repeat findings from prior_reviews.