@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.
- package/README.md +3 -1
- package/crosscheck.config.example.yml +20 -4
- package/dist/__tests__/detector.test.js +22 -0
- package/dist/__tests__/detector.test.js.map +1 -1
- package/dist/__tests__/onboard-preservation.test.js +27 -5
- package/dist/__tests__/onboard-preservation.test.js.map +1 -1
- package/dist/__tests__/optimize.test.js +18 -2
- package/dist/__tests__/optimize.test.js.map +1 -1
- package/dist/__tests__/review-models.test.js +5 -5
- package/dist/__tests__/review-models.test.js.map +1 -1
- package/dist/__tests__/review-strategy.test.js +10 -10
- package/dist/__tests__/review-strategy.test.js.map +1 -1
- package/dist/__tests__/runner.test.js +12 -2
- package/dist/__tests__/runner.test.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +14 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/issue.d.ts.map +1 -1
- package/dist/commands/issue.js +40 -16
- package/dist/commands/issue.js.map +1 -1
- package/dist/commands/onboard.d.ts +5 -4
- package/dist/commands/onboard.d.ts.map +1 -1
- package/dist/commands/onboard.js +126 -48
- package/dist/commands/onboard.js.map +1 -1
- package/dist/commands/optimize.d.ts +1 -1
- package/dist/commands/optimize.d.ts.map +1 -1
- package/dist/commands/optimize.js +53 -13
- package/dist/commands/optimize.js.map +1 -1
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/commands/review.js +9 -3
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/run.d.ts +2 -2
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +1 -1
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +12 -1
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/watch.d.ts.map +1 -1
- package/dist/commands/watch.js +6 -3
- package/dist/commands/watch.js.map +1 -1
- package/dist/config/review-model-tiers.json +9 -9
- package/dist/config/review-strategy.json +12 -12
- package/dist/config/schema.d.ts +102 -16
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +28 -3
- package/dist/config/schema.js.map +1 -1
- package/dist/github/client.js +1 -1
- package/dist/github/client.js.map +1 -1
- package/dist/github/detector.d.ts +2 -2
- package/dist/github/detector.d.ts.map +1 -1
- package/dist/github/detector.js +46 -11
- package/dist/github/detector.js.map +1 -1
- package/dist/lib/comment-bodies.d.ts.map +1 -1
- package/dist/lib/comment-bodies.js +4 -2
- package/dist/lib/comment-bodies.js.map +1 -1
- package/dist/lib/review-models.d.ts +4 -1
- package/dist/lib/review-models.d.ts.map +1 -1
- package/dist/lib/review-models.js +8 -0
- package/dist/lib/review-models.js.map +1 -1
- package/dist/lib/reviewer-error.d.ts.map +1 -1
- package/dist/lib/reviewer-error.js +12 -8
- package/dist/lib/reviewer-error.js.map +1 -1
- package/dist/lib/runner.d.ts +8 -7
- package/dist/lib/runner.d.ts.map +1 -1
- package/dist/lib/runner.js +71 -20
- package/dist/lib/runner.js.map +1 -1
- package/dist/lib/smart-switch.d.ts +7 -6
- package/dist/lib/smart-switch.d.ts.map +1 -1
- package/dist/lib/smart-switch.js +9 -2
- package/dist/lib/smart-switch.js.map +1 -1
- package/dist/lib/vendor.d.ts +2 -2
- package/dist/lib/vendor.d.ts.map +1 -1
- package/dist/lib/vendor.js +12 -2
- package/dist/lib/vendor.js.map +1 -1
- package/dist/lib/workflow.d.ts +8 -8
- package/dist/lib/workflow.js +1 -1
- package/dist/lib/workflow.js.map +1 -1
- package/dist/reviewers/fix.d.ts +6 -0
- package/dist/reviewers/fix.d.ts.map +1 -1
- package/dist/reviewers/fix.js +61 -0
- package/dist/reviewers/fix.js.map +1 -1
- package/dist/reviewers/opencode-env.d.ts +13 -0
- package/dist/reviewers/opencode-env.d.ts.map +1 -0
- package/dist/reviewers/opencode-env.js +52 -0
- package/dist/reviewers/opencode-env.js.map +1 -0
- package/dist/reviewers/opencode.d.ts +17 -0
- package/dist/reviewers/opencode.d.ts.map +1 -0
- package/dist/reviewers/opencode.js +293 -0
- package/dist/reviewers/opencode.js.map +1 -0
- package/get-started.md +38 -4
- package/package.json +1 -1
package/dist/lib/runner.js
CHANGED
|
@@ -7,7 +7,8 @@ import { filterStepsByTypes } from './repo-workflow.js';
|
|
|
7
7
|
import { vendorDisplayName } from '../lib/vendor.js';
|
|
8
8
|
import { runCodexReview } from '../reviewers/codex.js';
|
|
9
9
|
import { runClaudeReview } from '../reviewers/claude.js';
|
|
10
|
-
import {
|
|
10
|
+
import { runOpenCodeReview } from '../reviewers/opencode.js';
|
|
11
|
+
import { runFixStep, runCodexFixStep, runOpenCodeFixStep } from '../reviewers/fix.js';
|
|
11
12
|
import { runConflictResolveStep, findConflictedFiles } from '../reviewers/conflict-resolve.js';
|
|
12
13
|
import { parseVerdict, prependVerdictToComment, NULL_VERDICT_WARNING, applySeverityGate, SEVERITY_GATE_NOTE, DOC_ONLY_GATE_NOTE, detectInconclusiveReview, reviewHasNoFindings } from '../lib/verdict.js';
|
|
13
14
|
import { createGithubClient, postReviewComment, getLastCrossCheckCommentId, getLastCrossCheckReviewComment } from '../github/client.js';
|
|
@@ -22,9 +23,9 @@ import { acquireRemoteLock, releaseRemoteLock } from '../github/review-status.js
|
|
|
22
23
|
import { log as fileLog, logError, classifyError } from '../lib/logger.js';
|
|
23
24
|
import { buildCommitTrailers, parseAnnotation } from '../lib/annotation.js';
|
|
24
25
|
import { shaCovers } from '../lib/pr-workflow-state.js';
|
|
25
|
-
import { resolveClaudeModel, resolveCodexModel } from '../lib/review-models.js';
|
|
26
|
+
import { resolveClaudeModel, resolveCodexModel, resolveOpenCodeModel } from '../lib/review-models.js';
|
|
26
27
|
import { resolveReviewStrategy, escalate, clampToLevels, isDocOnlyChange } from './review-strategy.js';
|
|
27
|
-
import { CLAUDE_EFFORT_LEVELS, CODEX_EFFORT_LEVELS } from '../config/schema.js';
|
|
28
|
+
import { CLAUDE_EFFORT_LEVELS, CODEX_EFFORT_LEVELS, OPENCODE_EFFORT_LEVELS } from '../config/schema.js';
|
|
28
29
|
import { buildStepIdentityFields } from '../lib/event-fields.js';
|
|
29
30
|
import { planAutoFixDelivery, forceWithLeaseArgs, parseLsRemoteOid, isLeaseRejection, assessFixBranchOwnership, isInvalidBaseError } from '../lib/auto-fix-branch.js';
|
|
30
31
|
import { prOpenToVerdictMs } from '../lib/adoption.js';
|
|
@@ -254,34 +255,56 @@ function resolveReviewer(reviewer, origin, config, fallback) {
|
|
|
254
255
|
return 'claude';
|
|
255
256
|
if (origin === 'codex' && config.vendors.codex.enabled)
|
|
256
257
|
return 'codex';
|
|
258
|
+
if (origin === 'opencode' && config.vendors.opencode.enabled)
|
|
259
|
+
return 'opencode';
|
|
257
260
|
return fallback && config.vendors[fallback].enabled ? fallback : null;
|
|
258
261
|
}
|
|
259
262
|
if (reviewer === 'auto') {
|
|
260
263
|
if (origin === 'claude' && config.vendors.codex.enabled)
|
|
261
264
|
return 'codex';
|
|
265
|
+
if (origin === 'claude' && config.vendors.opencode.enabled)
|
|
266
|
+
return 'opencode';
|
|
262
267
|
if (origin === 'codex' && config.vendors.claude.enabled)
|
|
263
268
|
return 'claude';
|
|
269
|
+
if (origin === 'codex' && config.vendors.opencode.enabled)
|
|
270
|
+
return 'opencode';
|
|
271
|
+
if (origin === 'opencode' && config.vendors.claude.enabled)
|
|
272
|
+
return 'claude';
|
|
273
|
+
if (origin === 'opencode' && config.vendors.codex.enabled)
|
|
274
|
+
return 'codex';
|
|
264
275
|
if (config.vendors.codex.enabled)
|
|
265
276
|
return 'codex';
|
|
266
277
|
if (config.vendors.claude.enabled)
|
|
267
278
|
return 'claude';
|
|
279
|
+
if (config.vendors.opencode.enabled)
|
|
280
|
+
return 'opencode';
|
|
268
281
|
return null;
|
|
269
282
|
}
|
|
270
283
|
if (reviewer === 'claude')
|
|
271
284
|
return config.vendors.claude.enabled ? 'claude' : (fallback && config.vendors[fallback].enabled ? fallback : null);
|
|
272
285
|
if (reviewer === 'codex')
|
|
273
286
|
return config.vendors.codex.enabled ? 'codex' : (fallback && config.vendors[fallback].enabled ? fallback : null);
|
|
287
|
+
if (reviewer === 'opencode')
|
|
288
|
+
return config.vendors.opencode.enabled ? 'opencode' : (fallback && config.vendors[fallback].enabled ? fallback : null);
|
|
274
289
|
return null;
|
|
275
290
|
}
|
|
276
291
|
function supportsStep(vendor, stepType) {
|
|
277
292
|
if (stepType === 'review' || stepType === 'recheck' || stepType === 'fix')
|
|
278
293
|
return true;
|
|
279
|
-
// Conflict resolution is Claude-only until Codex conflict resolution exists.
|
|
294
|
+
// Conflict resolution is Claude-only until Codex/OpenCode conflict resolution exists.
|
|
280
295
|
return vendor === 'claude';
|
|
281
296
|
}
|
|
282
297
|
function resolveLimitFallbackVendor(failedVendor, stepType, config) {
|
|
283
|
-
const
|
|
284
|
-
|
|
298
|
+
const candidates = failedVendor === 'claude'
|
|
299
|
+
? ['codex', 'opencode']
|
|
300
|
+
: failedVendor === 'codex'
|
|
301
|
+
? ['claude', 'opencode']
|
|
302
|
+
: ['claude', 'codex'];
|
|
303
|
+
for (const candidate of candidates) {
|
|
304
|
+
if (config.vendors[candidate].enabled && supportsStep(candidate, stepType))
|
|
305
|
+
return candidate;
|
|
306
|
+
}
|
|
307
|
+
return null;
|
|
285
308
|
}
|
|
286
309
|
function notifyVendorFailure(ctx, failedVendor, fallbackVendor, reason, stepName) {
|
|
287
310
|
const handler = ctx.onVendorFailure ?? ctx.onVendorLimit;
|
|
@@ -327,7 +350,7 @@ function resolveStepVendor(stepType, stepReviewer, origin, config, fallback) {
|
|
|
327
350
|
// reviewer: auto, or routing.fallback_reviewer is an operator decision, left as
|
|
328
351
|
// written so the caller can report the precise unsupported-step skip.
|
|
329
352
|
if (stepReviewer === 'origin' &&
|
|
330
|
-
(origin === 'claude' || origin === 'codex') &&
|
|
353
|
+
(origin === 'claude' || origin === 'codex' || origin === 'opencode') &&
|
|
331
354
|
vendor !== null &&
|
|
332
355
|
!supportsStep(vendor, stepType)) {
|
|
333
356
|
const capable = vendor === 'claude' ? 'codex' : 'claude';
|
|
@@ -344,11 +367,13 @@ function resolveStepVendor(stepType, stepReviewer, origin, config, fallback) {
|
|
|
344
367
|
humanFallback = config.vendors.claude.enabled ? 'claude' : null;
|
|
345
368
|
else if (fb === 'codex')
|
|
346
369
|
humanFallback = config.vendors.codex.enabled ? 'codex' : null;
|
|
370
|
+
else if (fb === 'opencode')
|
|
371
|
+
humanFallback = config.vendors.opencode.enabled ? 'opencode' : null;
|
|
347
372
|
else if (fb !== null) {
|
|
348
|
-
// 'auto': prefer codex then claude, same as resolveReviewer's
|
|
349
|
-
// narrowed to vendors that support this step type.
|
|
373
|
+
// 'auto': prefer codex then claude then opencode, same as resolveReviewer's
|
|
374
|
+
// auto path, narrowed to vendors that support this step type.
|
|
350
375
|
const usable = (v) => config.vendors[v].enabled && supportsStep(v, stepType);
|
|
351
|
-
humanFallback = usable('codex') ? 'codex' : usable('claude') ? 'claude' : null;
|
|
376
|
+
humanFallback = usable('codex') ? 'codex' : usable('claude') ? 'claude' : usable('opencode') ? 'opencode' : null;
|
|
352
377
|
}
|
|
353
378
|
if (!humanFallback)
|
|
354
379
|
return { vendor: null, usedHumanFallback: false };
|
|
@@ -558,6 +583,7 @@ export function resolveRoundExecution(config, strategy, round) {
|
|
|
558
583
|
quality: config.quality,
|
|
559
584
|
claudeVendor: config.vendors.claude,
|
|
560
585
|
codexVendor: config.vendors.codex,
|
|
586
|
+
opencodeVendor: config.vendors.opencode,
|
|
561
587
|
roundConfig: config,
|
|
562
588
|
escalated: false,
|
|
563
589
|
};
|
|
@@ -575,17 +601,22 @@ export function resolveRoundExecution(config, strategy, round) {
|
|
|
575
601
|
if (config.vendors.codex.enabled) {
|
|
576
602
|
lanes.push({ model: resolveCodexModel(baseQuality, config.vendors.codex), accepted: CODEX_EFFORT_LEVELS });
|
|
577
603
|
}
|
|
604
|
+
if (config.vendors.opencode.enabled) {
|
|
605
|
+
lanes.push({ model: resolveOpenCodeModel(baseQuality, config.vendors.opencode) ?? 'default', accepted: OPENCODE_EFFORT_LEVELS });
|
|
606
|
+
}
|
|
578
607
|
const escalated = escalate({ tier: strategy.tier ?? config.quality.tier, effort: strategy.effort }, round, lanes);
|
|
579
608
|
const roundStrategy = { ...strategy, tier: escalated.tier, effort: escalated.effort };
|
|
580
609
|
const quality = strategyQuality(config.quality, roundStrategy);
|
|
581
610
|
const claudeVendor = strategyVendor(config.vendors.claude, roundStrategy, CLAUDE_EFFORT_LEVELS);
|
|
582
611
|
const codexVendor = strategyVendor(config.vendors.codex, roundStrategy, CODEX_EFFORT_LEVELS);
|
|
612
|
+
const opencodeVendor = strategyVendor(config.vendors.opencode, roundStrategy, OPENCODE_EFFORT_LEVELS);
|
|
583
613
|
return {
|
|
584
614
|
strategy: roundStrategy,
|
|
585
615
|
quality,
|
|
586
616
|
claudeVendor,
|
|
587
617
|
codexVendor,
|
|
588
|
-
|
|
618
|
+
opencodeVendor,
|
|
619
|
+
roundConfig: { ...config, quality, vendors: { ...config.vendors, claude: claudeVendor, codex: codexVendor, opencode: opencodeVendor } },
|
|
589
620
|
escalated: escalated.tier !== strategy.tier || escalated.effort !== strategy.effort,
|
|
590
621
|
};
|
|
591
622
|
}
|
|
@@ -847,7 +878,7 @@ export async function runWorkflow(ctx) {
|
|
|
847
878
|
// Above the try, and above emitPRComplexity, because both the complexity event
|
|
848
879
|
// and workflow_complete report the tier that ran: an escalated round reporting
|
|
849
880
|
// the base class tier is the same defect as a comment citing one.
|
|
850
|
-
const { strategy: roundStrategy, quality, claudeVendor, codexVendor, roundConfig, escalated } = resolveRoundExecution(config, strategy, ctx.round ?? 1);
|
|
881
|
+
const { strategy: roundStrategy, quality, claudeVendor, codexVendor, opencodeVendor, roundConfig, escalated } = resolveRoundExecution(config, strategy, ctx.round ?? 1);
|
|
851
882
|
emitPRComplexity(ctx, triggerField, quality.tier);
|
|
852
883
|
try {
|
|
853
884
|
// Inside the try so a preflight failure still reaches the completion handler in
|
|
@@ -869,11 +900,12 @@ export async function runWorkflow(ctx) {
|
|
|
869
900
|
const appliedEffort = [...new Set([
|
|
870
901
|
...(config.vendors.claude.enabled ? [claudeVendor.effort] : []),
|
|
871
902
|
...(config.vendors.codex.enabled ? [codexVendor.effort] : []),
|
|
903
|
+
...(config.vendors.opencode.enabled ? [opencodeVendor.effort] : []),
|
|
872
904
|
])].join('/');
|
|
873
905
|
const escalatedNote = escalated ? ` · round ${ctx.round} escalated` : '';
|
|
874
906
|
log(chalk.dim(` strategy v${strategy.version}: ${strategy.classId} → ${roundStrategy.tier ?? 'skip'} tier${appliedEffort ? ` (${appliedEffort})` : ''}${escalatedNote}`));
|
|
875
907
|
if (escalatedNote) {
|
|
876
|
-
fileLog({ level: 'info', event: 'strategy_escalated', repo: `${owner}/${repoName}`, pr: prNumber, round: ctx.round, from_tier: strategy.tier, to_tier: roundStrategy.tier, from_effort: strategy.effort, to_effort: roundStrategy.effort, applied_effort_claude: config.vendors.claude.enabled ? claudeVendor.effort : null, applied_effort_codex: config.vendors.codex.enabled ? codexVendor.effort : null, strategy_version: strategy.version });
|
|
908
|
+
fileLog({ level: 'info', event: 'strategy_escalated', repo: `${owner}/${repoName}`, pr: prNumber, round: ctx.round, from_tier: strategy.tier, to_tier: roundStrategy.tier, from_effort: strategy.effort, to_effort: roundStrategy.effort, applied_effort_claude: config.vendors.claude.enabled ? claudeVendor.effort : null, applied_effort_codex: config.vendors.codex.enabled ? codexVendor.effort : null, applied_effort_opencode: config.vendors.opencode.enabled ? opencodeVendor.effort : null, strategy_version: strategy.version });
|
|
877
909
|
}
|
|
878
910
|
}
|
|
879
911
|
for (const step of steps) {
|
|
@@ -978,6 +1010,12 @@ export async function runWorkflow(ctx) {
|
|
|
978
1010
|
inputTokens = undefined;
|
|
979
1011
|
outputTokens = undefined;
|
|
980
1012
|
}
|
|
1013
|
+
else if (candidate === 'opencode') {
|
|
1014
|
+
;
|
|
1015
|
+
({ review: rawReview, tokensUsed, model, effort, retried } = await runOpenCodeReview(tmpDir, pr.base.ref, pr.title, quality, opencodeVendor, memoryPlan?.instructions ?? step.instructions, undefined, ctx.overrideTimeoutMs ?? vendorTimeoutMs(config.vendors.opencode.timeout_sec), log, reviewContext, skillSession));
|
|
1016
|
+
inputTokens = undefined;
|
|
1017
|
+
outputTokens = undefined;
|
|
1018
|
+
}
|
|
981
1019
|
else {
|
|
982
1020
|
;
|
|
983
1021
|
({ review: rawReview, tokensUsed, inputTokens, outputTokens, model, effort, retried } = await runClaudeReview(tmpDir, pr.base.ref, pr.title, quality, claudeVendor, config.budget.per_review_usd, memoryPlan?.instructions ?? step.instructions, undefined, ctx.overrideTimeoutMs ?? vendorTimeoutMs(config.vendors.claude.timeout_sec), !!ctx.roundMode, log, reviewContext, skillSession));
|
|
@@ -1160,7 +1198,7 @@ export async function runWorkflow(ctx) {
|
|
|
1160
1198
|
// Withheld when an explicit vendors.*.model overrode the tier map:
|
|
1161
1199
|
// citing a tier the run did not use would assert a routing decision
|
|
1162
1200
|
// that never happened.
|
|
1163
|
-
strategyCitation(reviewer === 'codex' ? config.vendors.codex : config.vendors.claude, strategy, roundStrategy, model));
|
|
1201
|
+
strategyCitation(reviewer === 'codex' ? config.vendors.codex : reviewer === 'opencode' ? config.vendors.opencode : config.vendors.claude, strategy, roundStrategy, model));
|
|
1164
1202
|
if (memoryPlan && structured?.snapshot) {
|
|
1165
1203
|
// The review is already posted; a memory write failure only costs the next review its delta.
|
|
1166
1204
|
try {
|
|
@@ -1313,6 +1351,7 @@ export async function runWorkflow(ctx) {
|
|
|
1313
1351
|
// away.
|
|
1314
1352
|
const claudeFixModel = resolveClaudeModel(quality, claudeVendor);
|
|
1315
1353
|
const codexFixModel = resolveCodexModel(quality, codexVendor);
|
|
1354
|
+
const opencodeFixModel = resolveOpenCodeModel(quality, opencodeVendor);
|
|
1316
1355
|
// Guard: don't push more than MAX_CROSSCHECK_COMMITS per PR.
|
|
1317
1356
|
// Scope to commits ahead of base so long-lived branches (e.g. staging)
|
|
1318
1357
|
// don't count [crosscheck] commits from previously merged PRs.
|
|
@@ -1345,6 +1384,9 @@ export async function runWorkflow(ctx) {
|
|
|
1345
1384
|
if (v === 'codex') {
|
|
1346
1385
|
return runCodexFixStep(tmpDir, pr.base.ref, pr.title, reviewCommentBody, step.instructions ?? '', codexFixModel, ctx.overrideTimeoutMs ?? vendorTimeoutMs(config.vendors.codex.timeout_sec) ?? tierMs, skillSession, codexVendor.effort, config.skills.codex_full_access, fixHumanFeedback);
|
|
1347
1386
|
}
|
|
1387
|
+
if (v === 'opencode') {
|
|
1388
|
+
return runOpenCodeFixStep(tmpDir, pr.base.ref, pr.title, reviewCommentBody, step.instructions ?? '', opencodeFixModel, ctx.overrideTimeoutMs ?? vendorTimeoutMs(config.vendors.opencode.timeout_sec) ?? tierMs, skillSession, opencodeVendor.effort, fixHumanFeedback);
|
|
1389
|
+
}
|
|
1348
1390
|
// roundConfig, not config: runFixStep reads vendors.claude.effort and
|
|
1349
1391
|
// quality.tier out of it, and both must be this round's values.
|
|
1350
1392
|
return runFixStep(tmpDir, pr.base.ref, pr.title, reviewCommentBody, step.instructions ?? '', roundConfig, claudeFixModel, ctx.overrideTimeoutMs ?? vendorTimeoutMs(config.vendors.claude.timeout_sec) ?? tierMs, skillSession, fixHumanFeedback);
|
|
@@ -1411,7 +1453,7 @@ export async function runWorkflow(ctx) {
|
|
|
1411
1453
|
const fixAttributionFooter = () => buildAttributionFooter({
|
|
1412
1454
|
action: 'Fixed',
|
|
1413
1455
|
vendor: activeVendor,
|
|
1414
|
-
model: activeVendor === 'codex' ? codexFixModel : claudeFixModel,
|
|
1456
|
+
model: activeVendor === 'codex' ? codexFixModel : activeVendor === 'opencode' ? opencodeFixModel : claudeFixModel,
|
|
1415
1457
|
effort: fixEffort,
|
|
1416
1458
|
skills: activatedSkills,
|
|
1417
1459
|
});
|
|
@@ -1426,7 +1468,7 @@ export async function runWorkflow(ctx) {
|
|
|
1426
1468
|
body: buildFixFailedCommentBody({
|
|
1427
1469
|
prUrl: pr.html_url,
|
|
1428
1470
|
vendor: activeVendor,
|
|
1429
|
-
model: activeVendor === 'codex' ? codexFixModel : claudeFixModel,
|
|
1471
|
+
model: activeVendor === 'codex' ? codexFixModel : activeVendor === 'opencode' ? opencodeFixModel : claudeFixModel,
|
|
1430
1472
|
effort: fixEffort,
|
|
1431
1473
|
skills: activatedSkills,
|
|
1432
1474
|
}),
|
|
@@ -1460,7 +1502,7 @@ export async function runWorkflow(ctx) {
|
|
|
1460
1502
|
const deliveryMode = config.post_review.auto_fix.delivery.mode;
|
|
1461
1503
|
const landing = resolveFixLanding(deliveryMode);
|
|
1462
1504
|
if (landing === 'branch') {
|
|
1463
|
-
const fixModel = activeVendor === 'codex' ? codexFixModel : claudeFixModel;
|
|
1505
|
+
const fixModel = activeVendor === 'codex' ? codexFixModel : activeVendor === 'opencode' ? (opencodeFixModel ?? 'default') : claudeFixModel;
|
|
1464
1506
|
runGitWithoutHooks(tmpDir, ['add', '-A']);
|
|
1465
1507
|
runGitWithoutHooks(tmpDir, [
|
|
1466
1508
|
'commit',
|
|
@@ -1512,7 +1554,7 @@ export async function runWorkflow(ctx) {
|
|
|
1512
1554
|
changedFiles: fixChangedFiles,
|
|
1513
1555
|
vendor: activeVendor,
|
|
1514
1556
|
reviewCommentBody,
|
|
1515
|
-
model: activeVendor === 'codex' ? codexFixModel : claudeFixModel,
|
|
1557
|
+
model: activeVendor === 'codex' ? codexFixModel : activeVendor === 'opencode' ? opencodeFixModel : claudeFixModel,
|
|
1516
1558
|
effort: fixEffort,
|
|
1517
1559
|
skills: activatedSkills,
|
|
1518
1560
|
});
|
|
@@ -1525,7 +1567,7 @@ export async function runWorkflow(ctx) {
|
|
|
1525
1567
|
results[step.name] = { applied_count: appliedCount, tokens_used: fixTokensUsed, vendor: activeVendor };
|
|
1526
1568
|
}
|
|
1527
1569
|
else if (landing === 'branch-then-separate-pr') {
|
|
1528
|
-
const fixModel = activeVendor === 'codex' ? codexFixModel : claudeFixModel;
|
|
1570
|
+
const fixModel = activeVendor === 'codex' ? codexFixModel : activeVendor === 'opencode' ? (opencodeFixModel ?? 'default') : claudeFixModel;
|
|
1529
1571
|
// Commit the fix on the PR's own branch (already checked out in tmpDir) and
|
|
1530
1572
|
// try to push it there, so the fix, recheck, and approval all stay on the
|
|
1531
1573
|
// original PR. Only when that push can't land — e.g. the PR was merged and
|
|
@@ -1585,7 +1627,7 @@ export async function runWorkflow(ctx) {
|
|
|
1585
1627
|
changedFiles: fixChangedFiles,
|
|
1586
1628
|
vendor: activeVendor,
|
|
1587
1629
|
reviewCommentBody,
|
|
1588
|
-
model: activeVendor === 'codex' ? codexFixModel : claudeFixModel,
|
|
1630
|
+
model: activeVendor === 'codex' ? codexFixModel : activeVendor === 'opencode' ? opencodeFixModel : claudeFixModel,
|
|
1589
1631
|
effort: fixEffort,
|
|
1590
1632
|
skills: activatedSkills,
|
|
1591
1633
|
});
|
|
@@ -1920,6 +1962,15 @@ export async function runWorkflow(ctx) {
|
|
|
1920
1962
|
skipConflictResolve('codex_conflict_resolve_unsupported');
|
|
1921
1963
|
continue;
|
|
1922
1964
|
}
|
|
1965
|
+
if (vendor === 'opencode') {
|
|
1966
|
+
try {
|
|
1967
|
+
runGitWithoutHooks(tmpDir, ['merge', '--abort']);
|
|
1968
|
+
}
|
|
1969
|
+
catch { /* ignore */ }
|
|
1970
|
+
;
|
|
1971
|
+
skipConflictResolve('opencode_conflict_resolve_unsupported');
|
|
1972
|
+
continue;
|
|
1973
|
+
}
|
|
1923
1974
|
// Conflict-resolve is mechanical text surgery bounded by the markers —
|
|
1924
1975
|
// measured at 37s against ~643s for a review — so it always runs fast.
|
|
1925
1976
|
const conflictResolveModel = resolveClaudeModel({ ...config.quality, tier: config.quality.mode === 'smart' ? 'fast' : config.quality.tier }, config.vendors.claude);
|