Skip to content

Commit f55b272

Browse files
committed
Merge origin/main into issue-21-path-review
Incorporates PR #58 (rescue inlining + reaper + safe-command task bridge) and PR #59 (persisted command defaults) which both merged while this branch was open. Resolved conflicts: - README.md: merged the rescue slash-command line (combines #58's bridge description, #59's default model/agent note, and #60's --path addition to review and adversarial-review lines). - plugins/opencode/scripts/opencode-companion.mjs: three conflict hunks, all in handleReview and handleAdversarialReview. Combined #59's loadState/normalizeDefaults/applyDefaultModelOptions preamble with #60's paths/effectivePrNumber resolution. The job record now stores model as `requestedModel?.raw ?? modelOptions.model` (from #59) AND pr as `effectivePrNumber` plus paths (from #60), so both features coexist without interference. No code fixes were needed in this merge — all Copilot/Codex review comments on the original PR #60 first commit were already addressed by the second commit (6404d03 "fix(review): harden path review collection") before the merge. 221/221 tests pass.
2 parents 6404d03 + 80d4b03 commit f55b272

File tree

12 files changed

+409
-31
lines changed

12 files changed

+409
-31
lines changed

README.md

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,17 +93,30 @@ To check your configured providers:
9393
| `/codex:status` | `/opencode:status` | Show running/recent jobs |
9494
| `/codex:result` | `/opencode:result` | Show finished job output |
9595
| `/codex:cancel` | `/opencode:cancel` | Cancel active background job |
96-
| `/codex:setup` | `/opencode:setup` | Check install/auth, toggle review gate |
96+
| `/codex:setup` | `/opencode:setup` | Check install/auth, configure defaults, toggle review gate |
9797

9898
## Slash Commands
9999

100-
- `/opencode:review` -- Normal OpenCode code review (read-only). Supports `--base <ref>`, `--pr <number>`, `--path <path>`, `--model <provider/model>`, `--free`, `--wait`, and `--background`.
101-
- `/opencode:adversarial-review` -- Steerable review that challenges implementation and design decisions. Supports `--base <ref>`, `--pr <number>`, `--path <path>`, `--model <provider/model>`, `--free`, `--wait`, `--background`, and custom focus text.
102-
- `/opencode:rescue` -- Delegates a task to OpenCode via the `safe-command.mjs` bridge, which validates flags and feeds the task text through a shell-insulated heredoc. Supports `--model`, `--free`, `--agent`, `--resume`, `--fresh`, `--worktree`, `--wait`, and `--background`. Foreground is the default; `--wait` is an explicit no-op alias for foreground; `--background` detaches a worker and returns a job id you can poll with `/opencode:status`.
100+
- `/opencode:review` -- Normal OpenCode code review (read-only). Supports `--base <ref>`, `--pr <number>`, `--path <path>`, `--model <provider/model>`, `--free`, `--wait`, and `--background`. Uses the saved default model when configured and no runtime model flag is supplied.
101+
- `/opencode:adversarial-review` -- Steerable review that challenges implementation and design decisions. Supports `--base <ref>`, `--pr <number>`, `--path <path>`, `--model <provider/model>`, `--free`, `--wait`, `--background`, and custom focus text. Uses the saved default model when configured and no runtime model flag is supplied.
102+
- `/opencode:rescue` -- Delegates a task to OpenCode via the `safe-command.mjs` bridge, which validates flags and feeds the task text through a shell-insulated heredoc. Supports `--model`, `--free`, `--agent`, `--resume`, `--fresh`, `--worktree`, `--wait`, and `--background`. Foreground is the default; `--wait` is an explicit no-op alias for foreground; `--background` detaches a worker and returns a job id you can poll with `/opencode:status`. Uses saved default model/agent values when configured and no runtime flag is supplied.
103103
- `/opencode:status` -- Shows running/recent OpenCode jobs for the current repo.
104104
- `/opencode:result` -- Shows final output for a finished job, including OpenCode session ID for resuming.
105105
- `/opencode:cancel` -- Cancels an active OpenCode job.
106-
- `/opencode:setup` -- Checks OpenCode install/auth, can enable/disable the review gate hook, and can configure review-gate throttles.
106+
- `/opencode:setup` -- Checks OpenCode install/auth, can configure default model/agent values, can enable/disable the review gate hook, and can configure review-gate throttles.
107+
108+
## Command Defaults
109+
110+
Persist model and rescue-agent defaults with setup:
111+
112+
```
113+
/opencode:setup --default-model anthropic/claude-opus-4-6 --default-agent build
114+
/opencode:setup --default-model off
115+
/opencode:setup --default-agent off
116+
```
117+
118+
- `--default-model <provider/model>` applies to `/opencode:review`, `/opencode:adversarial-review`, and `/opencode:rescue` unless a command includes `--model` or `--free`.
119+
- `--default-agent <build|plan>` applies to `/opencode:rescue` unless the command includes `--agent`. Review commands keep using the bundled read-only review agent.
107120

