diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9766b6f..0c04619 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -200,6 +200,9 @@ model Product { definition_of_done String auto_pr Boolean @default(false) pr_strategy PrStrategy @default(SPRINT) + preferred_model String? + thinking_budget_default Int? + preferred_permission_mode String? archived Boolean @default(false) created_at DateTime @default(now()) updated_at DateTime @updatedAt @@ -353,6 +356,7 @@ model Task { status TaskStatus @default(TO_DO) verify_only Boolean @default(false) verify_required VerifyRequired @default(ALIGNED_OR_PARTIAL) + requires_opus Boolean @default(false) // Override product.repo_url for branch/worktree/push purposes. Set when // a task targets a different repo than its parent product (e.g. an // MCP-server task tracked under the main product's PBI). Falls back to @@ -398,6 +402,10 @@ model ClaudeJob { output_tokens Int? cache_read_tokens Int? cache_write_tokens Int? + requested_model String? + requested_thinking_budget Int? + requested_permission_mode String? + actual_thinking_tokens Int? plan_snapshot String? base_sha String? head_sha String? diff --git a/src/tools/wait-for-job.ts b/src/tools/wait-for-job.ts index 1323e50..99a8090 100644 --- a/src/tools/wait-for-job.ts +++ b/src/tools/wait-for-job.ts @@ -18,6 +18,7 @@ import { createWorktreeForJob } from '../git/worktree.js' import { getWorktreeRoot } from '../git/worktree-paths.js' import { setupProductWorktrees, releaseLocksOnTerminal } from '../git/job-locks.js' import { pushBranchForJob } from '../git/push.js' +import { resolveJobConfig } from '../lib/job-config.js' /** Parse `https://github.com//(.git)?` → ``. */ export function repoNameFromUrl(repoUrl: string | null | undefined): string | null { @@ -467,11 +468,38 @@ async function getFullJobContext(jobId: string) { }, }, }, - product: { select: { id: true, name: true, repo_url: true, definition_of_done: true } }, + product: { + select: { + id: true, + name: true, + repo_url: true, + definition_of_done: true, + preferred_model: true, + thinking_budget_default: true, + preferred_permission_mode: true, + }, + }, }, }) if (!job) return null + // PBI-67: model + mode-selectie. Resolved op claim-moment; override-cascade + // task.requires_opus → job.requested_* → product.preferred_* → kind-default. + const config = resolveJobConfig( + { + kind: job.kind, + requested_model: job.requested_model, + requested_thinking_budget: job.requested_thinking_budget, + requested_permission_mode: job.requested_permission_mode, + }, + { + preferred_model: job.product.preferred_model, + thinking_budget_default: job.product.thinking_budget_default, + preferred_permission_mode: job.product.preferred_permission_mode, + }, + job.task ? { requires_opus: job.task.requires_opus } : undefined, + ) + // M12: branch on kind. Idea-jobs hebben geen task/story/pbi/sprint; ze // hebben in plaats daarvan idea + embedded prompt_text. if (job.kind === 'IDEA_GRILL' || job.kind === 'IDEA_MAKE_PLAN') { @@ -515,6 +543,7 @@ async function getFullJobContext(jobId: string) { job_id: job.id, kind: job.kind, status: 'claimed', + config, idea: { id: idea.id, code: idea.code, @@ -659,6 +688,7 @@ async function getFullJobContext(jobId: string) { job_id: job.id, kind: job.kind, status: 'claimed', + config, sprint: { id: sprintRun.sprint.id, sprint_goal: sprintRun.sprint.sprint_goal, @@ -724,6 +754,7 @@ async function getFullJobContext(jobId: string) { job_id: job.id, kind: job.kind, status: 'claimed', + config, task: { id: task.id, title: task.title,