opencode-reviewer 0.8.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +146 -53
  2. package/dist/tui.js +2155 -975
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,13 +1,19 @@
1
1
  # OpenCode Reviewer
2
2
 
3
3
  Reviews pending permissions in the OpenCode sidebar using a separate LLM. Covers
4
- shell commands, file edits, MCP calls, custom tools, and external-directory access.
4
+ shell commands, file edits, skill loading, MCP calls, custom tools, and external-directory access.
5
5
  Supports streaming explanations, saved report history, auto-approval, and Linux
6
6
  desktop notifications with distinct sounds.
7
7
 
8
+ This plugin is meant to enable you to run sessions with less frequent input in a safer way. Also giving the reviewer context of specific files the agent might want to run/see/... The review prompts are freely configurable, so you can determine the reviewers behavior very accurately.
9
+
10
+ > ⚠️ **Disclaimer**
11
+ >
12
+ > This does not make running agents foolproof and safe! An actor running intrusive commands on your machine will always create attack surface! It just reduces risk, and enables you to get more insight into what your agent is doing.
13
+
8
14
  ![](./assets/recording.gif)
9
15
 
10
- Requires OpenCode 1.18.35 or newer on Linux.
16
+ Verified with OpenCode 1.18.35 on Linux.
11
17
 
12
18
  ## Install and config
13
19
 
