Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

model-routing.test.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

model-routing.test.tsBlame118 lines · 1 contributor
bc519aeClaude1/**
2 * Tests for the task → model router in src/lib/ai-client.ts.
3 *
4 * Routing policy under test:
5 * - Only the light, human-reviewed tasks (commit-message, issue-triage,
6 * pr-triage, label-suggest) may run on Haiku.
7 * - Everything that writes or judges code — and any unknown task —
8 * defaults to Sonnet.
9 * - AI_FORCE_SONNET=1 is an instant kill-switch that forces Sonnet for
10 * every task, read from the environment at call time.
11 */
12
13import { describe, it, expect, afterEach } from "bun:test";
14import {
15 modelForTask,
16 MODEL_SONNET,
17 MODEL_HAIKU,
18 type AiTask,
19} from "../lib/ai-client";
20
21const ORIGINAL_FORCE_SONNET = process.env.AI_FORCE_SONNET;
22
23afterEach(() => {
24 // Restore whatever the environment had before the suite ran.
25 if (ORIGINAL_FORCE_SONNET === undefined) {
26 delete process.env.AI_FORCE_SONNET;
27 } else {
28 process.env.AI_FORCE_SONNET = ORIGINAL_FORCE_SONNET;
29 }
30});
31
32// ──────────────────────────── Haiku allowlist ────────────────────────────
33
34describe("modelForTask — Haiku allowlist", () => {
35 const haikuTasks: AiTask[] = [
36 "commit-message",
37 "issue-triage",
38 "pr-triage",
39 "label-suggest",
40 ];
41
42 for (const task of haikuTasks) {
43 it(`routes ${task} to Haiku`, () => {
44 delete process.env.AI_FORCE_SONNET;
45 expect(modelForTask(task)).toBe(MODEL_HAIKU);
46 });
47 }
48});
49
50// ──────────────────────────── Sonnet defaults ────────────────────────────
51
52describe("modelForTask — code-critical and doc tasks stay on Sonnet", () => {
53 const sonnetTasks: AiTask[] = [
54 "code-review",
55 "code-completion",
56 "spec-to-pr",
57 "ci-heal",
58 "pr-summary",
59 "changelog",
60 ];
61
62 for (const task of sonnetTasks) {
63 it(`routes ${task} to Sonnet`, () => {
64 delete process.env.AI_FORCE_SONNET;
65 expect(modelForTask(task)).toBe(MODEL_SONNET);
66 });
67 }
68
69 it("defaults unknown tasks to Sonnet (never silently downgrades)", () => {
70 delete process.env.AI_FORCE_SONNET;
71 // Simulate a typo'd / future task name slipping past the type system.
72 expect(modelForTask("some-future-task" as AiTask)).toBe(MODEL_SONNET);
73 expect(modelForTask("" as AiTask)).toBe(MODEL_SONNET);
74 });
75});
76
77// ──────────────────────────── kill-switch ────────────────────────────
78
79describe("modelForTask — AI_FORCE_SONNET kill-switch", () => {
80 const allTasks: AiTask[] = [
81 "commit-message",
82 "issue-triage",
83 "pr-triage",
84 "label-suggest",
85 "code-review",
86 "code-completion",
87 "spec-to-pr",
88 "ci-heal",
89 "pr-summary",
90 "changelog",
91 ];
92
93 it("forces Sonnet for every task when AI_FORCE_SONNET=1", () => {
94 process.env.AI_FORCE_SONNET = "1";
95 for (const task of allTasks) {
96 expect(modelForTask(task)).toBe(MODEL_SONNET);
97 }
98 });
99
100 it("is read at call time — flipping the env var takes effect immediately", () => {
101 delete process.env.AI_FORCE_SONNET;
102 expect(modelForTask("commit-message")).toBe(MODEL_HAIKU);
103
104 process.env.AI_FORCE_SONNET = "1";
105 expect(modelForTask("commit-message")).toBe(MODEL_SONNET);
106
107 delete process.env.AI_FORCE_SONNET;
108 expect(modelForTask("commit-message")).toBe(MODEL_HAIKU);
109 });
110
111 it("only the exact value \"1\" activates the kill-switch", () => {
112 process.env.AI_FORCE_SONNET = "0";
113 expect(modelForTask("commit-message")).toBe(MODEL_HAIKU);
114
115 process.env.AI_FORCE_SONNET = "";
116 expect(modelForTask("commit-message")).toBe(MODEL_HAIKU);
117 });
118});