@mortz.prk/opencode-auto-mode 1.0.1 → 1.1.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 (47) hide show
  1. package/CONTRIBUTING.md +234 -0
  2. package/README.llm.md +314 -0
  3. package/README.md +175 -166
  4. package/dist/gui-experimental.cjs +1 -0
  5. package/package.json +27 -16
  6. package/src/audit.ts +136 -38
  7. package/src/classifier.ts +45 -37
  8. package/src/code-mode.ts +55 -0
  9. package/src/config.ts +104 -98
  10. package/src/debug-events.ts +223 -0
  11. package/src/decision.ts +72 -0
  12. package/src/errors.ts +39 -0
  13. package/src/gates.ts +16 -0
  14. package/src/global-options.ts +85 -0
  15. package/src/gui-state.ts +269 -0
  16. package/src/gui.tsx +78 -0
  17. package/src/index.ts +510 -131
  18. package/src/mode.ts +237 -0
  19. package/src/reviewer.ts +71 -85
  20. package/src/rpc.ts +127 -0
  21. package/src/score/files.ts +49 -0
  22. package/src/score/history.ts +141 -0
  23. package/src/score/index.ts +37 -0
  24. package/src/score/limits.ts +24 -0
  25. package/src/score/shell-argv.ts +74 -0
  26. package/src/score/shell-baseline.ts +113 -0
  27. package/src/score/shell-safety.ts +54 -0
  28. package/src/score/shell.ts +30 -0
  29. package/src/score/types.ts +14 -0
  30. package/src/score.ts +9 -0
  31. package/src/shell-policy.ts +1979 -0
  32. package/src/shell.ts +508 -225
  33. package/src/status.ts +85 -0
  34. package/src/tiers.ts +286 -40
  35. package/src/timeout.ts +30 -0
  36. package/src/tui.tsx +44 -34
  37. package/src/user-files.ts +75 -0
  38. package/src/worktree.ts +30 -0
  39. package/src/decisions.ts +0 -213
  40. package/src/logger.ts +0 -26
  41. package/src/otlp.ts +0 -74
  42. package/src/policy.ts +0 -36
  43. package/src/state.ts +0 -135
  44. package/src/storage.ts +0 -26
  45. package/src/traces.ts +0 -84
  46. package/src/transcript.ts +0 -208
  47. package/src/tui-state.ts +0 -7