108121
## Review Gate
109122

plugins/opencode/commands/adversarial-review.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Argument handling:
3737
- Do not strip `--wait`, `--background`, `--model`, `--free`, or `--pr` yourself.
3838
- Adversarial reviews support custom focus text. Any text after flags is treated as a focus area.
3939
- The companion script handles `--adversarial` internally.
40-
- `--model <id>` overrides OpenCode's default model for this single review (e.g. `--model openrouter/anthropic/claude-opus-4-6`). Pass it through verbatim if the user supplied it.
40+
- `--model <id>` overrides the saved setup default model and OpenCode's own default model for this single review (e.g. `--model openrouter/anthropic/claude-opus-4-6`). Pass it through verbatim if the user supplied it.
4141
- `--free` tells the companion script to shell out to `opencode models`, filter for first-party `opencode/*` free-tier models (those ending in `:free` or `-free`), and pick one at random for this review. Restricted to the `opencode/*` provider because OpenRouter free-tier models have inconsistent tool-use support, and the review agent needs `read`/`grep`/`glob`/`list`. Pass it through verbatim if the user supplied it. `--free` and `--model` are mutually exclusive — the companion will error if both are given.
4242
- `--pr <number>` reviews a GitHub pull request via `gh pr diff` instead of the local working tree. The cwd must be a git repo whose remote points at the PR's repository, and `gh` must be installed and authenticated.
4343
- `--path <path>` reviews a specific file or directory instead of git diff. Can be specified multiple times (`--path src --path lib`). When `--path` is set, the review is assembled from the actual file contents at those paths rather than from `git diff`. This is useful for reviewing specific directories, fixed sets of files, or large untracked/imported code drops. Mutually exclusive with `--pr` (paths take precedence over PR mode).

