Spiegelt de scrum4me-mcp wijzigingen naar de Scrum4Me web-app zodat enqueue-laag (lib/job-config-snapshot.ts) en claim-laag dezelfde defaults gebruiken. Plus runbook-correctie van een eerder gedocumenteerde maar niet-bestaande Claude CLI-flag. - T-25: lib/job-config.ts — mapBudgetToEffort export + KIND_DEFAULTS .allowed_tools voor TASK/SPRINT/IDEA_GRILL/IDEA_MAKE_PLAN omgezet naar expliciete lijsten zonder wait_for_job/check_queue_empty/ get_idea_context. Comment-block over CLI-flag-mapping en sync met scrum4me-mcp. - T-26: docs/runbooks/worker-idempotency.md sectie "Config doorgeven aan Claude Code (PBI-67)" herschreven. --thinking-budget vervangen door --effort (mapping-tabel toegevoegd); --max-turns geschrapt (CLI heeft die flag niet — audit-only). Sectie "Wie doet wat in de runner- architectuur" toegevoegd. - T-27: docs/runbooks/job-model-selection.md — notes over max_turns, thinking_budget en allowed_tools onder de matrix. Nieuwe sectie "Runner-architectuur" met verwijzing naar plan + worker-idempotency. - T-28: __tests__/lib/job-config.test.ts (nieuw) — 22 tests: mapBudgetToEffort grenswaarden + KIND_DEFAULTS.allowed_tools structurele checks + cascade regression. Plus: docs/plans/queue-loop-extraction.md (geschreven in plan-mode, nu gepubliceerd in repo). Verify: lint OK, typecheck OK, 587 tests in 78 files passed. Build niet lokaal uitgevoerd (vereist DATABASE_URL voor "Collecting page data" — diff raakt geen API-route). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
203 lines
6.3 KiB
TypeScript
203 lines
6.3 KiB
TypeScript
// PBI-67: model + mode-selectie per ClaudeJob-kind.
|
|
//
|
|
// Sync with scrum4me-mcp/src/lib/job-config.ts — als je hier een veld
|
|
// aanpast, doe hetzelfde aan de MCP-kant. Dit is bewust een duplicate
|
|
// (geen gedeeld package) om de MCP-server eigenstandig te houden.
|
|
//
|
|
// Override-cascade (eerste match wint):
|
|
// 1. task.requires_opus === true → forceer Opus
|
|
// 2. job.requested_* (snapshot bij enqueue, ingevuld door deze module)
|
|
// 3. product.preferred_*
|
|
// 4. KIND_DEFAULTS hieronder
|
|
//
|
|
// CLI-flag-mapping (Claude CLI 2.1.x):
|
|
// - thinking_budget (number) → mapBudgetToEffort() → --effort {low,medium,high,xhigh,max}
|
|
// (de CLI heeft geen --thinking-budget flag — alleen --effort)
|
|
// - max_turns blijft audit-only: de CLI heeft géén --max-turns flag.
|
|
// De waarde wordt gesnapshot voor cost-attribution maar niet doorgegeven.
|
|
// - allowed_tools → --allowedTools (komma-gescheiden lijst)
|
|
|
|
export type ClaudeModel =
|
|
| 'claude-opus-4-7'
|
|
| 'claude-sonnet-4-6'
|
|
| 'claude-haiku-4-5-20251001'
|
|
|
|
export type PermissionMode = 'plan' | 'default' | 'acceptEdits' | 'bypassPermissions'
|
|
|
|
export type JobConfig = {
|
|
model: ClaudeModel
|
|
thinking_budget: number
|
|
permission_mode: PermissionMode
|
|
max_turns: number | null
|
|
allowed_tools: string[] | null
|
|
}
|
|
|
|
export type JobInput = {
|
|
kind: string
|
|
requested_model?: string | null
|
|
requested_thinking_budget?: number | null
|
|
requested_permission_mode?: string | null
|
|
}
|
|
|
|
export type ProductInput = {
|
|
preferred_model?: string | null
|
|
thinking_budget_default?: number | null
|
|
preferred_permission_mode?: string | null
|
|
}
|
|
|
|
export type TaskInput = {
|
|
requires_opus?: boolean | null
|
|
}
|
|
|
|
// Tool-allowlists per kind. Bewust géén `wait_for_job`, `check_queue_empty`
|
|
// of `get_idea_context` — de runner (scrum4me-docker/bin/run-one-job.ts)
|
|
// claimt voor Claude. Vangrail tegen recursieve claims binnen één invocation.
|
|
const TASK_TOOLS = [
|
|
'Read', 'Edit', 'Write', 'Bash', 'Grep', 'Glob',
|
|
'mcp__scrum4me__get_claude_context',
|
|
'mcp__scrum4me__update_task_status',
|
|
'mcp__scrum4me__update_task_plan',
|
|
'mcp__scrum4me__log_implementation',
|
|
'mcp__scrum4me__log_test_result',
|
|
'mcp__scrum4me__log_commit',
|
|
'mcp__scrum4me__verify_task_against_plan',
|
|
'mcp__scrum4me__update_job_status',
|
|
'mcp__scrum4me__ask_user_question',
|
|
'mcp__scrum4me__get_question_answer',
|
|
'mcp__scrum4me__list_open_questions',
|
|
'mcp__scrum4me__cancel_question',
|
|
'mcp__scrum4me__worker_heartbeat',
|
|
]
|
|
|
|
const KIND_DEFAULTS: Record<string, JobConfig> = {
|
|
IDEA_GRILL: {
|
|
model: 'claude-sonnet-4-6',
|
|
thinking_budget: 12000,
|
|
permission_mode: 'plan',
|
|
max_turns: 15,
|
|
allowed_tools: [
|
|
'Read', 'Grep', 'Glob', 'WebSearch', 'AskUserQuestion',
|
|
'mcp__scrum4me__update_idea_grill_md',
|
|
'mcp__scrum4me__log_idea_decision',
|
|
'mcp__scrum4me__update_job_status',
|
|
'mcp__scrum4me__ask_user_question',
|
|
'mcp__scrum4me__get_question_answer',
|
|
],
|
|
},
|
|
IDEA_MAKE_PLAN: {
|
|
model: 'claude-opus-4-7',
|
|
thinking_budget: 24000,
|
|
permission_mode: 'plan',
|
|
max_turns: 20,
|
|
allowed_tools: [
|
|
'Read', 'Grep', 'Glob', 'WebSearch', 'AskUserQuestion', 'Write',
|
|
'mcp__scrum4me__update_idea_plan_md',
|
|
'mcp__scrum4me__log_idea_decision',
|
|
'mcp__scrum4me__update_job_status',
|
|
],
|
|
},
|
|
PLAN_CHAT: {
|
|
model: 'claude-sonnet-4-6',
|
|
thinking_budget: 6000,
|
|
permission_mode: 'plan',
|
|
max_turns: 5,
|
|
allowed_tools: ['Read', 'Grep', 'AskUserQuestion'],
|
|
},
|
|
TASK_IMPLEMENTATION: {
|
|
model: 'claude-sonnet-4-6',
|
|
thinking_budget: 6000,
|
|
permission_mode: 'bypassPermissions',
|
|
max_turns: 50,
|
|
allowed_tools: TASK_TOOLS,
|
|
},
|
|
SPRINT_IMPLEMENTATION: {
|
|
model: 'claude-sonnet-4-6',
|
|
thinking_budget: 6000,
|
|
permission_mode: 'bypassPermissions',
|
|
max_turns: null,
|
|
// Geen `mcp__scrum4me__job_heartbeat` — de runner verlengt de lease
|
|
// automatisch via setInterval (zie scrum4me-docker/bin/run-one-job.ts).
|
|
allowed_tools: [
|
|
...TASK_TOOLS,
|
|
'mcp__scrum4me__update_task_execution',
|
|
'mcp__scrum4me__verify_sprint_task',
|
|
],
|
|
},
|
|
}
|
|
|
|
const FALLBACK: JobConfig = {
|
|
model: 'claude-sonnet-4-6',
|
|
thinking_budget: 6000,
|
|
permission_mode: 'default',
|
|
max_turns: 50,
|
|
allowed_tools: null,
|
|
}
|
|
|
|
export function getKindDefault(kind: string): JobConfig {
|
|
return KIND_DEFAULTS[kind] ?? FALLBACK
|
|
}
|
|
|
|
// max_turns en allowed_tools blijven kind-default (geen product/task override
|
|
// in V1 — als de behoefte ontstaat, voeg analoge velden toe aan Product/Task).
|
|
export function resolveJobConfig(
|
|
job: JobInput,
|
|
product: ProductInput,
|
|
task?: TaskInput,
|
|
): JobConfig {
|
|
const base = getKindDefault(job.kind)
|
|
|
|
const model = (
|
|
task?.requires_opus
|
|
? 'claude-opus-4-7'
|
|
: job.requested_model ?? product.preferred_model ?? base.model
|
|
) as ClaudeModel
|
|
|
|
const thinking_budget =
|
|
job.requested_thinking_budget ?? product.thinking_budget_default ?? base.thinking_budget
|
|
|
|
const permission_mode = (job.requested_permission_mode ??
|
|
product.preferred_permission_mode ??
|
|
base.permission_mode) as PermissionMode
|
|
|
|
return {
|
|
model,
|
|
thinking_budget,
|
|
permission_mode,
|
|
max_turns: base.max_turns,
|
|
allowed_tools: base.allowed_tools,
|
|
}
|
|
}
|
|
|
|
// Map numeriek thinking_budget naar de Claude CLI 2.1.x --effort flag.
|
|
// Returns null als de flag niet meegegeven moet worden (budget = 0).
|
|
//
|
|
// Mapping (sync met scrum4me-mcp/src/lib/job-config.ts):
|
|
// 0 → null (geen --effort flag)
|
|
// 1..6000 → "medium"
|
|
// 6001..12000 → "high"
|
|
// 12001..24000→ "xhigh"
|
|
// >24000 → "max"
|
|
export function mapBudgetToEffort(budget: number): string | null {
|
|
if (budget <= 0) return null
|
|
if (budget <= 6000) return 'medium'
|
|
if (budget <= 12000) return 'high'
|
|
if (budget <= 24000) return 'xhigh'
|
|
return 'max'
|
|
}
|
|
|
|
// Snapshot-velden voor ClaudeJob.requested_*. Bij elke enqueue laden we
|
|
// product (voor preferred_*) en optioneel task (voor requires_opus), draaien
|
|
// de resolver, en schrijven het resultaat als auditspoor in de job-rij.
|
|
export type ClaudeJobSnapshotFields = {
|
|
requested_model: string
|
|
requested_thinking_budget: number
|
|
requested_permission_mode: string
|
|
}
|
|
|
|
export function snapshotFromConfig(cfg: JobConfig): ClaudeJobSnapshotFields {
|
|
return {
|
|
requested_model: cfg.model,
|
|
requested_thinking_budget: cfg.thinking_budget,
|
|
requested_permission_mode: cfg.permission_mode,
|
|
}
|
|
}
|