@@ -0,0 +1,234 @@
1
+ # Contributing
2
+
3
+ This repository is the `opencode-auto-mode` plugin for OpenCode V2. Start with
4
+ [README.md](README.md) for user setup and [README.llm.md](README.llm.md) for the
5
+ AI installation contract. This file covers the codebase, local development, and
6
+ review expectations.
7
+
8
+ ## Repository map
9
+
10
+ | Path | Responsibility |
11
+ | --- | --- |
12
+ | `src/index.ts` | Plugin entry. Wires the permission `evaluate` hook, Code Mode `execute.before/after`, mode commands, status RPC, audit, and cleanup. |
13
+ | `src/config.ts` | Strict public option parsing and defaults. Unknown keys fail. |
14
+ | `src/global-options.ts` | Loads safety options from exactly one user-owned global registration. Ignores project-merged `ctx.options`; rejects duplicate registrations and env/file substitution. |
15
+ | `src/score.ts`, `src/score/` | `src/score.ts` is the public `scoreRequest` compatibility re-export. Implementation lives under `src/score/` (`index`, `types`, `limits`, `files`, `history`, `shell`, `shell-argv`, `shell-safety`, `shell-baseline`). Hand-written shell, path, credential, VCS, and tool routing guards live in this ownership area. |
16
+ | `src/shell.ts` | Parsed shell guard: deny/ask classification, protected operands, recursive deletion, history changes, ambiguity floors. |
17
+ | `src/shell-policy.ts` | Shell grammar and command policy vocabulary. Large on purpose; it models parser behavior rather than matching raw text. |
18
+ | `src/tiers.ts` | Protected paths, credential names, symlink traversal, project/worktree containment, safe edit targets. |
19
+ | `src/worktree.ts` | Discovers the session's current worktree root and canonical location. |
20
+ | `src/gates.ts` | VCS preconditions for history-changing commands. Missing facts fail toward review. |
21
+ | `src/classifier.ts` | Ordered classifier pipeline. First decisive allow/deny wins; otherwise ask. |
22
+ | `src/decision.ts` | Native typed decision stage using `ctx.experimental.generate.decisions`. |
23
+ | `src/reviewer.ts` | LLM reviewer prompt, evidence filtering, response parsing, and deny-gate behavior. |
24
+ | `src/code-mode.ts` | Code Mode SHA-256 identity, save entry, identity validation, and reviewer source resource. |
25
+ | `src/mode.ts` | Durable per-session mode, daily counts, breaker window, and cancellation repair. |
26
+ | `src/status.ts`, `src/rpc.ts` | Bounded status surface and typed RPC contract (`status`, `setMode`). |
27
+ | `src/gui-state.ts`, `src/gui.tsx`, `src/tui.tsx` | Shared UI state plus desktop GUI and terminal UI parts. |
28
+ | `src/audit.ts`, `src/debug-events.ts` | Opt-in local JSONL audit and unsafe debug capture, including rotation. |
29
+ | `src/user-files.ts` | JSONC parsing and user-owned-file checks. |
30
+ | `src/timeout.ts`, `src/errors.ts` | Bounded provider calls and stable error categories. |
31
+ | `scripts/replay.ts` | Checkout-only offline audit replay with static diagnostics. |
32
+ | `scripts/no-personal-defaults.test.ts` | Privacy guard for models, endpoints, credentials, and allowlist-shaped defaults. |
33
+ | `scripts/dev-reload.sh` | Optional GUI build plus an explicit `opencode reload` request. Never restarts the live service. |
34
+ | `integration/opencode.ts` | Real `opencode serve` integration harness using scratch HOME/XDG roots and fixture providers. |
35
+ | `integration/transport.ts`, `integration/code-mode-identity.ts`, `integration/spikes.ts` | Fixture transport, Code Mode identity regression, and isolated host-behavior checks. |
36
+ | `.github/workflows/` | PR checks and the manually triggered npm release workflow. |
37
+
38
+ Tests live next to the source as `*.test.ts`. The server entry is exported through
39
+ the package root; `./tui`, `./rpc`, and `./gui-experimental` are separate exports.
40
+ `src/.npmignore` excludes test files from the packaged `src` directory.
41
+
42
+ Documentation is intentionally exactly three root files: `README.md` (human
43
+ setup), `README.llm.md` (AI installation), and `CONTRIBUTING.md` (this file). All
44
+ three are listed in `package.json` `files` and ship in the npm package. There is
45
+ no `docs/` directory; do not recreate one.
46
+
47
+ ## Local development
48
+
49
+ Prerequisites: Bun for the repository scripts, and OpenCode V2 for integration
50
+ tests. CI uses Bun `1.4.3` and Node `24`.
51
+
52
+ ```sh
53
+ bun install
54
+ bun test
55
+ bun run lint
56
+ bun run typecheck
57
+ bun run build:gui
58
+ ```
59
+
60
+ The server part is TypeScript loaded from `src/`; no server build step is needed.
61
+ `bun run build:gui` writes `dist/gui-experimental.cjs` and is required before
62
+ desktop testing or packaging. `prepublishOnly` builds the GUI automatically.
63
+
64
+ To dogfood a checkout, register its absolute directory **once** in your global
65
+ config, preserving your existing options:
66
+
67
+ ```jsonc
68
+ {
69
+ "$schema": "https://opencode.ai/config.json",
70
+ "plugins": [{
71
+ "package": "/absolute/path/to/opencode-auto-mode",
72
+ "options": {
73
+ "classifiers": [
74
+ { "type": "static" },
75
+ { "type": "llm", "model": "<provider>/<reviewer-model>" }
76
+ ]
77
+ }
78
+ }]
79
+ }
80
+ ```
81
+
82
+ Do not also register it in project config, `cli.json`, or `.opencode/plugins/`.
83
+ The global-options loader requires exactly one matching global registration.
84
+ Config files reload automatically. `scripts/dev-reload.sh [--gui]` only requests
85
+ `opencode reload`; source/dependency watching and cached server-code replacement
86
+ remain unverified. Do not restart a shared live service as a routine edit step.
87
+
88
+ ## Host behavior notes
89
+
90
+ These were observed against OpenCode `2.2.x`. Re-check them on the eventual
91
+ minimum server version before making release claims.
92
+
93
+ - A configured permission deny can skip `evaluate`, while a `policies` deny can
94
+ still invoke it before the final native block. Do not treat `hostEffect` as the
95
+ final execution outcome.
96
+ - The actual `execute` tool does not invoke permission `evaluate`; the plugin's
97
+ `execute.before` bridge is required for Code Mode review.
98
+ - Project registrations can merge `ctx.options`; `src/global-options.ts` ignores
99
+ those merged options rather than rejecting them by reading only the user-owned
100
+ global registration. That is a narrow configuration boundary, not a sandbox
101
+ against project plugins.
102
+ - Config saves reload plugins. An explicit server reload can pick up a source
103
+ marker, but attribution to the CLI versus automatic watching, and dependency or
104
+ bundle watching, remain unverified.
105
+ - Native permission rules were observed as last-match-wins. Put broad asks
106
+ before exceptions. Allows still reach the permission hook.
107
+
108
+ ## Tests
109
+
110
+ Run the full unit suite after source changes settle:
111
+
112
+ ```sh
113
+ bun test
114
+ ```
115
+
116
+ Focused checks while touching docs, scripts, or a narrow module:
117
+
118
+ ```sh
119
+ bun test src/score.test.ts src/score-security.test.ts src/shell.test.ts
120
+ bun test scripts/no-personal-defaults.test.ts scripts/replay.test.ts
121
+ bunx eslint scripts/replay.ts scripts/replay.test.ts scripts/no-personal-defaults.test.ts
122
+ git diff --check
123
+ ```
124
+
125
+ `bun test` includes the co-located unit tests plus the Bun tests under `scripts/`
126
+ and `integration/transport.test.ts`. The full real-server suite is separate:
127
+
128
+ ```sh
129
+ bun run test:integration
130
+ ```
131
+
132
+ It launches `opencode serve` (or `OPENCODE_BINARY`) against a scratch HOME and XDG
133
+ config/data/cache/state/tmp roots, starts loopback fixture providers, and sets
134
+ `OPENCODE_PLUGIN_PATH` to the checkout under test. Cases rewrite only scratch
135
+ configuration and saved approvals. Never use personal config, personal saved
136
+ approvals, personal audit logs, production credentials, or production models.
137
+
138
+ Run `bun run test:integration` after classifier, audit, Code Mode, mode, or
139
+ permission-hook changes. It covers allow/deny/invalid/timeout reviewer paths,
140
+ protected-path floors, native allow and saved-approval review, mode commands,
141
+ breaker behavior, audit isolation, shell execution, hostile project options, and
142
+ Code Mode identity/evidence rejection. Last recorded results: 44 scenarios passed
143
+ against OpenCode `2.2.5`, and `bun test` reported 396 passing. Record the actual
144
+ result of each run rather than assuming a historical count.
145
+
146
+ `bun run build:gui` verifies the desktop bundle. The privacy guard is part of
147
+ `bun test`; run it before packing. It is pattern-based, not a general secret
148
+ scanner. Build output, dependencies, lockfiles, and license text are outside its
149
+ coverage.
150
+
151
+ ## Complexity linter
152
+
153
+ All new or extracted functions must meet these limits:
154
+
155
+ - cyclomatic complexity **<= 10**;
156
+ - **<= 60 nonblank/non-comment lines** per function;
157
+ - **max depth <= 4**.
158
+
159
+ `bun run lint` runs ESLint plus `node scripts/complexity-lint.js`. The linter
160
+ applies those limits to all JavaScript/TypeScript and compares each function/rule
161
+ exceedance with `scripts/complexity-baseline.json`. Entries are keyed by file,
162
+ rule, function label, and function or depth-block start `line:column`; anonymous
163
+ arrows, separate depth blocks, and repeated function names therefore each need
164
+ their own exemption.
165
+ New exceedances and increases over a legacy entry fail. Existing legacy entries may
166
+ shrink but not grow. Moving a legacy violation (or lines before it) changes its
167
+ identity and is reported as new until the baseline is refreshed in a reviewed
168
+ change, so avoid unrelated movement around legacy entries. `src/score.ts`,
169
+ `src/score/**`, and `scripts/complexity*` are strict: they cannot be baselined or
170
+ exempted and must pass the limits directly. Tests are not exempt from these
171
+ limits.
172
+
173
+ `node scripts/complexity-lint.js --update` records improvements only. All failure
174
+ checks run before the file is written, so it refuses without modifying the
175
+ baseline while new, increased, or strict scoring/tooling violations exist. Do not
176
+ cite a different threshold.
177
+
178
+ ## Coding conventions
179
+
180
+ - TypeScript is strict ESM. Relative imports use the `.js` suffix even from
181
+ `.ts`/`.tsx` sources.
182
+ - Tests use `node:test` in `src/` and Bun's `bun:test` in `scripts/` and
183
+ `integration/transport.test.ts`. Match the surrounding file.
184
+ - Preserve the safety contract:
185
+ - native `deny` is final and is never reviewed or weakened;
186
+ - invalid configuration performs no review and leaves native behavior in
187
+ place;
188
+ - uncertain, failed, interrupted, or timed-out reviews resolve to `ask`;
189
+ - no default model, endpoint, credential, or allowlist is added;
190
+ - safety options come from the user-owned global registration, not merged
191
+ project options;
192
+ - hand-written guards belong in `scoreRequest`; classifiers must not override
193
+ mandatory score deny/ask effects;
194
+ - Code Mode identity is the exact SHA-256 of the source, metadata must validate
195
+ that identity, and the source is supplied to reviewers as a resource.
196
+ - Do not reintroduce removed features: shadow mode, custom rules, unconfigured
197
+ mode, rule IDs, performance metrics, OTLP, or remote log export.
198
+ - Do not log or commit personal audit data. Audit records are unredacted by
199
+ design and may contain secrets, prompts, and file contents.
200
+ - Keep prose in the three docs short and accurate. Prefer verified source
201
+ behavior and linked OpenCode V2 docs over guesses.
202
+
203
+ ## Release checks
204
+
205
+ Before a release, run on settled source:
206
+
207
+ ```sh
208
+ bun install --frozen-lockfile
209
+ bun test
210
+ bun run lint
211
+ bun run typecheck
212
+ bun run build:gui
213
+ bun run test:integration
214
+ bun test scripts/no-personal-defaults.test.ts scripts/replay.test.ts
215
+ git diff --check
216
+ npm pack --dry-run
217
+ ```
218
+
219
+ Confirm the packed file list includes the server and GUI entries plus `README.md`,
220
+ `README.llm.md`, and `CONTRIBUTING.md`, that their relative links resolve in the
221
+ packed layout, that `scripts/` and `docs/` are absent, and that no personal
222
+ models, endpoints, credentials, or allowlists are present. Verify the actual
223
+ `npm pack` result rather than assuming the link layout.
224
+
225
+ Last recorded results: 44 integration scenarios passed against OpenCode `2.2.5`;
226
+ the full unit suite also passed (396 tests when recorded, 399 on the current
227
+ tree). Manual desktop acceptance and multi-day dogfood have not been executed. A
228
+ green local unit/lint/typecheck/pack run is not a release readiness claim; record
229
+ the actual real-server, desktop, and dogfood results before claiming one.
230
+
231
+ Do not commit, tag, publish, restart a live service, or change personal
232
+ configuration as part of a code change unless the owner has explicitly authorized
233
+ that action. The release workflow is manual and requires `main` plus owner
234
+ authorization.
package/README.llm.md ADDED
@@ -0,0 +1,314 @@
1
+ # OpenCode Auto Mode: AI installation guide
2
+
3
+ Use this file when an AI agent is installing, auditing, or migrating the plugin
4
+ for a user. The human-facing overview is [README.md](README.md). Code changes are
5
+ covered by [CONTRIBUTING.md](CONTRIBUTING.md).
6
+
7
+ Sources of truth checked for this guide:
8
+
9
+ - [OpenCode V2 configuration](https://opencode.ai/v2/docs/config)
10
+ - [OpenCode V2 plugins](https://opencode.ai/v2/docs/build/plugins)
11
+ - This repository's `package.json` and `src/` implementation.
12
+
13
+ ## Contract
14
+
15
+ - Package: `@mortz.prk/opencode-auto-mode` version `1.1.0`.
16
+ - Host: OpenCode V2; the checked-in SDK/schema targets OpenCode `2.2.0`.
17
+ - Register the package **once** in the user-owned global config:
18
+ `~/.config/opencode/opencode.json(c)` or
19
+ `$XDG_CONFIG_HOME/opencode/opencode.json(c)`. `XDG_CONFIG_HOME`, when set,
20
+ must be absolute.
21
+ - The configuration file must be outside the repository, contain no symlink in
22
+ any path component, be user-owned, not group/other-writable, and be a single
23
+ hardlink. System-owned non-writable parents are accepted.
24
+ - Do not add the full plugin to project config, `cli.json`, or
25
+ `.opencode/plugins/` as a second registration. A plain package name, a
26
+ version-suffixed name, a local path (absolute or relative to the config file),
27
+ or a `file://` URL that resolves to this package all count as registrations.
28
+ - Do not put model endpoints, credentials, production model defaults, or
29
+ allowlists in repository files. They belong in the user's OpenCode provider
30
+ config.
31
+ - The plugin reads safety options from its global registration only. It ignores
32
+ project-merged `ctx.options` and rejects duplicate registrations or ambiguous
33
+ global files.
34
+ - **Data egress:** a configured `decision` or `llm` stage sends the action and
35
+ resources (including full Code Mode source), agent, project/worktree, git
36
+ state, and up to 12 KB of user messages to that stage's model provider, even
37
+ when audit is disabled. The `static` stage makes no model call.
38
+ - Registration strings and option values do **not** support OpenCode
39
+ `{env:...}` or `{file:...}` substitution. Write concrete values after the user
40
+ chooses them.
41
+ - Invalid configuration is **passthrough**, not fail-closed. Review stops and
42
+ native OpenCode behavior applies.
43
+
44
+ ## Preflight
45
+
46
+ 1. Confirm the OpenCode version. Install or test against V2, not V1.
47
+ 2. Locate the global config. Prefer `opencode.jsonc`; OpenCode accepts
48
+ `opencode.json` or `opencode.jsonc`.
49
+ 3. Verify loader acceptance: the file must be outside the repository, have no
50
+ symlink in any path component, be user-owned, not group/other-writable, and
51
+ be a single hardlink. `XDG_CONFIG_HOME`, when set, must be absolute; when
52
+ unset, the loader uses `~/.config`. Non-writable system-owned parents are
53
+ accepted.
54
+ 4. Back up the global config before editing it.
55
+ 5. Inspect `plugins` for an existing registration of
56
+ `@mortz.prk/opencode-auto-mode`. There must be exactly one. A plain package
57
+ name, a version-suffixed name such as
58
+ `@mortz.prk/opencode-auto-mode@1.1.0`, a local path (absolute or relative),
59
+ and a `file://` URL that resolves to this package all count as registrations.
60
+ 6. If both `opencode.json` and `opencode.jsonc` exist, check both. A matching
61
+ registration in both files is ambiguous and invalid.
62
+ 7. Confirm the user has provider/model references for any `decision` or `llm`
63
+ stage. Do not invent model IDs or credentials.
64
+ 8. For a local checkout used in the desktop app, run `bun run build:gui` before
65
+ testing; desktop loads `dist/gui-experimental.cjs`.
66
+ 9. Preserve every unrelated setting in the file. Add or edit only the plugin
67
+ entry.
68
+
69
+ The config schema is `https://opencode.ai/config.json`. A global example:
70
+
71
+ ```jsonc
72
+ {
73
+ "$schema": "https://opencode.ai/config.json",
74
+ "plugins": [{
75
+ "package": "@mortz.prk/opencode-auto-mode",
76
+ "options": {
77
+ "classifiers": [
78
+ { "type": "static" },
79
+ { "type": "llm", "model": "<provider>/<reviewer-model>" }
80
+ ],
81
+ "reviewTimeoutMs": 15000,
82
+ "breaker": { "consecutiveDenies": 3, "windowDenies": 10, "windowSize": 50 },
83
+ "tui": true,
84
+ "audit": { "enabled": false, "maxBytes": 10485760 }
85
+ }
86
+ }]
87
+ }
88
+ ```
89
+
90
+ For a local checkout, use the absolute checkout directory or a `file://` URL as
91
+ `package`. It must resolve to this package's real directory. For a published
92
+ package, a version suffix such as `@mortz.prk/opencode-auto-mode@1.1.0` is valid.
93
+
94
+ ## Install procedure
95
+
96
+ 1. Edit the chosen global config. Keep exactly one matching plugin entry.
97
+ 2. Set `options.classifiers` to at least one stage. Start with `static` only when
98
+ no review model is available:
99
+
100
+ ```jsonc
101
+ "classifiers": [{ "type": "static" }]
102
+ ```
103
+
104
+ 3. Add a `decision` stage only when the user has a native decision provider:
105
+
106
+ ```jsonc
107
+ { "type": "decision", "model": "<provider>/<decision-model>", "minConfidence": 0.9, "timeoutMs": 3000 }
108
+ ```
109
+
110
+ 4. Add an `llm` stage for the explicit reasoning reviewer. `model` is required;
111
+ there is no session-model fallback:
112
+
113
+ ```jsonc
114
+ { "type": "llm", "model": "<provider>/<reviewer-model>", "timeoutMs": 5000 }
115
+ ```
116
+
117
+ 5. Save the file. Do not restart OpenCode for a config-only change. Config files
118
+ are watched and reload automatically; `/reload` forces a full reload if the
119
+ plugin does not appear.
120
+ 6. In a session, run `/auto-mode status`. Expected for a valid install:
121
+ `Auto mode: on` with counts, active reviews, and breaker state.
122
+ 7. If the command reports `Auto mode: invalid` with `Review disabled: <error>`
123
+ (the desktop pill reads `Invalid config — review disabled`), inspect the
124
+ exact error and fix the option shape. Do not add removed 1.0.x keys. A
125
+ desktop pill reading `Review unavailable` or `status unavailable`, or a
126
+ command that appends a storage/status error without `Review disabled:`, is a
127
+ status/storage read failure, not a configuration error.
128
+ 8. Exercise a harmless permission request, such as an in-project read the
129
+ configured chain can resolve. Do not test with destructive commands,
130
+ credential files, or production data.
131
+
132
+ ## Options
133
+
134
+ Unknown keys fail validation. `options` must be an object with only these keys.
135
+
136
+ | Option | Default | Contract |
137
+ | --- | --- | --- |
138
+ | `classifiers` | required | Array of 1–64 ordered stages: `static`, `decision`, `llm`. At most one `static`. Missing or empty is invalid. |
139
+ | `reviewTimeoutMs` | `15000` | Whole-review deadline. Integer 1–120000. |
140
+ | `breaker` | `3 / 10 / 50` | `{consecutiveDenies, windowDenies, windowSize}`. If supplied, all three keys are required; each is an integer 1–10000 and `windowDenies <= windowSize`. |
141
+ | `tui` | `true` | Boolean. Shows or hides the TUI indicator. UI-only; `false` does not disable server review. |
142
+ | `audit` | `{enabled:false,maxBytes:10485760}` | Object only. `maxBytes: 0` disables rotation. Fixed path; no path option. |
143
+
144
+ Classifier stages:
145
+
146
+ | Stage | Shape | Behavior |
147
+ | --- | --- | --- |
148
+ | `static` | `{type:"static", allowInProjectEdits?: boolean}` | Allows low-risk reads and verified in-project edits. `allowInProjectEdits: false` keeps edit/write actions out of static allows. |
149
+ | `decision` | `{type:"decision", model, timeoutMs?, minConfidence?}` | Native `ctx.experimental.generate.decisions` only. Defaults: timeout 3000, confidence 0.9. |
150
+ | `llm` | `{type:"llm", model, timeoutMs?}` | Native `ctx.experimental.generate.text` reviewer. `model` is explicit and required. |
151
+
152
+ Stages run in array order. The first decisive allow or deny stops the chain.
153
+ Abstention, low confidence, invalid output, provider errors, and timeouts continue
154
+ to the next stage. A chain that cannot decide asks the user. Per-stage timeouts
155
+ are bounded by `reviewTimeoutMs`.
156
+
157
+ ## Decision behavior
158
+
159
+ | State | Native allow | Native ask | Native deny |
160
+ | --- | --- | --- | --- |
161
+ | On | Reviewed | Reviewed | Final; not reviewed |
162
+ | Off | Passthrough | Passthrough | Final |
163
+ | Paused breaker | Resolves to `ask` | Native prompt | Final |
164
+ | Invalid config | No review; native behavior | No review; native behavior | Final |
165
+
166
+ - Native deny is always final. Saved **Always** entries are re-reviewed when mode
167
+ is on but are never deleted, narrowed, rewritten, or migrated.
168
+ - A valid review that fails, times out, is interrupted, or cannot resolve
169
+ uncertainty resolves to `ask`.
170
+ - Mode off restores the original host effect and message.
171
+ - A paused breaker resolves a reviewed native allow to `ask`; an existing native
172
+ ask keeps its prompt. Paused mode never automatically allows.
173
+ - `/auto-mode on` resumes and resets the breaker.
174
+ - Session state is stored through `ctx.storage` and survives plugin reloads.
175
+
176
+ ## Guardrails and Code Mode
177
+
178
+ Built-in guards are not a policy language and are not user-extensible. They cover
179
+ protected repository/config paths, credential paths, risky shell structure,
180
+ recursive external deletion, history changes, symlink traversal, project scope,
181
+ and ambiguous syntax. Protected shell operands are a hard deny; protected
182
+ `read`/`edit` targets and symlink traversal have a mandatory ask floor. `glob` and
183
+ `grep` scope is not verified, so static review abstains. `webfetch`, `websearch`,
184
+ `execute`, and unknown tools remain HIGH.
185
+
186
+ Code Mode is bridged in `execute.before` through `ctx.permission.assert`.
187
+ Permission resources and save entries use
188
+ `execute:sha256:<UTF-8-source-hash>`, the SHA-256 of the exact source. Metadata
189
+ only validates that hash identity, and mismatches are rejected. After validation,
190
+ the reviewer receives the source as a resource; the source is evidence, not
191
+ authorization to execute it. Code Mode stays HIGH because JavaScript can hide
192
+ egress behind aliases, templates, and computed URLs. Old raw-code approvals
193
+ generally do not match the hash identity; broad native `execute` rules still do.
194
+
195
+ Coverage is limited to the permission `evaluate` hook and the Code Mode
196
+ `execute.before` bridge. The `execute.after` hook only adds a UX warning; it makes
197
+ no permission decision. If the plugin is absent, invalid, or disabled, native
198
+ behavior applies. Native path grants are not an OS sandbox. Other project plugins
199
+ can affect residual effects; only run trusted code.
200
+
201
+ ## Native Auto/YOLO
202
+
203
+ OpenCode's client-side Auto/YOLO setting can auto-approve asks without a dialog,
204
+ including plugin asks. When it is enabled:
205
+
206
+ - plugin checks still run for requests the host exposes;
207
+ - plugin denies still block;
208
+ - plugin asks are not an interactive gate;
209
+ - the plugin cannot detect, mirror, or change the client switch.
210
+
211
+ Tell the user to disable client Auto/YOLO if they need prompts. Invalid plugin
212
+ configuration also leaves native behavior, including client auto-approval, in
213
+ control.
214
+
215
+ ## Audit and replay
216
+
217
+ Audit is the only plugin-generated telemetry. It is **off by default**, local, and
218
+ unsafe. There is no OTLP, metrics, or remote export. Enable it only for temporary
219
+ debugging:
220
+
221
+ ```jsonc
222
+ "audit": { "enabled": true, "maxBytes": 10485760 }
223
+ ```
224
+
225
+ - Fixed path: `$XDG_STATE_HOME/opencode-auto-mode/decisions.jsonl` when
226
+ `XDG_STATE_HOME` is absolute; otherwise
227
+ `~/.local/state/opencode-auto-mode/decisions.jsonl`.
228
+ - `audit` is an object only. Booleans, strings, and custom `path` fields are
229
+ invalid.
230
+ - Records are unredacted and may contain secrets, credentials, PII, file
231
+ contents, source, user context, full model prompts, and model outputs.
232
+ - File mode `0600` and directory mode `0700` are access control, not
233
+ sanitization.
234
+ - When a record would exceed `maxBytes`, the active file rotates to a sibling
235
+ archive. Archives are retained; the plugin does not prune or delete them.
236
+ `maxBytes: 0` disables rotation.
237
+ - Host-event capture covers only SDK-visible events. The same JSONL also receives
238
+ plugin lifecycle and full model request/response records. Row counts are not
239
+ decision counts.
240
+ - `hostEffect` is the effect at the hook; `decision` is the applied plugin
241
+ effect. Neither is necessarily the final execution outcome.
242
+ - Never commit, upload, or share audit files without review and redaction. Tests
243
+ must use scratch XDG roots, never personal logs.
244
+
245
+ Offline replay is a repository-checkout tool because `scripts/` is not in the
246
+ published package file list:
247
+
248
+ ```sh
249
+ bun scripts/replay.ts --input <audit-jsonl-path> --project <project-root> --json
250
+ ```
251
+
252
+ Replay does not execute tools or contact models. It reports current static
253
+ routing diagnostics only and cannot reconstruct native policies, saved approvals,
254
+ model decisions, historical settings, VCS state, or invisible events.
255
+
256
+ ## Migration from 1.0.x
257
+
258
+ Back up global config first. The plugin does not rewrite or migrate user files.
259
+ A config containing any removed key is invalid and acts disabled.
260
+
261
+ | Removed setting | Required change |
262
+ | --- | --- |
263
+ | `enabled` | Remove it. Mode and the required classifier list control review. |
264
+ | `reviewNativeAllows` | Remove it. Native allows are always reviewed when mode is on. |
265
+ | `shadow` | Remove it. Shadow mode was removed with no replacement. |
266
+ | `auditLog` | Replace with `audit: { enabled, maxBytes }`. The path is fixed. |
267
+ | `otlp` | Remove it. Remote telemetry was removed. |
268
+ | `performanceTelemetry` | Remove it. Metrics export was removed. |
269
+ | `fetchHosts` | Remove it. Code Mode stays HIGH. |
270
+ | `rulesPath` | Remove it. Custom rules were removed; built-in guards remain. Existing rule files are no longer read. |
271
+
272
+ | Removed stage key | Required change |
273
+ | --- | --- |
274
+ | `llm.model` | Now required. There is no implicit session-model fallback. |
275
+ | `policy` | Remove it. Custom policy/rules were removed. |
276
+ | `endpoint` | Remove it. There is no plugin HTTP adapter or endpoint option. |
277
+ | `apiKeyEnv` | Remove it. Credentials belong in provider config. |
278
+
279
+ After migration, ensure `classifiers` is present and non-empty, with 1–64 stages
280
+ and at most one `static`. Preserve saved approvals and native rules; the plugin
281
+ does not delete or migrate them. Audit native permission/policy ordering
282
+ read-only before changing it.
283
+
284
+ ## Troubleshooting
285
+
286
+ | Symptom | Check |
287
+ | --- | --- |
288
+ | `Auto mode: invalid` with `Review disabled: <error>`; desktop pill `Invalid config — review disabled` | Exactly one global registration; only the five public option keys; valid classifier shapes and values. Fix the reported error. |
289
+ | `Auto-mode is not registered in user-owned global configuration` | Register the package in global `opencode.json(c)`. A project-only registration does not satisfy the loader. |
290
+ | `Ambiguous auto-mode global registrations` | Remove duplicate entries across `opencode.json` and `opencode.jsonc`, or duplicate objects in one file. A package name, name with version, local path, and `file://` URL resolving to this package can all collide. |
291
+ | `XDG_CONFIG_HOME must be absolute` or a `Policy file`/`Policy path` rejection | Use an absolute `XDG_CONFIG_HOME`; put config outside the repository; remove symlinks; make it user-owned, not group/other-writable, single-hardlinked, and not inside the current project. Non-writable system-owned parents are allowed. |
292
+ | `Unknown auto-mode option '<name>'` | Remove removed/unknown keys. Runtime errors do not suggest replacements. |
293
+ | Review never uses a model | A static-only chain asks when static abstains. Add a valid `decision` or `llm` stage. |
294
+ | Decision stage always abstains | Provider must support native decisions; lower confidence or check provider support. Otherwise remove the stage. |
295
+ | No prompts appear | Check client Auto/YOLO and native permissions. Plugin asks can be auto-approved by the client. |
296
+ | Desktop pill `Review unavailable` or `status unavailable`; command appends a status/storage error without `Review disabled` | This is not necessarily a config error. Retry, then check session storage and the status RPC. A storage failure during review resolves to `ask`. |
297
+ | Review latency too high | Prefer a `static` stage; tune `reviewTimeoutMs` and per-stage timeouts; remove unused model stages. |
298
+
299
+ ## Completion checklist
300
+
301
+ - [ ] Exactly one global registration exists; no project/`cli.json` duplicate.
302
+ - [ ] `classifiers` is valid, ordered, and has at most one `static`.
303
+ - [ ] Every `decision`/`llm` model reference is configured in the user's provider
304
+ settings; no credentials or endpoints were added to this repository.
305
+ - [ ] `/auto-mode status` reports `Auto mode: on` and no configuration error.
306
+ - [ ] A harmless permission request is reviewed, and mode commands respond.
307
+ - [ ] Audit remains off unless the user explicitly authorized temporary local
308
+ debugging.
309
+ - [ ] The user knows a configured `decision`/`llm` stage sends action and
310
+ resources (including full Code Mode source), agent, project/worktree, git
311
+ state, and up to 12 KB of user messages to that model's provider even with
312
+ audit off.
313
+ - [ ] The user knows that native deny remains final, invalid config is
314
+ passthrough, and client Auto/YOLO can suppress plugin asks.