plugins/opencode/commands/rescue.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ Flag handling (all of these are recognized, validated, and forwarded by `safe-co
3434
- `--worktree` — run OpenCode in an isolated git worktree instead of editing the working directory in-place.
3535
- `--resume` (or `--resume-last`) — continue the most recent OpenCode session from this Claude session. The bridge translates `--resume` into the companion-native `--resume-last`.
3636
- `--fresh` — explicit marker that the task must NOT resume. The bridge strips it (the absence of `--resume-last` already conveys "fresh").
37-
- `--model <provider/model-id>` — override OpenCode's default model for this single task. Value must match `[A-Za-z0-9._/:-]+`.
37+
- `--model <provider/model-id>` — override OpenCode's default model for this single task. Value must match `[A-Za-z0-9._/:-]+`. When `--model` and `--free` are both omitted, the companion applies the saved default model from `/opencode:setup --default-model` if one is configured.
3838
- `--free` — tells the companion to pick a random first-party `opencode/*` free-tier model from `opencode models`. Restricted to `opencode/*` because OpenRouter free models have inconsistent tool-use support.
39-
- `--agent <build|plan>` — override the OpenCode agent. Value must be `build` or `plan`.
39+
- `--agent <build|plan>` — override the OpenCode agent. Value must be `build` or `plan`. When `--agent` is omitted, the companion applies the saved default agent from `/opencode:setup --default-agent` if one is configured.
4040
- `--free` and `--model` are mutually exclusive — the bridge rejects payloads that include both. If the user supplies both, return the bridge's error verbatim and stop.
4141

4242
Resume detection (runs before the final bridged call, only when neither `--resume` nor `--fresh` is in the raw user request):

plugins/opencode/commands/review.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Argument handling:
3939
- The companion script parses `--wait` and `--background`, but Claude Code's `Bash(..., run_in_background: true)` is what actually detaches the run.
4040
- `/opencode:review` is native-review only. It does not support staged-only review, unstaged-only review, or extra focus text.
4141
- If the user needs custom review instructions or more adversarial framing, they should use `/opencode:adversarial-review`.
42-
- `--model <id>` overrides OpenCode's default model for this single review (e.g. `--model openrouter/anthropic/claude-opus-4-6`). Pass it through verbatim if the user supplied it.
42+
- `--model <id>` overrides the saved setup default model and OpenCode's own default model for this single review (e.g. `--model openrouter/anthropic/claude-opus-4-6`). Pass it through verbatim if the user supplied it.
4343
- `--free` tells the companion script to shell out to `opencode models`, filter for first-party `opencode/*` free-tier models (those ending in `:free` or `-free`), and pick one at random for this review. Restricted to the `opencode/*` provider because OpenRouter free-tier models have inconsistent tool-use support, and the review agent needs `read`/`grep`/`glob`/`list`. Pass it through verbatim if the user supplied it. `--free` and `--model` are mutually exclusive — the companion will error if both are given.
4444
- `--pr <number>` reviews a GitHub pull request via `gh pr diff` instead of the local working tree. The cwd must be a git repo whose remote points at the PR's repository, and `gh` must be installed and authenticated. Pass it through verbatim if the user supplied it.
4545
- `--path <path>` reviews a specific file or directory instead of git diff. Can be specified multiple times (`--path src --path lib`). When `--path` is set, the review is assembled from the actual file contents at those paths rather than from `git diff`. This is useful for reviewing specific directories, fixed sets of files, or large untracked/imported code drops. Mutually exclusive with `--pr` (paths take precedence over PR mode).

plugins/opencode/commands/setup.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
2-
description: Check whether the local OpenCode CLI is ready and optionally toggle the stop-time review gate
3-
argument-hint: '[--enable-review-gate|--disable-review-gate] [--review-gate-max <n|off>] [--review-gate-cooldown <minutes|off>]'
2+
description: Check whether the local OpenCode CLI is ready, configure defaults, and optionally toggle the stop-time review gate
3+
argument-hint: '[--default-model <provider/model|off>] [--default-agent <build|plan|off>] [--enable-review-gate|--disable-review-gate] [--review-gate-max <n|off>] [--review-gate-cooldown <minutes|off>]'
44
allowed-tools: Bash(node:*), Bash(npm:*), Bash(brew:*), Bash(curl:*), AskUserQuestion
55
---
66

@@ -39,3 +39,5 @@ Output rules:
3939
- Present the final setup output to the user.
4040
- If installation was skipped, present the original setup output.
4141
- If OpenCode is installed but no provider is configured, guide the user to run `!opencode providers` to set up authentication.
42+
- `--default-model <provider/model>` sets the model used by `/opencode:review`, `/opencode:adversarial-review`, and `/opencode:rescue` when no `--model` or `--free` flag is supplied. Use `--default-model off` to clear it.
43+
- `--default-agent <build|plan>` sets the rescue/task agent used when no `--agent` flag is supplied. Review commands keep using the bundled read-only review agent. Use `--default-agent off` to clear it.
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Persistent command-default helpers for the OpenCode companion.
2+
//
3+
// `/opencode:setup` can persist defaults in `state.config.defaults`.
4+
// These helpers keep precedence rules centralized and testable:
5+
// explicit runtime flags win, otherwise saved defaults apply.
6+
7+
import { parseModelString } from "./model.mjs";
8+
9+
const SUPPORTED_DEFAULT_AGENTS = new Set(["build", "plan"]);
10+
11+
/**
12+
* @param {Record<string, unknown>|undefined|null} options
13+
* @param {string} key
14+
* @returns {boolean}
15+
*/
16+
export function hasOwnOption(options, key) {
17+
return Object.prototype.hasOwnProperty.call(options ?? {}, key);
18+
}
19+
20+
/**
21+
* Normalize persisted defaults read from state. Invalid or missing values are
22+
* ignored so a hand-edited state file cannot break every command invocation.
23+
* @param {unknown} raw
24+
* @returns {{ model: string | null, agent: string | null }}
25+
*/
26+
export function normalizeDefaults(raw) {
27+
const defaults = raw && typeof raw === "object" ? raw : {};
28+
29+
const modelRaw = typeof defaults.model === "string" ? defaults.model.trim() : "";
30+
const model = modelRaw && parseModelString(modelRaw) ? modelRaw : null;
31+
32+
const agentRaw = typeof defaults.agent === "string" ? defaults.agent.trim() : "";
33+
const agent = SUPPORTED_DEFAULT_AGENTS.has(agentRaw) ? agentRaw : null;
34+
35+
return { model, agent };
36+
}
37+
38+
/**
39+
* Parse a `/opencode:setup --default-model` value. Returns null for "off".
40+
* @param {unknown} value
41+
* @returns {string | null}
42+
*/
43+
export function parseDefaultModelSetting(value) {
44+
const raw = typeof value === "string" ? value.trim() : "";
45+
if (raw === "off") return null;
46+
if (!raw || !parseModelString(raw)) {
47+
throw new Error(
48+
`--default-model must be "off" or a provider/model-id value ` +
49+
`(e.g. anthropic/claude-opus-4-6).`
50+
);
51+
}
52+
return raw;
53+
}
54+
55+
/**
56+
* Parse a `/opencode:setup --default-agent` value. Returns null for "off".
57+
* @param {unknown} value
58+
* @returns {"build" | "plan" | null}
59+
*/
60+
export function parseDefaultAgentSetting(value) {
61+
const raw = typeof value === "string" ? value.trim() : "";
62+
if (raw === "off") return null;
63+
if (!SUPPORTED_DEFAULT_AGENTS.has(raw)) {
64+
throw new Error(`--default-agent must be "build", "plan", or "off".`);
65+
}
66+
return raw;
67+
}
68+
69+
/**
70+
* Apply a persisted model default when the user did not explicitly supply
71+
* either `--model` or `--free`.
72+
* @param {Record<string, unknown>} options
73+
* @param {{ model?: string | null }} defaults
74+
* @returns {Record<string, unknown>}
75+
*/
76+
export function applyDefaultModelOptions(options, defaults) {
77+
if (hasOwnOption(options, "model") || options?.free) return options;
78+
if (!defaults?.model) return options;
79+
return { ...options, model: defaults.model };
80+
}
81+
82+
/**
83+
* Resolve the task agent using explicit CLI args first, then persisted
84+
* defaults, then the existing write/read-only fallback.
85+
* @param {Record<string, unknown>} options
86+
* @param {{ agent?: string | null }} defaults
87+
* @param {boolean} isWrite
88+
* @returns {string}
89+
*/
90+
export function resolveTaskAgentName(options, defaults, isWrite) {
91+
if (hasOwnOption(options, "agent")) return options.agent;
92+
if (defaults?.agent) return defaults.agent;
93+
return isWrite ? "build" : "plan";
94+
}

plugins/opencode/scripts/lib/render.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,10 @@ export function renderSetup(status) {
207207
} else if (status.installed) {
208208
lines.push(`- **Providers**: None configured. Run \`!opencode providers\` to set up.`);
209209
}
210+
if (status.defaults) {
211+
lines.push(`- **Default Model**: ${status.defaults.model ? `\`${status.defaults.model}\`` : "Unset"}`);
212+
lines.push(`- **Default Agent**: ${status.defaults.agent ? `\`${status.defaults.agent}\`` : "Unset"}`);
213+
}
210214
if (status.reviewGate !== undefined) {
211215
const parts = [status.reviewGate ? "Enabled" : "Disabled"];
212216
if (status.reviewGateMaxPerSession != null) {

0 commit comments

Comments
 (0)