feat(FN-2662): enforce model override fallback hierarchy

- Fix triage planning model resolution to fall back through project/global planning settings and default overrides
- Fix reviewer model selection to honor validator-specific settings before default provider/model overrides
- Update merger model resolution to apply default override fallback and align shared task setting types/executor flow
- Add regression coverage for triage, reviewer, and merger fallback behavior and update settings hierarchy documentation
This commit is contained in:
Fusion
2026-04-27 02:02:01 -07:00
committed by gsxdsm
parent d4a8a45904
commit 788de7dd8a
9 changed files with 288 additions and 24 deletions

View File

@@ -6331,7 +6331,7 @@ describe("aiMergeTask — in-merge verification fix", () => {
return Buffer.from("");
});
mockedCreateFnAgent.mockImplementation(async (opts: any) => {
mockedCreateFnAgent.mockImplementation(async (_opts: any) => {
return {
session: {
prompt: vi.fn().mockResolvedValue(undefined),
@@ -6362,6 +6362,61 @@ describe("aiMergeTask — in-merge verification fix", () => {
expect(fixAgentCall[0].defaultModelId).toBe("claude-sonnet-4-5");
});
it("uses project default override for merge agent model", async () => {
mockedExecSync.mockImplementation((cmd: any) => {
const cmdStr = String(cmd);
if (cmdStr.includes("rev-parse --verify")) return Buffer.from("abc123");
if (cmdStr.includes("git log")) return "- feat: something" as any;
if (cmdStr.includes("merge-base")) return Buffer.from("abc123");
if (cmdStr.includes("git diff") && cmdStr.includes("--stat")) return "1 file changed" as any;
if (cmdStr.includes("merge --squash")) return Buffer.from("");
if (cmdStr.includes("vitest run")) {
const err = new Error("Test failed") as any;
err.status = 1;
err.stdout = "";
err.stderr = "";
throw err;
}
if (cmdStr.includes("diff --cached --quiet")) return "1" as any;
if (cmdStr.includes("diff --cached")) return "" as any;
if (cmdStr.includes("branch -d") || cmdStr.includes("branch -D")) return Buffer.from("");
if (cmdStr.includes("worktree remove")) return Buffer.from("");
if (cmdStr === "git rev-parse HEAD" || cmdStr.startsWith("git rev-parse HEAD ")) return "mergedcommit123";
return Buffer.from("");
});
mockedCreateFnAgent.mockImplementation(async (_opts: any) => {
return {
session: {
prompt: vi.fn().mockResolvedValue(undefined),
dispose: vi.fn(),
},
} as any;
});
const store = createMockStore(
{ id: "FN-050", worktree: "/tmp/root/.worktrees/KB-050" },
[{ id: "FN-050", worktree: "/tmp/root/.worktrees/KB-050", column: "in-review" } as Task],
);
(store.getSettings as ReturnType<typeof vi.fn>).mockResolvedValue({
...DEFAULT_SETTINGS,
testCommand: "vitest run",
verificationFixRetries: 0,
defaultProvider: "anthropic",
defaultModelId: "claude-sonnet-4-5",
defaultProviderOverride: "openai",
defaultModelIdOverride: "gpt-4o",
});
await expect(aiMergeTask(store, "/tmp/root", "FN-050")).rejects.toMatchObject({
name: "VerificationError",
});
const mergeAgentCall = mockedCreateFnAgent.mock.calls[0];
expect(mergeAgentCall[0].defaultProvider).toBe("openai");
expect(mergeAgentCall[0].defaultModelId).toBe("gpt-4o");
});
it("fix agent session is disposed", async () => {
const disposeMock = vi.fn();