Nieuwe centrale resolver `resolveJobConfig(job, product, task?)` die per ClaudeJob bepaalt welk model + thinking-budget + permission-mode + max_turns + allowed_tools de worker moet gebruiken. Override-cascade (eerste match wint): task.requires_opus → job.requested_* → product.preferred_* → kind-default Kind-defaults: IDEA_GRILL sonnet-4-6 thinking 12k plan IDEA_MAKE_PLAN opus-4-7 thinking 24k plan PLAN_CHAT sonnet-4-6 thinking 6k plan (max 5 turns) TASK_IMPLEMENTATION sonnet-4-6 thinking 6k bypassPermissions SPRINT_IMPLEMENTATION sonnet-4-6 thinking 6k bypassPermissions 19 unit tests (alle 5 kinds × cascade-niveaus). Geen externe deps. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
97 lines
3.9 KiB
TypeScript
97 lines
3.9 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import { getKindDefault, resolveJobConfig } from '../src/lib/job-config.js'
|
|
|
|
const KIND_EXPECTED = {
|
|
IDEA_GRILL: { model: 'claude-sonnet-4-6', thinking_budget: 12000, permission_mode: 'plan', max_turns: 15 },
|
|
IDEA_MAKE_PLAN: { model: 'claude-opus-4-7', thinking_budget: 24000, permission_mode: 'plan', max_turns: 20 },
|
|
PLAN_CHAT: { model: 'claude-sonnet-4-6', thinking_budget: 6000, permission_mode: 'plan', max_turns: 5 },
|
|
TASK_IMPLEMENTATION: { model: 'claude-sonnet-4-6', thinking_budget: 6000, permission_mode: 'bypassPermissions', max_turns: 50 },
|
|
SPRINT_IMPLEMENTATION: { model: 'claude-sonnet-4-6', thinking_budget: 6000, permission_mode: 'bypassPermissions', max_turns: null },
|
|
} as const
|
|
|
|
describe('getKindDefault', () => {
|
|
for (const [kind, expected] of Object.entries(KIND_EXPECTED)) {
|
|
it(`returnt de juiste defaults voor ${kind}`, () => {
|
|
const cfg = getKindDefault(kind)
|
|
expect(cfg.model).toBe(expected.model)
|
|
expect(cfg.thinking_budget).toBe(expected.thinking_budget)
|
|
expect(cfg.permission_mode).toBe(expected.permission_mode)
|
|
expect(cfg.max_turns).toBe(expected.max_turns)
|
|
})
|
|
}
|
|
|
|
it('valt terug op een veilige fallback voor onbekende kinds', () => {
|
|
const cfg = getKindDefault('SOMETHING_NEW')
|
|
expect(cfg.model).toBe('claude-sonnet-4-6')
|
|
expect(cfg.permission_mode).toBe('default')
|
|
})
|
|
})
|
|
|
|
describe('resolveJobConfig — geen overrides', () => {
|
|
for (const kind of Object.keys(KIND_EXPECTED)) {
|
|
it(`returnt kind-default voor ${kind} zonder overrides`, () => {
|
|
const cfg = resolveJobConfig({ kind }, {})
|
|
expect(cfg).toEqual(getKindDefault(kind))
|
|
})
|
|
}
|
|
})
|
|
|
|
describe('resolveJobConfig — cascade', () => {
|
|
it('product.preferred_model overrult kind-default', () => {
|
|
const cfg = resolveJobConfig({ kind: 'TASK_IMPLEMENTATION' }, { preferred_model: 'claude-haiku-4-5-20251001' })
|
|
expect(cfg.model).toBe('claude-haiku-4-5-20251001')
|
|
})
|
|
|
|
it('job.requested_model overrult product.preferred_model', () => {
|
|
const cfg = resolveJobConfig(
|
|
{ kind: 'TASK_IMPLEMENTATION', requested_model: 'claude-opus-4-7' },
|
|
{ preferred_model: 'claude-haiku-4-5-20251001' },
|
|
)
|
|
expect(cfg.model).toBe('claude-opus-4-7')
|
|
})
|
|
|
|
it('task.requires_opus overrult product.preferred_model', () => {
|
|
const cfg = resolveJobConfig(
|
|
{ kind: 'TASK_IMPLEMENTATION' },
|
|
{ preferred_model: 'claude-sonnet-4-6' },
|
|
{ requires_opus: true },
|
|
)
|
|
expect(cfg.model).toBe('claude-opus-4-7')
|
|
})
|
|
|
|
it('task.requires_opus overrult ook job.requested_model = haiku', () => {
|
|
const cfg = resolveJobConfig(
|
|
{ kind: 'TASK_IMPLEMENTATION', requested_model: 'claude-haiku-4-5-20251001' },
|
|
{},
|
|
{ requires_opus: true },
|
|
)
|
|
expect(cfg.model).toBe('claude-opus-4-7')
|
|
})
|
|
|
|
it('job.requested_thinking_budget overrult kind-default', () => {
|
|
const cfg = resolveJobConfig({ kind: 'PLAN_CHAT', requested_thinking_budget: 1024 }, {})
|
|
expect(cfg.thinking_budget).toBe(1024)
|
|
})
|
|
|
|
it('product.thinking_budget_default overrult kind-default', () => {
|
|
const cfg = resolveJobConfig({ kind: 'IDEA_GRILL' }, { thinking_budget_default: 0 })
|
|
expect(cfg.thinking_budget).toBe(0)
|
|
})
|
|
|
|
it('product.preferred_permission_mode = acceptEdits overrult bypassPermissions voor TASK_IMPLEMENTATION', () => {
|
|
const cfg = resolveJobConfig(
|
|
{ kind: 'TASK_IMPLEMENTATION' },
|
|
{ preferred_permission_mode: 'acceptEdits' },
|
|
)
|
|
expect(cfg.permission_mode).toBe('acceptEdits')
|
|
})
|
|
|
|
it('max_turns en allowed_tools blijven kind-default ook met product- en job-overrides (geen V1-cascade)', () => {
|
|
const cfg = resolveJobConfig(
|
|
{ kind: 'IDEA_GRILL', requested_model: 'claude-haiku-4-5-20251001' },
|
|
{ preferred_model: 'claude-sonnet-4-6' },
|
|
)
|
|
expect(cfg.max_turns).toBe(15)
|
|
expect(cfg.allowed_tools).toEqual(['Read', 'Grep', 'Glob', 'WebSearch', 'AskUserQuestion'])
|
|
})
|
|
})
|