agentleFS
aarontzengaarontzeng/dev-lead/skills/opencode-adversarial-review/SKILL.md

opencode-adversarial-review

Skill3 starsChanged 7 days agoMITView on GitHub

Run an adversarial (red-team) review through the OpenCode CLI's free model pool (muse-spark, Nemotron, stealth models) under a machine-enforced read-only permission config. Use when a HIGH-risk change needs a second cross-family reviewer without spending paid quota, or as the cheap extra pair of eyes beside a primary review.

The file

We split this skill into the rules it gives an agent

9 rules. About 0 are common, 2 are distinctive, and 7 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 7

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    When any of this trips, the fourth leg runs on opencode-go/glm-5.3 and the report says why.

    line 93
  2. Very specific:

    Call this what it is: a machine-enforced guardrail against a fallible delegate, not a security boundary against an adversarial one.

    line 166
  3. Very specific:

    NOT on the list — git branch -d writes refs, and rev-parse --abbrev-ref HEAD answers the read-only question) — stronger than a behavioral …

    line 169
  4. Very specific:

    Verify what actually governs before spending a run: opencode debug agent prints the merged rule array in evaluation order (runtime file).

    line 179
  5. Very specific:

    A declaration only works when the config is untracked: If the target's own repo tracks opencode.json, the mv-and-write above produces M …

    line 239
  6. Distinctive:

    NOT REACHED, one plain verdict line -- and your brief says what this round is about.

    line 272
  7. Very specific:

    Before this recipe, complete the frozen-target triage step in dev-lead Phase 2.

    line 278
  8. Very specific:

    Check before editing anything, and in this order — the error greps first, because every one of them names a cause that a model switch would …

    line 374
  9. Distinctive:

    Only once all four come back clean — a run that errored nowhere and simply stopped — is the fifth shape in play.

    line 381

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Adversarial review via opencode (free pool)

The value here is an additional model family at zero quota cost: muse-spark (Meta) and Nemotron are neither GPT, Gemini, nor Claude, so this is the cheapest way to satisfy the cross-family rule's second reviewer on HIGH-risk work. The trade-off is best-effort capacity — congestion is normal, so this leg is the additional opinion or the unhurried gate, never the time-critical one.

Before the first run of a session

Read references/opencode-runtime.md (same directory). It holds the family mechanics — the free-model catalogue and the stealth-model family-unknown caveat, the two silent permission traps (zero-commit project binding, last-match-wins ordering), congestion behavior, and the audit log lines. This file assumes them and covers only the review role.

Choosing the model for this leg — by LENS, not a fixed favourite

The owner ruled this on 2026-09-16 and it now lives in their roster (roster.json, located per /dev-lead:config; it was in their CLAUDE.md before the roster existed); what follows is the same rule with the numbers behind it, and this file is where those numbers belong. The division is now explicit in both directions — the roster names which leg, dev-lead holds prices, buckets and calibration rows. (The earlier attempt to write a single model in here as "the default" was mine, on a peer's recommendation, and was retracted in 0.6.8; the rule below is the owner's, not a plugin preference.)

