@humanbased/crosscheck 1.6.1-beta.5 → 1.7.0-beta.22

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 (92) hide show
  1. package/README.md +3 -1
  2. package/crosscheck.config.example.yml +20 -4
  3. package/dist/__tests__/detector.test.js +22 -0
  4. package/dist/__tests__/detector.test.js.map +1 -1
  5. package/dist/__tests__/onboard-preservation.test.js +27 -5
  6. package/dist/__tests__/onboard-preservation.test.js.map +1 -1
  7. package/dist/__tests__/optimize.test.js +18 -2
  8. package/dist/__tests__/optimize.test.js.map +1 -1
  9. package/dist/__tests__/review-models.test.js +5 -5
  10. package/dist/__tests__/review-models.test.js.map +1 -1
  11. package/dist/__tests__/review-strategy.test.js +10 -10
  12. package/dist/__tests__/review-strategy.test.js.map +1 -1
  13. package/dist/__tests__/runner.test.js +12 -2
  14. package/dist/__tests__/runner.test.js.map +1 -1
  15. package/dist/commands/init.d.ts.map +1 -1
  16. package/dist/commands/init.js +14 -1
  17. package/dist/commands/init.js.map +1 -1
  18. package/dist/commands/issue.d.ts.map +1 -1
  19. package/dist/commands/issue.js +40 -16
  20. package/dist/commands/issue.js.map +1 -1
  21. package/dist/commands/onboard.d.ts +5 -4
  22. package/dist/commands/onboard.d.ts.map +1 -1
  23. package/dist/commands/onboard.js +126 -48
  24. package/dist/commands/onboard.js.map +1 -1
  25. package/dist/commands/optimize.d.ts +1 -1
  26. package/dist/commands/optimize.d.ts.map +1 -1
  27. package/dist/commands/optimize.js +53 -13
  28. package/dist/commands/optimize.js.map +1 -1
  29. package/dist/commands/review.d.ts.map +1 -1
  30. package/dist/commands/review.js +9 -3
  31. package/dist/commands/review.js.map +1 -1
  32. package/dist/commands/run.d.ts +2 -2
  33. package/dist/commands/run.d.ts.map +1 -1
  34. package/dist/commands/run.js +1 -1
  35. package/dist/commands/run.js.map +1 -1
  36. package/dist/commands/status.d.ts.map +1 -1
  37. package/dist/commands/status.js +12 -1
  38. package/dist/commands/status.js.map +1 -1
  39. package/dist/commands/watch.d.ts.map +1 -1
  40. package/dist/commands/watch.js +6 -3
  41. package/dist/commands/watch.js.map +1 -1
  42. package/dist/config/review-model-tiers.json +9 -9
  43. package/dist/config/review-strategy.json +12 -12
  44. package/dist/config/schema.d.ts +102 -16
  45. package/dist/config/schema.d.ts.map +1 -1
  46. package/dist/config/schema.js +28 -3
  47. package/dist/config/schema.js.map +1 -1
  48. package/dist/github/client.js +1 -1
  49. package/dist/github/client.js.map +1 -1
  50. package/dist/github/detector.d.ts +2 -2
  51. package/dist/github/detector.d.ts.map +1 -1
  52. package/dist/github/detector.js +46 -11
  53. package/dist/github/detector.js.map +1 -1
  54. package/dist/lib/comment-bodies.d.ts.map +1 -1
  55. package/dist/lib/comment-bodies.js +4 -2
  56. package/dist/lib/comment-bodies.js.map +1 -1
  57. package/dist/lib/review-models.d.ts +4 -1
  58. package/dist/lib/review-models.d.ts.map +1 -1
  59. package/dist/lib/review-models.js +8 -0
  60. package/dist/lib/review-models.js.map +1 -1
  61. package/dist/lib/reviewer-error.d.ts.map +1 -1
  62. package/dist/lib/reviewer-error.js +12 -8
  63. package/dist/lib/reviewer-error.js.map +1 -1
  64. package/dist/lib/runner.d.ts +8 -7
  65. package/dist/lib/runner.d.ts.map +1 -1
  66. package/dist/lib/runner.js +71 -20
  67. package/dist/lib/runner.js.map +1 -1
  68. package/dist/lib/smart-switch.d.ts +7 -6
  69. package/dist/lib/smart-switch.d.ts.map +1 -1
  70. package/dist/lib/smart-switch.js +9 -2
  71. package/dist/lib/smart-switch.js.map +1 -1
  72. package/dist/lib/vendor.d.ts +2 -2
  73. package/dist/lib/vendor.d.ts.map +1 -1
  74. package/dist/lib/vendor.js +12 -2
  75. package/dist/lib/vendor.js.map +1 -1
  76. package/dist/lib/workflow.d.ts +8 -8
  77. package/dist/lib/workflow.js +1 -1
  78. package/dist/lib/workflow.js.map +1 -1
  79. package/dist/reviewers/fix.d.ts +6 -0
  80. package/dist/reviewers/fix.d.ts.map +1 -1
  81. package/dist/reviewers/fix.js +61 -0
  82. package/dist/reviewers/fix.js.map +1 -1
  83. package/dist/reviewers/opencode-env.d.ts +13 -0
  84. package/dist/reviewers/opencode-env.d.ts.map +1 -0
  85. package/dist/reviewers/opencode-env.js +52 -0
  86. package/dist/reviewers/opencode-env.js.map +1 -0
  87. package/dist/reviewers/opencode.d.ts +17 -0
  88. package/dist/reviewers/opencode.d.ts.map +1 -0
  89. package/dist/reviewers/opencode.js +293 -0
  90. package/dist/reviewers/opencode.js.map +1 -0
  91. package/get-started.md +38 -4
  92. package/package.json +1 -1