@@ -29,10 +35,12 @@ OpenCode installs the [npm package](https://www.npmjs.com/package/opencode-revie
29
35
  "stream": false,
30
36
  "reviewBash": true,
31
37
  "reviewEdits": true,
38
+ "reviewSkills": true,
32
39
  "reviewMcp": false,
33
40
  "reviewCustomTools": false,
34
41
  "reviewExternalDirectories": false,
35
42
  "autoApprove": false,
43
+ "fastMode": false,
36
44
  "extraCareful": true,
37
45
  "autoApproveDelaySeconds": 15,
38
46
  "notify": true,
@@ -42,13 +50,13 @@ OpenCode installs the [npm package](https://www.npmjs.com/package/opencode-revie
42
50
  "attention": { "banner": true, "sound": true },
43
51
  "unsafe": { "banner": true, "sound": true },
44
52
  "question": { "banner": true, "sound": true },
45
- "approved": { "banner": true, "sound": true },
53
+ "approved": { "banner": true, "sound": false },
46
54
  "error": { "banner": true, "sound": true },
47
55
  "ended": { "banner": true, "sound": true }
48
56
  },
49
57
  "notificationSoundDirectory": "/absolute/path/to/notification-sounds",
50
58
  "formatRetries": 1,
51
- "maxOutputTokens": 2048,
59
+ "maxOutputTokens": 4096,
52
60
  "timeoutMs": 30000,
53
61
  "maxFiles": 6,
54
62
  "maxEvidenceBytes": 131072
@@ -67,34 +75,53 @@ launching OpenCode. The inline key takes precedence. Omit both for an unauthenti
67
75
  endpoint. Remove `instructions` to use the built-in prompts and
68
76
  `notificationSoundDirectory` to use the bundled sounds.
69
77
 
70
- The remaining values above are the defaults. Restart OpenCode after changing
71
- configuration. Reviews run when OpenCode asks for permission, so set the relevant
72
- rules to `ask` in `opencode.json`. Existing `allow` rules skip review.
78
+ The connection, credential and directory values above are examples, not defaults.
79
+ All boolean, numeric and per-type notification values shown are the defaults.
80
+ Restart OpenCode after changing configuration. Reviews run when OpenCode asks for
81
+ permission, so set the relevant rules to `ask` in `opencode.json`. Existing `allow`
82
+ rules skip review.
73
83
 
74
84
  ## Options reference
75
85
 
76
- | Option | Behavior |
77
- | --- | --- |
78
- | `stream` | Show the rating and explanation as they arrive. Requires SSE support from the endpoint. |
79
- | `reviewBash` | Review native shell commands. |
80
- | `reviewEdits` | Review native edit, write, and apply-patch requests. |
81
- | `reviewMcp` | Review identifiable MCP tool and resource permissions. |
82
- | `reviewCustomTools` | Review permissions requested by registered custom tools. |
83
- | `reviewExternalDirectories` | Review directory access independently of the other switches. Directory approval can resume the operation without another prompt. |
84
- | `autoApprove` | Allow completed Safe reviews once after their countdown, including behind report history. |
85
- | `extraCareful` | Include the extra-careful prompt in auto-mode reviews. Defaults to `true`; set `false` to omit it. |
86
- | `autoApproveDelaySeconds` | Countdown duration, 0–3600 seconds. |
87
- | `notify` | Linux desktop notifications and sounds. Defaults to `true`; set `false` to disable both. |
88
- | `notifySound` | Play notification sounds. Defaults to `true`; set `false` to keep banners silent. |
89
- | `staleReminderSeconds` | Repeat pending human-interaction notifications every 60 seconds by default. Nonnegative safe integer seconds; `0` disables reminders. Only the front-of-queue blocker per conversation repeats. |
90
- | `notifications` | Per-type `banner` and `sound` booleans for `attention`, `unsafe`, `question`, `approved`, `error`, and `ended`. Omitted types and fields default to `true`. |
91
- | `notificationSoundDirectory` | Absolute custom sound directory. Use any of the six notification type names as basenames with `.wav` or `.mp3`; WAV takes precedence. Missing or unusable files fall back to that type's bundled sound. |
92
- | `formatRetries` | Additional attempts to correct malformed assessment JSON, 0–100. |
93
- | `maxOutputTokens` | Provider output-token limit, sent as `max_tokens`. Defaults to 2,048; accepts positive safe integers within your provider/model's supported range. Retries keep the same limit. |
94
- | `timeoutMs` | Total review deadline, 1–3,600,000 ms. |
95
- | `maxFiles` | File limit per review, 1–1,000. |
96
- | `maxEvidenceBytes` | Evidence limit, 1–16,777,216 bytes. Whole files or diffs may be omitted; oversized mandatory arguments fail review. |
97
- | `instructions` | Absolute directory containing overrides for the [prompt templates](https://github.com/mightykatun/opencode-reviewer/tree/main/prompts). Missing templates use the built-ins. |
86
+ These are options inside the plugin's configuration object in `tui.json`.
87
+ Defaults apply only to omitted options; `null` is invalid. Boolean and integer
88
+ options require JSON booleans and numbers, not quoted strings. Integer ranges are
89
+ inclusive, fractions are invalid, and the maximum safe integer is
90
+ 9,007,199,254,740,991. Unknown option names are rejected.
91
+
92
+ | Option | Type | Default | Accepted values and behavior |
93
+ | --- | --- | --- | --- |
94
+ | `baseURL` | string | Required | HTTP(S) API base URL; `/chat/completions` is appended after removing trailing slashes. Embedded credentials, query strings and fragments are rejected. |
95
+ | `model` | string | Required | Nonempty model identifier accepted by the configured endpoint. |
96
+ | `apiKey` | string | Unset | Nonempty Bearer token. Takes precedence over `apiKeyEnv`. |
97
+ | `apiKeyEnv` | string | Unset | Environment-variable name matching `[A-Za-z_][A-Za-z_0-9]*`. Without an inline key, its value must be set and nonblank when a review runs. Omit both key options for no Authorization header. |
98
+ | `instructions` | string | Unset (built-ins) | Absolute, NUL-free directory path containing overrides for the [prompt templates](https://github.com/mightykatun/opencode-reviewer/tree/main/prompts), not inline instructions. Missing named files use built-ins; supplied files must be readable, regular, non-symlink, nonempty UTF-8 text up to 64 KiB. Fixed contracts cannot be overridden. |
99
+ | `stream` | boolean | `false` | Request SSE and show provisional rating/text updates. Normal automatic approval waits for a completed, validated and rendered report; fast mode can approve on an early Safe rating. |
100
+ | `reviewBash` | boolean | `true` | Review native shell-command permissions. |
101
+ | `reviewEdits` | boolean | `true` | Review native `edit`, `write` and `apply_patch` permissions. |
102
+ | `reviewSkills` | boolean | `true` | Review native skill-load permissions for dangerous guidance and relevance to the current task. Supplies the host's skill instructions and bounded, directly referenced supporting files within the skill directory. |
103
+ | `reviewMcp` | boolean | `false` | Review identifiable MCP tool and resource permissions. |
104
+ | `reviewCustomTools` | boolean | `false` | Review permissions requested by registered custom tools. |
105
+ | `reviewExternalDirectories` | boolean | `false` | Review directory access independently of operation-review switches. Directory approval can resume the operation without another prompt. |
106
+ | `autoApprove` | boolean | `false` | Allow Safe reviews once automatically. Normally waits for completion, validation, rendering and the countdown, including behind this plugin's report history. |
107
+ | `fastMode` | boolean | `false` | Requires `autoApprove: true`. Approve a visible, front-of-queue request as soon as a Safe rating is parsed, bypassing the countdown. Respects `stream`; with `stream: false`, waits for the complete validated response. Finish the report and save it in the background after approval. |
108
+ | `extraCareful` | boolean | `true` | Include extra-careful guidance when `autoApprove` is enabled. Setting `false` omits that guidance without disabling automatic approval. |
109
+ | `autoApproveDelaySeconds` | integer | `15` | `0`–`3600` seconds. Positive values include an extra 200 ms initial grace; `0` skips the wait but retains rendering and eligibility checks. Ignored in fast mode. |
110
+ | `notify` | boolean | `true` | Master switch for desktop banners, sounds and reminders. `false` overrides all per-type controls. |
111
+ | `notifySound` | boolean | `true` | Master switch for notification audio. `false` silences every type and reminder without disabling enabled banners. |
112
+ | `staleReminderSeconds` | integer | `60` | `0` through the maximum safe integer, in seconds. `0` disables reminders; otherwise eligible pending human interactions repeat at this interval, limited to the native front-of-queue blocker per conversation. |
113
+ | `notifications` | object | All banners enabled; all sounds enabled except `approved` | Keys: `attention`, `unsafe`, `question`, `approved`, `error`, `ended`. Each entry is an object with optional `banner` and `sound` booleans. Omitted types/fields retain their defaults; unknown types/fields are invalid. Controls also apply to reminders. |
114
+ | `notificationSoundDirectory` | string | Unset (bundled sounds) | Absolute, NUL-free path, at most 4096 string code units. Use the six notification type names as basenames: usable `.wav` first, then `.mp3`, then the corresponding bundled sound. |
115
+ | `formatRetries` | integer | `1` | `0`–`100` additional assessment-format correction attempts. `0` disables format corrections, not independent transport recovery. |
116
+ | `maxOutputTokens` | integer | `4096` | `1` through the maximum safe integer. Sent as `max_tokens` on every reviewer POST, including retries/corrections. Your provider/model enforces its supported output limit. |
117
+ | `timeoutMs` | integer | `30000` | `1`–`3600000` milliseconds shared across evidence collection, HTTP requests, retries and corrections; not a fresh budget per POST. |
118
+ | `maxFiles` | integer | `6` | `1`–`1000` distinct file candidates per shell/edit/skill review, including unavailable candidates. Skill reviews count the main instructions as one file. |
119
+ | `maxEvidenceBytes` | integer | `131072` | `1`–`16777216` bytes for command/source content, edit diffs or category-specific JSON evidence. Skill reviews share this budget between mandatory skill JSON and UTF-8 supporting-file content. Optional items are omitted whole; oversized mandatory evidence fails analysis. This is not a cap on the full prompt or HTTP request. |
120
+
121
+ Notification channels are independent: `banner: false, sound: true` is sound-only;
122
+ `banner: true, sound: false` is banner-only; both `false` disable that type.
123
+ `notify` and `notifySound` take precedence. Invalid notification settings disable
124
+ notification work without invalidating otherwise valid review settings.
98
125
 
99
126
  My LLMs insist on writing a bunch of useless text to my README, so I collapsed them here in case anyone wants to inflict themselves (or more likely their agent) the pain of reading it.
100
127
 
@@ -104,11 +131,46 @@ The sidebar shows Safe, Unsafe, or Analysis unavailable. Explanations support
104
131
  Markdown and scrolling. If the sidebar is hidden, use OpenCode's Show sidebar
105
132
  command.
106
133
 
134
+ When browsing a direct subagent, the same front-of-queue review appears in a
135
+ right-hand panel at terminal widths of at least 120 columns. Its auto-approval
136
+ countdown can start there without returning to the parent conversation.
137
+
138
+ Skill-load review is enabled by default; set `skill` permissions to `ask` in
139
+ `opencode.json` to receive reviews. Its prompt requires both bounded risk and clear
140
+ relevance to the current work. A harmless but irrelevant skill is rated Unsafe.
141
+ The reviewer receives the exact main instructions from the host's skill catalog,
142
+ plus bounded snapshots of directly referenced local files within the skill directory.
143
+ It does not execute scripts, follow references in supporting files, expand directory
144
+ listings, fetch external URLs, or read supporting files outside that directory.
145
+ Built-in skills without a local directory receive instruction-only review.
146
+ Customize this guidance with `SKILL-REVIEW-PROMPT.md` in your prompt directory.
147
+
148
+ For every review category, subagent requests include the latest immediate delegation
149
+ linked to the requesting assistant invocation, alongside the genuine root-user prompt.
150
+ Resumed subagents use the current delegation; nested subagents include only their
151
+ immediate delegation. Missing or over-64-KiB delegation text is noted rather than
152
+ replaced with an older task. Both prompts are context for the reviewer, not instructions
153
+ that can override its review contract.
154
+
107
155
  With streaming enabled, Evaluating and its spinner disappear when a rating arrives.
108
156
  The rating remains provisional until the full response is validated. A retry
109
- clears the preview and restores the loading indicator. Auto-approval
157
+ clears the preview and restores the loading indicator. Normal auto-approval
110
158
  starts only after the full response is validated and rendered.
111
159
 
160
+ With `autoApprove: true` and `fastMode: true`, a parsed Safe rating approves the
161
+ current visible permission immediately, without a countdown or waiting for the
162
+ rest of a streamed report. Hidden panels and covering dialogs or history prevent
163
+ fast approval. Without streaming, fast mode waits for the full validated response
164
+ and then skips the countdown.
165
+
166
+ After confirmed fast approval, the live panel shows **Auto-approved; finishing
167
+ report…** and stays ahead of newer requests until its report finishes. Hiding the
168
+ panel, changing conversations or disabling review does not stop that already-approved
169
+ assessment. Normal retries and the original deadline still apply. The panel closes
170
+ when the assessment finishes; history saves continue in the background. Only a
171
+ completed validated report is saved, including a final Unsafe rating after a retry.
172
+ A failed remainder creates no report entry and cannot undo the earlier approval.
173
+
112
174
  Transient connection failures, HTTP 408/429/500/502/503/504 responses, and rejected
113
175
  assessment streams share up to two internal retries within `timeoutMs`, honoring
114
176
  server cooldowns. Stream protocol failures and output-token truncation regenerate
@@ -126,13 +188,13 @@ or Cancel to leave the request manual. Hiding or covering the panel also cancels
126
188
  that request's countdown, except when covered by this plugin's report history.
127
189
  Cancel before using native Allow always or rejection
128
190
  forms; those forms alone do not stop the countdown.
129
- Positive countdowns hold their configured starting number for one extra second
130
- before counting down. A zero-second setting still approves without that hold.
191
+ Positive countdowns add a 200 ms initial grace and then update on remaining-time
192
+ boundaries. A zero-second setting still approves without that grace.
131
193
 
132
194
  `/reviewer-disable` and `/reviewer-enable` control the current conversation and its
133
195
  descendants. The setting is saved for resume. Both commands are also in the command
134
- palette. Disabling stops current reviews and countdowns while native permission
135
- controls remain available.
196
+ palette. Disabling stops unapproved reviews and countdowns while native permission
197
+ controls remain available. Already-approved fast-mode reports finish in the background.
136
198
 
137
199
  ### Report history
138
200
 
@@ -141,7 +203,8 @@ open the newest saved report for the current conversation and its descendants.
141
203
  Left/Right or the arrow buttons select older/newer entries; Up/Down and
142
204
  PageUp/PageDown scroll. Close or Escape closes history. Repeating the command
143
205
  returns to the newest report. History remains available with review disabled or
144
- invalid reviewer configuration, and does not force a hidden sidebar open.
206
+ invalid reviewer configuration. The history command preserves a hidden sidebar;
207
+ clicking an approval notification reveals it and selects that permission's live or saved report.
145
208
 
146
209
  Each entry shows the original report, available usage, model, provider and outcome:
147
210
  **Auto approved**, **Manually approved**, **Cancelled**, or **Rejected**. Cancelled
@@ -195,9 +258,9 @@ same state directory. Browsing and switching views do not add usage.
195
258
 
196
259
  Older retained history can provide a **partial** conversation baseline. Previously
197
260
  deleted details and usage predating history tracking cannot be attributed or
198
- reconstructed from legacy lifetime aggregates. This limitation is labeled in the
199
- dialog. Once recorded in conversation totals, deleting report details does not
200
- reduce those totals or Lifetime. Storage failures show unavailable, not zero.
261
+ reconstructed from legacy lifetime aggregates. Once recorded in conversation totals,
262
+ deleting report details does not reduce those totals or Lifetime. Storage failures
263
+ show unavailable, not zero.
201
264
 
202
265
  Tokens describe received provider usage, not just the visible explanation. One
203
266
  report may sum several reviewer POSTs, including format corrections and transport
@@ -230,25 +293,30 @@ terminal is focused:
230
293
  reviews and terminal analysis failures can notify without a report.
231
294
  - **Unsafe permission needs human approval:** a completed, validated Unsafe review,
232
295
  with its own sound. Provisional streamed ratings never notify.
233
- - **Agent has a question:** a pending agent question, with its own sound.
234
- - **Reviewer approved a permission:** sent with the approval sound after confirmed
235
- automatic approval, for both positive and zero delays. The countdown is silent.
236
- Approval sounds are limited to
237
- one every two seconds; every eligible banner is retained for delivery.
296
+ - **Agent has a question:** a pending agent question at the front of the native
297
+ input queue, with its own sound.
298
+ - **Reviewer approved a permission:** a silent banner by default, sent after confirmed
299
+ automatic approval, including fast mode while its report is still streaming.
300
+ Positive and zero delays also notify on success; the countdown is silent.
301
+ Opt in with `notifications.approved.sound: true`. Approval sounds are limited to
302
+ one every two seconds, including delayed playback. Sounds from the same session
303
+ wait for the previous player to finish, so successive alerts do not overlap.
238
304
  - **Session error:** an unrecovered session/provider failure, not a review failure.
239
305
  - **Session ended:** a completed root-agent response, not a question/permission
240
306
  pause or an explicit user interruption.
241
307
 
242
- Permission attention and Unsafe notifications, including their reminders, apply
243
- only to the current native permission blocker in each visited root conversation.
308
+ Permission attention, Unsafe and question notifications, including their reminders,
309
+ apply only to the current native input blocker in each visited root conversation.
244
310
  Requests queued behind it stay silent and are re-evaluated from their current
245
- review/approval state when they become actionable. A sequence that proceeds
311
+ state when they become actionable. Questions wait behind pending permissions and
312
+ earlier questions. A request resolved while queued never sends a delayed alert.
313
+ A sequence of permissions that proceeds
246
314
  through automatic approval stays free of attention notifications. Losing the
247
315
  actionable position, restarting analysis or entering automatic approval cancels
248
316
  pending attention delivery and reminders.
249
317
 
250
318
  Each type's `banner` and `sound` can be controlled independently. For example,
251
- `notifications.approved.banner: false` keeps approval sounds without banners;
319
+ `notifications.approved: { "banner": false, "sound": true }` enables approval sounds without banners;
252
320
  `notifications.question.sound: false` keeps question banners silent. Set both to
253
321
  `false` to disable that type. `notify: false` overrides all types and disables
254
322
  reminders too; `notifySound: false` silences all types and their reminders.
@@ -256,15 +324,16 @@ Sound-only delivery does not require a desktop banner or delivery acknowledgemen
256
324
  Invalid notification settings disable notification work without disabling reviews.
257
325
 
258
326
  Pending permissions and questions repeat after `staleReminderSeconds`, measured
259
- from initial dispatch, even if desktop delivery fails. Reminders replace their
327
+ from actual initial dispatch, even if desktop delivery fails. Capacity waiting
328
+ does not start the reminder clock or replace an undelivered initial alert. Reminders replace their
260
329
  previous banner and append **(Reminder)** to the event text, reusing the same
261
330
  sound, icon, session heading and click target. Only the native front-of-queue
262
331
  interaction per root conversation repeats: permissions precede questions, with
263
- the host's session/request ordering. Queued interactions wait; when the blocker
264
- changes, its replacement waits a full interval. Independent visited conversations
332
+ the host's session/request ordering. Queued interactions stay silent. Handoff sends
333
+ the next eligible request's initial notification, then waits a full interval before
334
+ its first reminder. Independent visited conversations
265
335
  can each remind. The supported host presents input for roots and direct children;
266
- deeper descendants cannot become a native root-input permission notification or
267
- reminder target. Their questions can still receive initial question notifications.
336
+ deeper descendants cannot become a native root-input notification or reminder target.
268
337
 
269
338
  Answering/dismissing a question in OpenCode, resolving a permission, deleting its
270
339
  session or closing the plugin stops its reminders. Closing/clicking a desktop
@@ -279,6 +348,12 @@ the originating GNOME Terminal tab and root conversation. Native input prompts
279
348
  cover root/direct-child requests in the supported host. Open dialogs are
280
349
  left intact; other terminals still receive banners and sounds. Desktop policies
281
350
  control expiry/history and whether activation actually brings a window forward.
351
+ Clicking **Reviewer approved a permission** opens that permission's unfinished
352
+ fast-mode report, or its matching saved report if it has completed, revealing the
353
+ sidebar if hidden. If a dialog is open, navigation waits until it closes and then
354
+ checks whether the report is still live. A clicked live panel closes on completion
355
+ like any fast-mode report. A pending save gets up to five seconds; if the report is
356
+ unavailable or deleted, the click just opens its conversation rather than a different report.
282
357
  GNOME Terminal clicks use the desktop activation token to bring the correct tab
283
358
  forward across workspaces. GNOME may attribute the notification source to Terminal;
284
359
  the banner heading remains Opencode.
@@ -293,6 +368,18 @@ Audio is prepared before showing its banner and played from the normalized cache
293
368
  with a low-latency buffer; preparation never blocks permission approval.
294
369
  `stdbuf` makes delivery acknowledgements immediate instead of waiting for
295
370
  `notify-send` to flush its output when the banner closes.
371
+ Delivery is bounded: up to 64 active tickets (48 routine), 256 waiting actionable
372
+ episodes, and 128 waiting routine events. A full routine backlog drops new
373
+ approval/error/completion events; already admitted events retain their exact
374
+ targets. Actionable attention/Unsafe/question work has priority, rotating among
375
+ roots; after eight actionable dispatches, waiting routine work gets a turn.
376
+ Each root has only its current actionable episode, and waiting reminders coalesce.
377
+ The process pool allows 24 children: 20 banners (16 routine), two players, one
378
+ withdrawal and one activation. Audio preparation has two actual-operation slots;
379
+ audio waiting is bounded to 64 requests. Cancellation and timeouts do not free
380
+ physical slots until their operations settle. Queued actionable work is canceled
381
+ when its request resolves or loses native queue ownership. Desktop/process
382
+ failures remain best-effort and do not delay permission approval.
296
383
  Disable overlapping notification plugins to avoid duplicate alerts.
297
384
  </details>
298
385
 
@@ -301,6 +388,12 @@ Disable overlapping notification plugins to avoid duplicate alerts.
301
388
  Use Node.js 24.15.0+ within 24.x, or 22.22.2+ within 22.x, and npm.
302
389
  Run `npm ci --ignore-scripts`, then `npm run check` for typechecking, source tests,
303
390
  pure-helper tests and the build. `npm run test:helpers` runs the helper checks
304
- without building or starting OpenCode. Pull-request CI checks both Node versions.
391
+ without building or starting OpenCode. Pull-request CI checks both Node versions
392
+ and runs a separate representative Linux OpenCode 1.18.35 fixture profile.
393
+ After building, run `node scripts/test-runtime.mjs --profile ci` for that same
394
+ profile. Use `node scripts/test-runtime.mjs --list` for the complete runtime
395
+ inventory or `node scripts/test-runtime.mjs --plan --profile ci` for a
396
+ host-independent plan. Profiles select documented scenario/flag combinations;
397
+ interactive desktop checks and historical replays have separate prerequisites.
305
398
  See [AGENTS.md](https://github.com/mightykatun/opencode-reviewer/blob/main/AGENTS.md)
306
399
  for runtime tests and the tag-driven release process.