lensmodelwhy
mechanical — consistency, is-it-still-true, convention, falsifiabilityopencode-go/muse-spark-1.3-contributor; opencode/muse-spark-1.3-contributor-free only when the go plan is not availableowner, 2026-10-04: on the go plan this model has no rate limit and draws no quota, while the free twin is a rate-limited pool. Measured strong on fact-checking and line accuracy. (Before that statement this file priced the Go twin at ~$0.014 a leg; take the owner's word for your own plan, and measure if yours differs.)
judgement — challenge, design rulings, sequencesopencode-go/glm-5.3an independent family, ~$0.20 a leg against the large ($12 / 5 h) bucket. No calibration row yet — treat as unproven until one exists
top-tier judgement onlyopencode-go/kimi-k3, opencode-go/qwen3.8-max1–2 legs per 5-hour window at most, and the buckets are shared across sessions, so probe immediately before dispatch

Effort follows the lens, never the model. There is no per-variant price; reasoning tokens bill as output. So xhigh on a cheap model is nearly free and xhigh on a dear one can double the leg.

The terms question, and how it was settled. Both muse-spark-*-contributor variants, free and paid, put the prompt into a training set, and a review leg is handed the frozen tree: source, design documents, sometimes a spec. That is a disclosure decision, not a cost one, so it went to the owner rather than being settled here. Ruled 2026-09-16: accepted for review legs — with the exception carried per change. If a change carries material that must not reach a training set, say so and run the fourth leg on opencode-go/glm-5.3 instead. The judgement stays with whoever knows what is in the diff; the default no longer has to be guessed.

And the owner delegated the judgement itself to this layer (2026-09-16): dev-lead decides what may go to a contributor tier. So it needs criteria that can be CHECKED, not a feeling. Three rules:

1. Decide per REPOSITORY, once, and write it down — not per change. A repo's disclosure class is stable; a change's is not knowable at a glance, and a judgement re-made every round is one that decays exactly like a STOP rule that keeps firing on benign causes. Record the class beside the repo's other dispatch facts. Re-open it only when the repo gains a new kind of content, not when a change looks unusual.

2. The stop classes. A repo is NOT eligible for a contributor tier if its tracked tree carries any of: credential material of any kind; a customer's or third party's confidential material; personal data beyond work identifiers; or specifications for an unannounced product. Everything else — this account's own infrastructure code, its ADRs, its tooling — is eligible.

3. Screen the frozen target before the first dispatch, not from memory. The tree is what the leg receives, so look at it:

git ls-files | wc -l; git ls-files -z | xargs -0 cat | wc -c   # what is actually handed over
grep -rlEi 'BEGIN (RSA|OPENSSH|EC) PRIVATE KEY|api[_-]?key *= *["'"'"'][A-Za-z0-9]{16}' .
grep -rhoE '[A-Za-z][A-Za-z.]+@[a-z]+\.com' . | sort -u      # people, not roles

Measured on one of the owner's infrastructure repositories 2026-09-16: no credential material; 153 files and 6.7 MB handed to each leg; colleague work addresses and internal project codenames present. That is the residual the owner accepted — named, not assumed absent. Eligible.

The brief is a prompt too. The claims file and lens text are authored by the lead and go to the model with the tree, so they can disclose material the repo does not contain — an audit excerpt with real actor ids, a quoted mail, a customer's words from a ticket. Screen what you WRITE by the same rules; this is the half no check on the repository can see.

When any of this trips, the fourth leg runs on opencode-go/glm-5.3 and the report says why. A leg not run is always available too: three legs and a stated reason beat four legs and a disclosure nobody decided.

Establish an immutable review target

One frozen directory per reviewer, at the exact commit, that nothing else touches — no lead activity inside it. Not "whenever possible": a reviewer reads the WORKING TREE, not your commit. Measured — a round ran mutation testing in the same worktree mid-review and the reviewer opened a CRITICAL on a mutated, non-compiling file it was never meant to see.

Freeze it with the suite's tested helpers instead of hand-rolling the shell; every bug ever found in this step was in a hand-rolled copy. The suite-root resolver and both calls — freeze-target.sh to create it, verify-target.sh before AND after the run — are in dev-lead Phase 2, and the reasoning is in methodology.md §7. Everything below assumes $REVIEW_TARGET_DIR is that frozen directory and $REVIEW_HEAD is the SHA it was frozen at.

The read-only boundary is a config file, not a mode

If the target already has an opencode.json, save it first — writing ours over a project's real config and then deleting "ours" at teardown destroys the project's file:

RUN_DIR=$(mktemp -d "${TMPDIR:-/tmp}/opencode-review.XXXXXX")   # created here; every later step reuses it
[ -f "$REVIEW_TARGET_DIR/opencode.json" ] && \
  mv "$REVIEW_TARGET_DIR/opencode.json" "$RUN_DIR/opencode.json.orig"
# ... run ...; at teardown, restore:
#   mv "$RUN_DIR/opencode.json.orig" "$REVIEW_TARGET_DIR/opencode.json"

(The frozen worktree above does NOT make this step optional: it is a checkout of the same repo, so a project that tracks its own opencode.json has one there too. The backup is what stops teardown deleting it.)

Then write this as opencode.json in the target (wildcard FIRST — last match wins; see runtime). freeze-target.sh … --opencode-lockdown writes exactly this file, read-only, at freeze time (and refuses a commit that tracks its own opencode.json); declare it to verify-target.sh as below:

{
  "$schema": "https://opencode.ai/config.json",
  "permission": {
    "bash": {
      "*": "deny",
      "git status*": "allow",
      "git log*": "allow",
      "git diff*": "allow",
      "git show*": "allow",
      "git rev-parse*": "allow"
    },
    "edit": "deny",
    "external_directory": "deny"
  }
}

external_directory: deny is what keeps a wrong path from ending the leg. Left at its default (ask), a read outside the target is auto-rejected headless and the leg stops with no verdict, however good the review was; with an explicit deny the model gets an error message back and keeps reviewing. A peer session measured it on 4 changes x 5 Google models: 3 of 20 legs died after ~20 s on a mis-typed path (the frozen path with its last components dropped), and the failing case ran 206 s to a verdict once the line was added. opencode debug agent build in the target lists the merged rule last, and opencode re-appends its own tool-output allow after it, so truncated tool output stays readable.

Call this what it is: a machine-enforced guardrail against a fallible delegate, not a security boundary against an adversarial one. File edits denied, shell denied except five git reads (git branch* is deliberately NOT on the list — git branch -d writes refs, and rev-parse --abbrev-ref HEAD answers the read-only question) — stronger than a behavioral plan mode, and stronger than opencode's own plan agent (whose edit-deny a shared config can override and whose bash is unrestricted — measured; see runtime). But command wildcards match command prefixes, not effects: git diff --output=<path> and git log --output=<path> write files while matching the allowed patterns. The threat model here is an honest model taking a lazy shortcut — which the denials catch — and the post-run bracket below is what catches everything else; a hostile-input review needs an OS-level read-only sandbox instead. Verify what actually governs before spending a run: opencode debug agent prints the merged rule array in evaluation order (runtime file). Use the default agent with this config. A denied call comes back to the model as a message, so an over-curious reviewer reports MACHINE-DENIED and keeps reviewing instead of dying.

The emitted leg-cmd.sh command begins with check-opencode-lockdown.py on the target and stops the run when opencode.json is not exactly the shape below: only $schema and permission, edit denied, bash and external_directory deny-by-default with at most the five read-only git rules (bash) or literal absolute paths (external_directory), no opencode.jsonc or .opencode in the target, no OPENCODE_CONFIG* / OPENCODE_DISABLE* in the environment, and the target is the root of a git checkout with a commit (otherwise opencode ignores the file). A managed config under /etc/opencode is not read (known limit). Without the file opencode runs as the build agent with edit and bash open (a peer session ran three legs that way before this check existed). leg-cmd.sh --help prints which of --target, --base, --prompt-file and --add-dir each adapter takes.

The config file itself appears as untracked in the target, and this leg cannot move it out — it must sit at the project root to bind. Declare it to the certification instead of excusing it:

set -o pipefail          # without it the digest below survives a MISSING file:
                         # sha256sum reports the bad operand on stderr, the
                         # second sha256sum still exits 0 and hashes whatever
                         # arrived, and the same omission after the run
                         # reproduces the same digest — certifying a run that
                         # never had the claims file. Measured.
for f in opencode.json REVIEW-CLAIMS.md; do
  [ -f "$REVIEW_TARGET_DIR/$f" ] || { echo "scaffold missing: $f"; exit 1; }
done

# Immutable FOR THE DURATION, not merely equal at the endpoints — the third of
# the four properties in the materializing-evidence reference, which this flow
# violated by relying on the digest alone. A before/after digest cannot see the
# edit → read → restore sequence, and this
# leg's own allow-list documents the write: `git diff --output=<path>`. Measured
# that a-w blocks it at the source: "fatal: could not open 'REVIEW-CLAIMS.md'
# for writing: Permission denied".
chmod a-w "$REVIEW_TARGET_DIR"/opencode.json "$REVIEW_TARGET_DIR"/REVIEW-CLAIMS.md

SCAFFOLD_SHA=$(sha256sum "$REVIEW_TARGET_DIR"/{opencode.json,REVIEW-CLAIMS.md} | sha256sum)

a-w on the files stops a rewrite through the allow-listed git … --output=. Unlinking and recreating them would need write on the directory rather than on the files, and no allow-listed command can do it: bash is deny except five git reads, and none of those five unlinks a file. The two layers are what make the digest meaningful — keep both, and chmod u+w before restoring.

Then pass those two paths as trailing arguments to the verify-target.sh bracket the section above already requires — … "$REVIEW_HEAD" opencode.json REVIEW-CLAIMS.md — and compare SCAFFOLD_SHA after the run.

Declaring a path permits that entry and no other, and fails if it has vanished — but it permits an entry, not its content: ?? opencode.json reads the same after a rewrite, and this leg's allow-list reaches git diff --output=<path> (see above). So keep the SCAFFOLD_SHA comparison in the lead as well; the two answer different questions. Remove the claims file and restore any original config after the run.

A declaration only works when the config is untracked. If the target's own repo tracks opencode.json, the mv-and-write above produces M opencode.json, and verify-target.sh rejects a declared tracked path by design — a modified tracked file is a mutation of the reviewed tree, never scaffolding, and loosening that would reopen exactly the hole the rule closes. Bracket outside the scaffold window instead, which is strictly stronger than a declaration would have been:

steptracked opencode.jsonuntracked
after freeze, before writing the configverify-target.sh "$REVIEW_TARGET_DIR" "$REVIEW_HEAD" — no declarations, must be fully cleansame
write scaffold, record SCAFFOLD_SHA, run the leg——
after the runcompare SCAFFOLD_SHA — meaningful only because the files were a-w for the whole window; a digest alone samples two endpoints and cannot see edit → read → restoresame
then restorechmod u+w both, then mv "$RUN_DIR/opencode.json.orig" back and rm REVIEW-CLAIMS.mdchmod u+w both, then rm opencode.json REVIEW-CLAIMS.md
after restoringverify-target.sh "$REVIEW_TARGET_DIR" "$REVIEW_HEAD" — no declarations, must be fully cleansame

The closing bracket proving clean is what proves the restore was byte-exact: a mv-back that produced anything other than the committed content would still show M opencode.json. Order matters — compare SCAFFOLD_SHA before restoring, or the restore erases the evidence of a rewrite and cleanup's success gets reported as the run's.

Run it

Capture REVIEW_HEAD when you freeze the target, not immediately before the launch assertion — captured at launch time the check compares HEAD against itself and can only pass (see the same note in claude-adversarial-review).

Since 0.6.30 the brief is a LENS inside this leg's framing (references/adversarial-framing.md): the frame carries the general discipline -- read the base revision, label inherited defects instead of dropping them, a per-claim HOLDS / BROKEN / NOT REACHED, one plain verdict line -- and your brief says what this round is about. Adopted on measurement: on three frozen targets with known answers, three runs per arm, the plain brief found 4 of the known defects and this frame 7; the plain brief answered broken claims "HOLDS" (runtime file, calibration table).

Before this recipe, complete the frozen-target triage step in dev-lead Phase 2; the review launcher requires its record for this exact commit.

REVIEW_HEAD=$(git -C "$REVIEW_TARGET_DIR" rev-parse HEAD)   # at freeze time

# ... later, at launch (RUN_DIR was created in the boundary step above):
# Write the lens to "$RUN_DIR/prompt.md" in its own FOREGROUND step.
DEV_LEAD=${DEV_LEAD_ROOT:-$(ls -d "$HOME"/.claude/plugins/cache/dev-lead/dev-lead/* 2>/dev/null | sort -V | tail -1)}
[ "$(git -C "$REVIEW_TARGET_DIR" rev-parse HEAD)" = "$REVIEW_HEAD" ] && \
  eval "$("$DEV_LEAD/scripts/leg-cmd.sh" opencode review --model opencode/<free-model> --effort <effort> \
            --base "$BASE" --target "$REVIEW_TARGET_DIR" --run-dir "$RUN_DIR")" > "$RUN_DIR/review.log" 2>&1

What it runs, spelled out (for a lead without the script): the lock-down check, the builder, then the leg inside the frozen target, in a subshell so your own cwd is kept:

{ python3 "$DEV_LEAD/scripts/check-opencode-lockdown.py" "$REVIEW_TARGET_DIR" && \
  python3 "$DEV_LEAD/scripts/review-prompt.py" --adapter opencode --base "$BASE" \
    --target "$REVIEW_TARGET_DIR" < "$RUN_DIR/prompt.md" > "$RUN_DIR/framed-prompt.md" && \
  ( cd "$REVIEW_TARGET_DIR" && opencode run --print-logs --log-level info \
      -m 'opencode/<free-model>#<effort>' \
      < "$RUN_DIR/framed-prompt.md" ); } > "$RUN_DIR/review.log" 2>&1

$RUN_DIR must be absolute (leg-cmd exports it that way) and outside the target; the lock-down opencode.json must already be in the target.

  • The #<effort> suffix on -m is the effort knob (opencode 2; v1 had --variant, which v2 rejects) — this family's --effort. Pass it explicitly; omitting it takes the provider default, and there is no reasoningEffort key in opencode.json to set instead (runtime file). The free pool costs no quota, so this is the one leg where you can spend the top tier without a budget argument.
  • "In its own step" means its own FOREGROUND step. Measured: a prompt file written by a heredoc inside a backgrounded compound command raced the read in a second background command — opencode received 168 bytes of a 5 KB prompt and, sensibly, asked what the session was for. It looks exactly like a model ignoring instructions. Compare the size of what you piped against the size of what you wrote before believing anything about the run.
  • The prompt goes on STDIN, not in argv. An argv prompt over ~1–2 KB hangs before the session is even created — message=init then silence, forever, in every model (measured; size sweep in the runtime file).
  • Put the claims IN a file inside the repo, not outside it. Anything outside the project trips permission=external_directory. Left at ask it is auto-rejected headless and the model treats the refusal as fatal (measured: died in seven seconds having read nothing; the same run later hallucinated a neighboring absolute path and died identically). The config above denies it instead, so the model gets an error and goes on; the claims still belong inside the repo, because a read it cannot make is a finding it cannot check. State the repo root explicitly in the prompt ("your cwd is EXACTLY <path>; use relative paths").
  • Check the log mechanically before reading it as a verdict:

    DEV_LEAD=${DEV_LEAD_ROOT:-$(ls -d "$HOME"/.claude/plugins/cache/dev-lead/dev-lead/* 2>/dev/null | sort -V | tail -1)}
    "$DEV_LEAD/scripts/leg-log-check.sh" "opencode" "$RUN_DIR/review.log"

    It passes only a log whose report carries a per-claim verdict word (HOLDS / BROKEN / NOT REACHED / FIXED, or --expect <regex> for another format), so an empty log, an error dump and a refused read with nothing after it all exit 1. A leg it fails is a MISSING leg, never "no findings" -- measured 2026-09-22 (a peer session): two refused out-of-cwd reads and one 20-minute timeout each left a file that looked like a clean run. It catches "nothing"; the diagnosis below is still yours.

  • Five terminal failures look alike in the log and are not. Ending with auto-rejecting is the permission wall — YOUR bug; fix the prompt and rerun. Streaming response failed: [502]/[503] … ResourceExhausted or queue is full is the provider out of capacity. A clean exit with tokens.output=0 USUALLY means nothing was produced — but not always; see the retraction below. Error: unknown certificate verification error after a > build · <model> line is TLS failing before any token — transport; plain rerun. And a fifth shape that carries no error line at all: a clean exit that read every file, ran its greps, and then simply stopped. Observed 2026-08-19, opencode 1.18.18: two consecutive deepseek-v4-flash-free legs on the same prompt each worked through the evidence pack and exited 0 without writing a word, while nemotron-3-ultra-free on that identical prompt returned a full table minutes later — and a second session on this machine was concurrently running deepseek-v4-flash-free.

    That model id no longer resolves (measured 2026-09-16: zero hits in opencode models; this account's free pool is big-pickle, ling-3.0-flash-fin-free, mimo-v2.5-free, muse-spark-1.2/1.3-contributor-free, nemotron-3-ultra-free, nemotron-3.5-lightning-free). The incident above is kept because the FAILURE SHAPE is what matters and it recurs on other models; the id is history, not a dispatch target. Why it left the catalogue is unknown — do not infer it from the workspace switches, which provably do not touch the listing (runtime file).

    Check before editing anything, and in this order — the error greps first, because every one of them names a cause that a model switch would only reproduce: grep -c auto-rejecting, grep -E '50[0-9]\]', grep -c 'certificate verification', grep -o 'tokens.output=[0-9]*' | tail -1.

    Only once all four come back clean — a run that errored nowhere and simply stopped — is the fifth shape in play. Per-model contention is a hypothesis there, not an established cause: one uncontrolled observation cannot separate it from provider state, a model-specific fault, or account quota, and pgrep cannot prove the other process holds the same model. What the episode does establish is two cheap steps that cost nothing and settle it either way:

    pgrep -af "opencode run"    # another local session? which model?

    Mind the self-match — a guard whose own command line contains the pattern always finds itself, so read the PIDs rather than counting them. Then rerun the same prompt on a different free model — as a recovery attempt, not as a diagnostic. Two runs separated in time confound the model with everything else that moves between them: provider state, quota, routing, and the model's own nondeterminism. So a second model returning a report buys you a report; it does not establish that the first failure was model-specific, and a second model failing too does not establish provider-wide congestion. If you actually need the cause — usually you do not, you need a review — the cheap next step is to re-run the original model once the switch succeeds: a failure that reproduces on A after B worked is worth believing, one that does not was transient and is not worth more of your afternoon.

    Getting this order wrong has a cost beyond the wasted rerun: a permission wall gets rediagnosed as a dead provider pool, or the empty run gets written up as a model-behaviour finding — and that claim then outlives the session in whatever notes the lead keeps.

    Retraction worth keeping: tokens.output=0 is a broken accounting metric, not a verdict. Measured: two runs with that fingerprint were genuinely empty; a third with the identical fingerprint contained a complete review with quoted evidence, verified 4/4 against the real files' line counts and last lines.

    The header grep is a ONE-WAY signal, and reading it as two-way cost a round. A hit (grep -c '^## ', or your prompt's required section headers) does mean the review is real regardless of the token counter. A zero does NOT mean the run was empty. Measured: a leg returned a full report — a citation table, four findings, two of them that round's only catches — written entirely in bold labels and tables with no markdown headings at all, so the header count was 0. On that count alone it was called a silent-empty run, reported as such to the user, and its findings were nearly dropped. Nothing in the prompt had required ##.

    So the discard decision needs more than one signal. In cost order:

    wc -c "$RUN_DIR/review.log"                              # a real report is rarely tiny
    grep -o 'step=[0-9]*' "$RUN_DIR/review.log" | tail -1    # loop depth: did it work at all?
    grep -cE '^#{1,4} |^\*\*|^\| |NOT REACHED|HOLDS|BROKEN' "$RUN_DIR/review.log"
    grep -v '^timestamp=' "$RUN_DIR/review.log" | tail -200  # then just read the tail

    The last line is what actually settles it. A 150 KB log whose final screen is git status output is empty; one whose final screen is prose carrying file:line citations is not — and no counter substitutes for looking. Reading 200 lines is cheaper than re-running the leg, and much cheaper than telling the user a leg produced nothing when it did.

  • The tell that it is the provider and not your prompt: did this SHAPE work recently? Measured: three consecutive failures on a six-property prompt made the shape look like the cause — but five reviews earlier the same day used exactly that shape and all returned full reports; the pool had simply degraded that afternoon. A prompt that worked in the morning did not become malformed by evening.
  • Congestion is provider-wide; switching MODELS inside a bad window does not help — the free pool is one queue. Wait, or route to a different FAMILY.
  • Hand the delegate a merge-base range, never origin/main..HEAD — this leg is the most exposed of all, because the prompt tells the model which git command to read the diff with and you never see the output. Pin it:

    BASE=$(git merge-base origin/main HEAD)
    git diff --stat "$BASE" HEAD        # file list must match the change under review
  • Launch in the background with a generous timeout (10m+; budget 40m under congestion — a measured healthy run went silent 10+ minutes mid-generation and then delivered a full report at ~31 minutes). Treat a missing evaluated permission= log line as "never really ran".
  • Running this leg as a subagent? You are a leaf — block, do not "wait". Nothing will wake you when opencode run exits; ending your turn on "waiting for the monitor" abandons the review. Poll to exit inside a single tool call, and issue another immediately if it times out (dev-lead Phase 2). This leg's long silent stretches make the urge to end the turn strongest here — and its documented silent-death mode means a leaf that leaves early cannot tell a dead run from a slow one.
  • Model: the named families are the accounting-valid reviewers. A stealth model (family undisclosed by design) is an ADDITIONAL pair of eyes, never the leg that satisfies the cross-family rule — and never review a stealth model's work with itself. Between the named models, choose by measured hit rate and by structural properties (a huge-context model earns its place when the review must hold a whole subsystem at once); tier and parameter count did NOT predict finding yield in measurement.
  • First run in a fresh worktree: confirm the log's projectID= is a hash, not global — global means the read-only config silently isn't loaded and the boundary does not exist (runtime, trap 1).

Bracket the run regardless (belt and braces — the config is the boundary, this is the proof):

git -C "$REVIEW_TARGET_DIR" rev-parse HEAD        # before, and again after —
git -C "$REVIEW_TARGET_DIR" status --porcelain=v1 # -C so it's the TARGET, not your cwd

Writing the prompt

The prompt you write is the LENS; the framing (above) already opens with the falsify-don't-confirm stance, asks for the base revision, the per-claim answers and the verdict line. Put into the lens what only this round knows: the claims, the surface you worry about, what the tests do not enumerate. Same red-team discipline as the other review skills — it matters more than the model. The short list, with this family's specifics:

  • First-party pre-merge framing; falsify, don't confirm; numbered claimed properties; a declared approximation gets a BOUNDED property; trigger + observable consequence + severity + file:line per finding; state fixes already made; forbid praise and generic summaries.
  • Ask what the tests do not enumerate — the highest-yield sentence in the prompt on every family (see docs/methodology.md).
  • State the read-only intent in the prompt as well as the config, and forbid recursive delegation.
  • Tell it NOT to run tests or builds — the config machine-blocks them, but saying so converts a wasted denied attempt into the useful artifact: "if confirming a finding requires execution, name the exact command and expected result; we run it."
  • Tell it not to call MCP tools if the target repo wires any — the diff and prompt are the complete context.
  • This family's measured strength is DOCUMENT AND SPEC CONSISTENCY; it is the LENIENT one on attack paths. In paired rounds it alone caught a design silently contradicting two accepted documents, a key reused across protocols with no domain separator, and a test whose name claimed more than its body proved — while passing attack-path defects another family caught. Ask this leg "does this contradict document Y", "is this claim supported by its cited evidence"; do not rely on it alone to decide whether an attack works. (Measure your own split — this is one journal's data.)
  • Size the evidence gate to what YOU cannot check (documents get the outsider-questions gate, not the re-derivation gate — see methodology).
  • Give every claim an evidence gate with an unguessable anchor: per file, line count AND verbatim last line; per claim, file:line plus the quoted code; NOT REACHED acceptable, HOLDS-without-quote not.

A long review eats its own prompt — the ask lives in a FILE

Measured: a review that read nine files came back having lost the numbered claim list to its own context compaction — the honest model stopped and said so; a less careful one reconstructs the claims and answers a question you never asked. Reordering the prompt does not help (compaction drops the original user message as a unit). The defense:

  • Put the claim list in a file inside the review directory (e.g. REVIEW-CLAIMS.md) and tell the model to re-read it whenever it needs the wording. A file survives compaction because it can be read again; a prompt cannot. Say the file plus opencode.json are the expected untracked entries, or your own tree-dirty check will fire on your scaffolding.
  • Give a reading budget for big files ("read only lines 2150–2560 of the test file") — context is the resource being exhausted.
  • Authorize a partial answer explicitly: "if you run low on context, stop and emit the verdicts you can support with quotes, mark the rest NOT REACHED."

Reporting back

Same contract as every review leg: relay findings faithfully; verify each against the code before acting (and record the measured hit rate in your calibration journal — it is this family's calibration data); hold rejections to the same evidence standard; review-only, no fixes unless asked.

Pairing rule across the suite: the reviewer must come from a different model family than whatever implemented the change. This adapter serves several DISCLOSED families and the accounting reads the served model's family, not the adapter — a stealth model whose family is undisclosed can never be the leg that satisfies the rule, however good its findings are.

The standing headline from measurement: model tier did not predict finding yield — a free leg found the only HIGH on a diff where two paid legs returned nothing usable. Do not drop this leg on the grounds that a stronger reviewer is already running.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.