diff --git a/.changeset/friendly-reviewers-fallback.md b/.changeset/friendly-reviewers-fallback.md new file mode 100644 index 0000000000..a5efe10946 --- /dev/null +++ b/.changeset/friendly-reviewers-fallback.md @@ -0,0 +1,5 @@ +--- +"@runfusion/fusion": patch +--- + +Pass project fallback model settings into triage spec reviewer sessions so global default overrides are honored during review. diff --git a/packages/engine/src/__tests__/triage.test.ts b/packages/engine/src/__tests__/triage.test.ts index 56731468fd..a8a4a59ca0 100644 --- a/packages/engine/src/__tests__/triage.test.ts +++ b/packages/engine/src/__tests__/triage.test.ts @@ -1010,6 +1010,67 @@ describe("fast-mode triage", () => { } }); + it("threads global fallback model settings into spec reviewer sessions", async () => { + const rootDir = await createTriageFixtureRoot("fusion-triage-review-fallback-"); + try { + const taskId = "FN-REVIEW-FALLBACK"; + await mkdir(join(rootDir, ".fusion", "tasks", taskId), { recursive: true }); + await writeFile( + join(rootDir, ".fusion", "tasks", taskId, "PROMPT.md"), + "# Task\n\n## Mission\n\nDo the work.\n\n## Steps\n\n### Step 0: Implement\n\nShip it.", + ); + mockReviewStep.mockResolvedValue({ verdict: "APPROVE", review: "ok", summary: "ok" }); + + const store = createMockStore({ + getTask: vi.fn().mockResolvedValue({ ...mockTaskDetail, id: taskId, comments: [] }), + getSettings: vi.fn().mockResolvedValue({ + maxConcurrent: 2, + maxWorktrees: 4, + pollIntervalMs: 10000, + groupOverlappingFiles: false, + autoMerge: true, + defaultProvider: "anthropic", + defaultModelId: "claude-opus-4-8", + defaultProviderOverride: "openai-codex", + defaultModelIdOverride: "gpt-5.5", + fallbackProvider: "openai-codex", + fallbackModelId: "gpt-5.5", + memoryEnabled: false, + agentPrompts: { roleAssignments: { reviewer: "custom-reviewer" } }, + } as Settings), + }); + const processor = new TriageProcessor(store, rootDir); + const tool = (processor as any).createReviewSpecTool( + taskId, + `.fusion/tasks/${taskId}/PROMPT.md`, + { current: null }, + { current: null }, + { current: null }, + { current: "" }, + {}, + false, + ); + + await tool.execute({}); + + const reviewOptions = mockReviewStep.mock.calls[0]?.[7]; + expect(reviewOptions).toMatchObject({ + projectDefaultOverrideProvider: "openai-codex", + projectDefaultOverrideModelId: "gpt-5.5", + fallbackProvider: "openai-codex", + fallbackModelId: "gpt-5.5", + agentPrompts: { roleAssignments: { reviewer: "custom-reviewer" } }, + }); + expect(reviewOptions.settings).toMatchObject({ + memoryEnabled: false, + agentPrompts: { roleAssignments: { reviewer: "custom-reviewer" } }, + }); + } finally { + mockReviewStep.mockReset(); + await cleanupTriageFixtureRoot(rootDir); + } + }); + it("passes post-session gate in fast mode after fn_review_spec auto-approval", async () => { const rootDir = await createTriageFixtureRoot("fusion-triage-fast-gate-"); try { diff --git a/packages/engine/src/triage.ts b/packages/engine/src/triage.ts index b62a246bf0..3e7eee5d9f 100644 --- a/packages/engine/src/triage.ts +++ b/packages/engine/src/triage.ts @@ -1890,6 +1890,10 @@ export class TriageProcessor { // Project-level validator fallback projectValidatorFallbackProvider: currentSettings.validatorFallbackProvider, projectValidatorFallbackModelId: currentSettings.validatorFallbackModelId, + // FNXC:SpecReviewerFallback 2026-06-23-08:50: + // Spec review must inherit global/default fallback reviewer model settings when no validator-specific fallback is configured, plus the project settings/prompt payload that reviewer sessions use for memory and custom prompt behavior. + fallbackProvider: currentSettings.fallbackProvider, + fallbackModelId: currentSettings.fallbackModelId, // Global validator lane globalValidatorProvider: currentSettings.validatorGlobalProvider, globalValidatorModelId: currentSettings.validatorGlobalModelId, @@ -1901,8 +1905,10 @@ export class TriageProcessor { taskId, task: currentDetail, userComments: currentUserComments.length > 0 ? currentUserComments : undefined, + agentPrompts: currentSettings.agentPrompts, agentStore: this.options.agentStore, rootDir, + settings: currentSettings, // Track the spec reviewer's session under this task so it's // disposed alongside the main triage session on global pause. onSessionCreated: (s) => this.registerSubagentSession(taskId, s),