@@ -0,0 +1,293 @@
1
+ import { execa } from 'execa';
2
+ import { rmSync, mkdtempSync, renameSync, existsSync, writeFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { tmpdir } from 'os';
5
+ import { DEFAULT_REVIEW_INSTRUCTIONS } from '../lib/workflow.js';
6
+ import { resolveOpenCodeModel } from '../lib/review-models.js';
7
+ import { withTimeoutRetry } from '../lib/with-timeout-retry.js';
8
+ import { vendorFailureSummary } from '../lib/vendor-error-summary.js';
9
+ import { tierTimeoutMs } from './tier-timeouts.js';
10
+ import { buildOpenCodeEnv } from './opencode-env.js';
11
+ import { loadRepositoryReviewGuidance } from '../lib/repository-guidance.js';
12
+ import { withCredentialFreeOrigin } from '../lib/clone.js';
13
+ // OpenCode's reasoning-effort ladder is the `#variant` suffix on
14
+ // `--model provider/model#variant`. Variants are provider-defined, not one
15
+ // global ladder like claude/codex: deepseek-v4-* (the opencode models
16
+ // crosscheck ships policy for) expose none / high / max, with no low/medium.
17
+ // Whitelist rather than translation — anything outside this vocabulary falls
18
+ // back to `high` instead of reaching the CLI as an unsupported variant.
19
+ const OPENCODE_EFFORT_MAP = {
20
+ none: 'none',
21
+ high: 'high',
22
+ max: 'max',
23
+ };
24
+ export function opencodeEffort(effort) {
25
+ return (effort && OPENCODE_EFFORT_MAP[effort]) ?? 'high';
26
+ }
27
+ export function parseOpenCodeOutput(raw) {
28
+ let review = '';
29
+ let inputTokens = 0;
30
+ let outputTokens = 0;
31
+ let reasoningTokens = 0;
32
+ let sawTokens = false;
33
+ for (const line of raw.split('\n')) {
34
+ if (!line.trim())
35
+ continue;
36
+ let event;
37
+ try {
38
+ event = JSON.parse(line);
39
+ }
40
+ catch {
41
+ // Non-JSON line (progress/log noise) — skip, never fail the whole parse.
42
+ continue;
43
+ }
44
+ if (event.type === 'text' && event.part?.text)
45
+ review += event.part.text;
46
+ if (event.type === 'step_finish' && event.part?.tokens) {
47
+ // Each model step emits its own payload; accumulate rather than overwrite,
48
+ // and include reasoning so a multi-step run is not undercounted.
49
+ const t = event.part.tokens;
50
+ if (typeof t.input === 'number')
51
+ inputTokens += t.input;
52
+ if (typeof t.output === 'number')
53
+ outputTokens += t.output;
54
+ if (typeof t.reasoning === 'number')
55
+ reasoningTokens += t.reasoning;
56
+ sawTokens = true;
57
+ }
58
+ }
59
+ const tokensUsed = sawTokens ? inputTokens + outputTokens + reasoningTokens : undefined;
60
+ return { review, tokensUsed };
61
+ }
62
+ // OpenCode follows the same VERDICT rule as claude/codex, so a well-behaved run
63
+ // ends with the line. Match it case-insensitively: a `Verdict: BLOCK` that the
64
+ // old exact-match check missed then received an appended APPROVE, which the
65
+ // parser read as the final verdict. When no verdict was stated at all, default
66
+ // to NEEDS WORK — never APPROVE, which would clear a PR on a verdict nobody
67
+ // gave (an empty or refusing body is caught earlier by detectInconclusiveReview).
68
+ export function inferVerdictFromOpenCodeOutput(text) {
69
+ return /\bVERDICT\s*:/i.test(text) ? text : `${text}\n\nVERDICT: NEEDS WORK`;
70
+ }
71
+ // Detect transient OpenCode errors that should be retried (rate limits, socket
72
+ // disconnects). OpenCode reports these through the same shapes as codex.
73
+ function isRetryableOpenCodeError(message) {
74
+ return /socket.*closed|429|rate limit|connection.*reset|econnreset/i.test(message);
75
+ }
76
+ const MAX_OPENCODE_RETRIES = 2;
77
+ const OPENCODE_RETRY_DELAY_MS = 5000;
78
+ // Scans stderr bottom-up for the first fatal/error line, skipping OpenCode
79
+ // header/log boilerplate.
80
+ function extractOpenCodeErrorSummary(stderr) {
81
+ const lines = stderr.split('\n').map(l => l.trim()).filter(Boolean);
82
+ for (let i = lines.length - 1; i >= 0; i--) {
83
+ const l = lines[i];
84
+ if (/^(fatal|error):/i.test(l))
85
+ return l;
86
+ }
87
+ return lines.filter(l => !l.startsWith('---') &&
88
+ !/^\d{4}-\d{2}-\d{2}T\d/.test(l) &&
89
+ !/ WARN /i.test(l)).at(-1);
90
+ }
91
+ // OpenCode auto-discovers a project's own opencode.json / .opencode directory
92
+ // from the working directory upward, and loads any plugin they register. In an
93
+ // untrusted checkout that is an arbitrary-code channel independent of the
94
+ // prompt, and OpenCode v2 has no `--pure` / config override flag to suppress it.
95
+ // Remove them before the run so only the operator's global config applies. The
96
+ // clone is a throwaway and a review reads the diff, not these files.
97
+ // A restrictive permission policy for the untrusted run. OpenCode v2 has no
98
+ // sandbox flag, so this is the tool-denial layer: a review inspects the diff by
99
+ // running read-only git, so allow exactly those shell commands and deny every
100
+ // other one. `--auto` auto-approves `ask` but cannot widen a `deny`.
101
+ // `allowEdit` is left for the fix step, which must write files.
102
+ export function opencodePolicy(allowEdit) {
103
+ return {
104
+ permissions: [
105
+ { action: 'shell', resource: '*', effect: 'deny' },
106
+ { action: 'shell', resource: 'git status *', effect: 'allow' },
107
+ { action: 'shell', resource: 'git diff *', effect: 'allow' },
108
+ { action: 'shell', resource: 'git log *', effect: 'allow' },
109
+ { action: 'shell', resource: 'git show *', effect: 'allow' },
110
+ ...(allowEdit ? [] : [{ action: 'edit', resource: '*', effect: 'deny' }]),
111
+ ],
112
+ };
113
+ }
114
+ export function isolateCheckoutOpenCodeConfig(repoDir, allowEdit = false) {
115
+ const backupDir = mkdtempSync(join(tmpdir(), 'crosscheck-oc-config-'));
116
+ const moved = [];
117
+ for (const rel of ['opencode.json', 'opencode.jsonc', '.opencode']) {
118
+ const from = join(repoDir, rel);
119
+ if (!existsSync(from))
120
+ continue;
121
+ const to = join(backupDir, rel.replace(/[/\\]/g, '_'));
122
+ try {
123
+ renameSync(from, to);
124
+ moved.push({ from, to });
125
+ }
126
+ catch { /* best effort */ }
127
+ }
128
+ // Install the policy as the project config this run will load; remove it on
129
+ // restore so a later fix's `git add -A` cannot stage it.
130
+ let policyWritten = false;
131
+ try {
132
+ writeFileSync(join(repoDir, 'opencode.json'), JSON.stringify(opencodePolicy(allowEdit), null, 2));
133
+ policyWritten = true;
134
+ }
135
+ catch { /* best effort */ }
136
+ // Restore the checkout's own config so a later fix step does not stage these
137
+ // tracked deletions as changed files (git add -A would then commit and push).
138
+ return () => {
139
+ if (policyWritten) {
140
+ try {
141
+ rmSync(join(repoDir, 'opencode.json'), { force: true });
142
+ }
143
+ catch { /* best effort */ }
144
+ }
145
+ const failed = [];
146
+ for (const { from, to } of moved.reverse()) {
147
+ try {
148
+ renameSync(to, from);
149
+ }
150
+ catch {
151
+ failed.push(from);
152
+ }
153
+ }
154
+ // Delete the backup only when every path was restored. If the agent recreated
155
+ // a conflicting path, keep the backup and fail loudly so the original tracked
156
+ // configuration is never silently lost.
157
+ if (failed.length > 0) {
158
+ throw new Error(`could not restore OpenCode config: ${failed.join(', ')} (backup kept at ${backupDir})`);
159
+ }
160
+ try {
161
+ rmSync(backupDir, { recursive: true, force: true });
162
+ }
163
+ catch { /* best effort */ }
164
+ };
165
+ }
166
+ export async function runOpenCodeReview(repoDir, baseBranch, prTitle, quality, vendor, stepInstructions, onLog, timeoutMs, onRetry, issueContext, _skillSession) {
167
+ const model = resolveOpenCodeModel(quality, vendor);
168
+ const effort = opencodeEffort(vendor.effort);
169
+ const tierTimeout = tierTimeoutMs(quality.tier);
170
+ // timeoutMs: 0 → no cap (crazy/halfcrazy); undefined → tier-based default; positive → user-specified
171
+ const resolvedTimeout = timeoutMs === undefined ? tierTimeout : timeoutMs === 0 ? undefined : timeoutMs;
172
+ const focusLine = quality.focus.length > 0
173
+ ? `Focus areas: ${quality.focus.join(', ')}.`
174
+ : '';
175
+ const customLine = quality.custom_prompt ?? '';
176
+ const behaviorInstructions = stepInstructions ?? DEFAULT_REVIEW_INSTRUCTIONS;
177
+ const repositoryGuidance = loadRepositoryReviewGuidance(repoDir, baseBranch);
178
+ // Same block order as runClaudeReview/runCodexReview, so the same PR gets the
179
+ // same brief whichever vendor draws it: behaviour block ends on the verdict
180
+ // rule, repository guidance after it (reference material, not a first step).
181
+ const prompt = [
182
+ `You are reviewing a pull request titled: "${prTitle}".`,
183
+ `The branch \`${baseBranch}\` is the base. Review only the changes introduced in this PR.`,
184
+ issueContext ?? '',
185
+ focusLine,
186
+ customLine,
187
+ behaviorInstructions,
188
+ repositoryGuidance,
189
+ ].filter(Boolean).join('\n\n');
190
+ // OpenCode takes the prompt on stdin (like claude/codex): `opencode run` with
191
+ // no message argument reads stdin, and keeping the prompt out of argv stops
192
+ // repository guidance from reaching the process list. `--standalone` runs a
193
+ // private server, so a review of untrusted code never shares a session with
194
+ // the operator's interactive work. `--auto` is required even for a read-only
195
+ // review: a headless run auto-rejects permission requests otherwise, and
196
+ // reviewing a diff can trip `external_directory` (the clone lives outside the
197
+ // agent's workspace root) which is fatal without it. The env allowlist and
198
+ // the throwaway clone bound the blast radius of the widened permissions.
199
+ //
200
+ // OpenCode has no separate effort flag — reasoning effort is the `#variant`
201
+ // suffix on `--model provider/model#variant`. Without a pinned model there is
202
+ // nowhere to hang the suffix, so `vendors.opencode.effort` is honoured only
203
+ // when `vendors.opencode.model` is set; otherwise OpenCode's configured
204
+ // default model AND its default variant run, and `effort` is reported but
205
+ // not applied (it is not a claim the CLI was given).
206
+ const modelArgs = model ? ['--model', `${model}#${effort}`] : [];
207
+ const args = ['run', '--format', 'json', '--auto', '--standalone', ...modelArgs];
208
+ onLog?.(` running: opencode run --format json --auto --standalone${model ? ` --model ${model}#${effort}` : ''}`);
209
+ let lastErr = undefined;
210
+ for (let attempt = 1; attempt <= MAX_OPENCODE_RETRIES; attempt++) {
211
+ try {
212
+ const { result: { stdout }, retried } = await withCredentialFreeOrigin(repoDir, () => {
213
+ // Hide any OpenCode config the untrusted checkout carries while the agent
214
+ // runs; restore it after so a later fix step cannot stage the deletion.
215
+ const restoreConfig = isolateCheckoutOpenCodeConfig(repoDir);
216
+ return withTimeoutRetry(resolvedTimeout, (t) => execa('opencode', args, {
217
+ cwd: repoDir,
218
+ timeout: t,
219
+ input: prompt,
220
+ // extendEnv: false or execa merges process.env back in and the
221
+ // allowlist means nothing.
222
+ extendEnv: false,
223
+ // No `${repoDir}/node_modules/.bin` on PATH: the checkout is untrusted
224
+ // and a committed `node_modules/.bin/opencode` would shadow the real
225
+ // executable. A review only reads the diff, so repo-local tools are
226
+ // not needed the way codex.ts assumed they were.
227
+ env: buildOpenCodeEnv(),
228
+ }), {
229
+ onRetry: (effectiveMs, delayMs) => (onRetry ?? onLog)?.(` ⏱ opencode timed out at ${effectiveMs / 1000}s — waiting ${delayMs / 1000}s and retrying once`),
230
+ }).finally(restoreConfig);
231
+ });
232
+ const { review: parsedReview, tokensUsed } = parseOpenCodeOutput(stdout ?? '');
233
+ const rawReview = parsedReview.trim();
234
+ const review = inferVerdictFromOpenCodeOutput(rawReview);
235
+ // Report effort only when a pinned model actually carried the #variant;
236
+ // with no model there is no suffix, so claiming a level would be false.
237
+ return { review, tokensUsed, model: model ?? 'default', effort: model ? effort : undefined, retried };
238
+ }
239
+ catch (err) {
240
+ const execa = err;
241
+ const rawStderr = execa.stderr ?? '';
242
+ const fullMessage = rawStderr || execa.message || '';
243
+ if (isRetryableOpenCodeError(fullMessage) && attempt < MAX_OPENCODE_RETRIES) {
244
+ const delay = OPENCODE_RETRY_DELAY_MS * attempt; // 5s, 10s
245
+ onLog?.(` opencode: transient error (${fullMessage.slice(0, 80)}), retrying in ${delay / 1000}s (attempt ${attempt}/${MAX_OPENCODE_RETRIES})...`);
246
+ await new Promise(resolve => setTimeout(resolve, delay));
247
+ lastErr = err;
248
+ continue;
249
+ }
250
+ const effectiveMs = execa.effectiveTimeoutMs ?? resolvedTimeout;
251
+ const retryNote = execa.retryDelayMs !== undefined ? ' (retried once)' : '';
252
+ const summary = execa.timedOut
253
+ ? `timed out after ${effectiveMs !== undefined ? effectiveMs / 1000 : '?'}s${retryNote} — PR diff may be too large (tier: ${quality.tier})`
254
+ : vendorFailureSummary(execa, extractOpenCodeErrorSummary);
255
+ const thrown = Object.assign(new Error(`opencode: ${summary}`), {
256
+ exitCode: execa.exitCode,
257
+ timedOut: execa.timedOut,
258
+ stderr: rawStderr,
259
+ effectiveTimeoutMs: effectiveMs,
260
+ retryDelayMs: execa.retryDelayMs,
261
+ });
262
+ throw thrown;
263
+ }
264
+ }
265
+ if (lastErr)
266
+ throw lastErr;
267
+ throw new Error('opencode: unexpected retry loop exit');
268
+ }
269
+ export async function checkOpenCodeAuth() {
270
+ try {
271
+ // `--format json` returns one record per provider, so human-readable headers
272
+ // and summaries can never be miscounted as authenticated providers the way
273
+ // the text form's line count could.
274
+ const { stdout } = await execa('opencode', ['auth', 'list', '--format', 'json'], { timeout: 10_000 });
275
+ let parsed;
276
+ try {
277
+ parsed = JSON.parse(stdout);
278
+ }
279
+ catch {
280
+ parsed = null;
281
+ }
282
+ const providers = Array.isArray(parsed) ? parsed : [];
283
+ if (providers.length === 0) {
284
+ return { ok: false, detail: 'no providers configured — run: opencode auth login' };
285
+ }
286
+ return { ok: true, detail: `${providers.length} provider(s) configured` };
287
+ }
288
+ catch (err) {
289
+ const error = err;
290
+ return { ok: false, detail: error.stderr ?? error.message ?? 'not found' };
291
+ }
292
+ }
293
+ //# sourceMappingURL=opencode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opencode.js","sourceRoot":"","sources":["../../src/reviewers/opencode.ts"],"names":[],"mappings":"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"}
package/get-started.md CHANGED
@@ -37,7 +37,7 @@
37
37
 
38
38
  ## Prerequisites
39
39
 
40
- You need GitHub CLI and at least one authenticated AI reviewer CLI before crosscheck can run a one-shot review. Install both Claude Code and Codex only if you want cross-vendor review routing.
40
+ You need GitHub CLI and at least one authenticated AI reviewer CLI before crosscheck can run a one-shot review. Install both Claude Code and Codex only if you want cross-vendor review routing. OpenCode is an optional third harness, disabled by default.
41
41
 
42
42
  ### Claude Code
43
43
 
@@ -65,6 +65,15 @@ printenv OPENAI_API_KEY | codex login --with-api-key
65
65
 
66
66
  Then set `auth: api-key` in your config to enable model selection.
67
67
 
68
+ ### OpenCode
69
+
70
+ ```bash
71
+ npm install -g @opencode/cli
72
+ opencode auth login # sign in to a provider (e.g. DeepSeek, Anthropic, OpenAI)
73
+ ```
74
+
75
+ OpenCode is **opt-in**: enable it with `vendors.opencode.enabled: true` (or `crosscheck onboard`). Its model and provider come from your OpenCode config (`opencode auth login` / `opencode.json`) — crosscheck pins no model by default, so reviews run against whichever model OpenCode is configured to use. Reasoning effort is carried as the `#variant` suffix on `--model provider/model#variant`, so `vendors.opencode.effort` (none | high | max) applies only when `vendors.opencode.model` is also set.
76
+
68
77
  ### GitHub CLI
69
78
 
70
79
  ```bash
@@ -794,6 +803,7 @@ crosscheck status
794
803
  Auth
795
804
  ✓ codex authenticated
796
805
  ✓ claude 2.1.x (Claude Code)
806
+ ✓ opencode v2.x
797
807
  ✓ GITHUB_TOKEN via gh auth login
798
808
  ✓ WEBHOOK_SECRET auto-managed at ~/.crosscheck/webhook-secret
799
809
 
@@ -804,6 +814,8 @@ crosscheck status
804
814
  enabled skills code-review-skill (by @awesome-skills, MIT), diagnosing-bugs (by @mattpocock, MIT)
805
815
  codex auth subscription
806
816
  claude model sonnet
817
+ opencode enabled false
818
+ opencode model configured in opencode.json
807
819
  per-review budget $2.00/review
808
820
 
809
821
  Impact
@@ -817,6 +829,7 @@ crosscheck status
817
829
  CLIs
818
830
  codex codex-cli 0.128.0
819
831
  claude 2.1.x (Claude Code)
832
+ opencode v2.x
820
833
  ```
821
834
 
822
835
  | Flag | Description |
@@ -1192,8 +1205,8 @@ vendors:
1192
1205
  codex:
1193
1206
  enabled: true
1194
1207
  auth: subscription # subscription | api-key
1195
- model: gpt-5.6-terra # pins the review model; unset = tier model (api-key) / CLI default (subscription)
1196
- effort: medium # low | medium | high | xhigh | max | ultra (ultra: terra/sol only)
1208
+ model: gpt-6.1-sol # pins the review model; unset = tier model (api-key) / CLI default (subscription)
1209
+ effort: medium # low | medium | high | xhigh | max | ultra
1197
1210
  # timeout_sec: 1200 # max seconds per CLI call; unset = tier-based (300/600/1200)
1198
1211
 
1199
1212
  claude:
@@ -1202,6 +1215,16 @@ vendors:
1202
1215
  effort: medium # low | medium | high | max
1203
1216
  # timeout_sec: 1200 # max seconds per CLI call; unset = tier-based (300/600/1200)
1204
1217
 
1218
+ # Opt-in third harness; disabled by default. Its model/provider come from
1219
+ # `opencode auth login` / opencode.json, not from a crosscheck catalog.
1220
+ # Reasoning effort rides the `--model provider/model#variant` suffix, so
1221
+ # `effort` applies only when `model` is also set.
1222
+ opencode:
1223
+ enabled: false
1224
+ # model: provider/model # e.g. alibaba-cn/deepseek-v4-pro; unset = opencode.json default
1225
+ effort: high # none | high | max
1226
+ # timeout_sec: 1200 # max seconds per CLI call; unset = tier-based (300/600/1200)
1227
+
1205
1228
  # ── Quality ───────────────────────────────────────────────────────────────────
1206
1229
  quality:
1207
1230
  # Structured findings and incremental follow-ups; set false for legacy prose reviews.
@@ -1285,12 +1308,18 @@ routing:
1285
1308
  claude_reviews_patterns:
1286
1309
  - "Generated with \\[OpenAI Codex\\]" # Codex attribution footer
1287
1310
  - "Co-Authored-By: codex" # commit trailer
1311
+ opencode_reviews_patterns:
1312
+ - "Generated with \\[OpenCode\\]" # OpenCode attribution footer
1313
+ - "Co-Authored-By: opencode" # commit trailer
1288
1314
 
1289
- # Branch prefix detection (signal 3). Claude Code uses claude/, Codex uses codex/.
1315
+ # Branch prefix detection (signal 3). Claude Code uses claude/, Codex uses
1316
+ # codex/, OpenCode uses opencode/.
1290
1317
  claude_branch_prefixes:
1291
1318
  - "claude/"
1292
1319
  codex_branch_prefixes:
1293
1320
  - "codex/"
1321
+ opencode_branch_prefixes:
1322
+ - "opencode/"
1294
1323
 
1295
1324
  # Restrict reviews to PRs opened by these GitHub logins.
1296
1325
  # Auto-filled with your GitHub login by `crosscheck init` or first `crosscheck watch`.
@@ -1304,6 +1333,11 @@ routing:
1304
1333
  author_routes:
1305
1334
  your-github-login: claude # your PRs → treated as Claude-authored → Codex reviews
1306
1335
 
1336
+ # Reviewer for PRs crosscheck cannot attribute (origin: human). 'auto' = the
1337
+ # first authenticated vendor (codex, then claude, then opencode); an explicit
1338
+ # 'codex' | 'claude' | 'opencode' always uses that vendor; null = skip the PR.
1339
+ fallback_reviewer: auto
1340
+
1307
1341
  # ── Tunnel (watch mode only) ──────────────────────────────────────────────────
1308
1342
  # localhost.run (default) — SSH tunnel, zero install, URL changes on reconnect.
1309
1343
  # smee — stable relay via smee.io; events queued while offline.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanbased/crosscheck",
3
- "version": "1.6.1-beta.5",
3
+ "version": "1.7.0-beta.22",
4
4
  "description": "AI code review pipeline that turns agent-written PRs into merge-ready patches",
5
5
  "bin": {
6
6
  "crosscheck": "dist/cli.js",