PBI-50 F3: nieuwe MCP-tools voor SPRINT_IMPLEMENTATION-flow
Vier nieuwe tools + propagateStatusUpwards uitbreiding:
T1 — verify_sprint_task (src/tools/verify-sprint-task.ts):
Execution-aware verify met frozen plan_snapshot. Input: execution_id +
worktree_path + optionele summary (voor PARTIAL/DIVERGENT-rationale).
Vult base_sha dynamisch voor task[1..N] op basis van vorige DONE-execution's
head_sha. Schrijft verify_result + verify_summary op execution-row.
Returns { result, reasoning, base_sha, allowed_for_done, reason? } —
allowed_for_done via standaard checkVerifyGate met snapshot-velden.
T2 — update_task_execution (src/tools/update-task-execution.ts):
Lifecycle-tool voor SprintTaskExecution: PENDING/RUNNING/DONE/FAILED/SKIPPED
+ base_sha/head_sha/skip_reason. Idempotent. Token-check via
execution.sprint_job.claimed_by_token_id. started_at/finished_at automatisch.
T3 — job_heartbeat (src/tools/job-heartbeat.ts):
Verlengt ClaudeJob.lease_until met 5 min via atomic conditional UPDATE
(token-check + status-check in WHERE). Voor SPRINT-jobs: response bevat
sprint_run_status + sprint_run_pause_reason zodat worker op UI-side cancel
of MERGE_CONFLICT-pause kan breken zonder extra query.
T4 — update_task_status sprint_run_id-arg + token-coupling
(src/tools/update-task-status.ts):
Optionele sprint_run_id-arg voor expliciete cascade. Validaties: SprintRun
bestaat + actief, task in deze sprint, current token heeft een actieve
ClaudeJob in deze run geclaimd (403 anders). Response uitgebreid met
sprint_run_status_change.
T5 — propagateStatusUpwards sprintRunId-param
(src/lib/tasks-status-update.ts):
Optionele sprintRunId-parameter. Resolve-volgorde: expliciete arg →
ClaudeJob.task_id-lookup → Story → Sprint → SprintRun.findFirst({active}).
De derde fallback dekt SPRINT_IMPLEMENTATION (geen task_id-koppeling) én
handmatige task-statuswijzigingen via UI. cancelExceptJobId voor
sibling-cancel; null voor SPRINT-job betekent geen siblings te cancellen.
src/index.ts: drie nieuwe tools geregistreerd.
Tests: 31 files, 243 passing (geen tests voor nieuwe tools nog — F5).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
35601e8e4b
commit
25ab68073a
6 changed files with 471 additions and 16 deletions
110
src/tools/update-task-execution.ts
Normal file
110
src/tools/update-task-execution.ts
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
// PBI-50 F3-T2: update_task_execution
|
||||
//
|
||||
// SPRINT_IMPLEMENTATION-flow lifecycle-tool. Worker roept dit aan voor elke
|
||||
// task in de batch om de SprintTaskExecution-row te muteren:
|
||||
// PENDING → RUNNING → DONE/FAILED/SKIPPED
|
||||
// Idempotent: dezelfde call kan veilig herhaald worden.
|
||||
|
||||
import { z } from 'zod'
|
||||
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
||||
import { prisma } from '../prisma.js'
|
||||
import { requireWriteAccess } from '../auth.js'
|
||||
import { toolError, toolJson, withToolErrors } from '../errors.js'
|
||||
|
||||
const inputSchema = z.object({
|
||||
execution_id: z.string().min(1),
|
||||
status: z.enum(['PENDING', 'RUNNING', 'DONE', 'FAILED', 'SKIPPED']),
|
||||
base_sha: z.string().optional(),
|
||||
head_sha: z.string().optional(),
|
||||
skip_reason: z.string().max(2000).optional(),
|
||||
})
|
||||
|
||||
export function registerUpdateTaskExecutionTool(server: McpServer) {
|
||||
server.registerTool(
|
||||
'update_task_execution',
|
||||
{
|
||||
title: 'Update SprintTaskExecution status',
|
||||
description:
|
||||
'Mutate a SprintTaskExecution row in a SPRINT_IMPLEMENTATION batch. ' +
|
||||
'Status: PENDING|RUNNING|DONE|FAILED|SKIPPED. Worker calls this for each ' +
|
||||
'task transition. Token must own the parent SPRINT_IMPLEMENTATION ClaudeJob. ' +
|
||||
'Idempotent — safe to retry. Schrijft started_at (RUNNING) en finished_at ' +
|
||||
'(DONE/FAILED/SKIPPED). Forbidden for demo accounts.',
|
||||
inputSchema,
|
||||
},
|
||||
async ({ execution_id, status, base_sha, head_sha, skip_reason }) =>
|
||||
withToolErrors(async () => {
|
||||
const auth = await requireWriteAccess()
|
||||
|
||||
const execution = await prisma.sprintTaskExecution.findUnique({
|
||||
where: { id: execution_id },
|
||||
select: {
|
||||
id: true,
|
||||
sprint_job_id: true,
|
||||
sprint_job: {
|
||||
select: { claimed_by_token_id: true, status: true, kind: true },
|
||||
},
|
||||
},
|
||||
})
|
||||
if (!execution) {
|
||||
return toolError(`SprintTaskExecution ${execution_id} not found`)
|
||||
}
|
||||
if (execution.sprint_job.kind !== 'SPRINT_IMPLEMENTATION') {
|
||||
return toolError(
|
||||
`Execution ${execution_id} hangs at job kind ${execution.sprint_job.kind}, expected SPRINT_IMPLEMENTATION`,
|
||||
)
|
||||
}
|
||||
if (execution.sprint_job.claimed_by_token_id !== auth.tokenId) {
|
||||
return toolError(
|
||||
`Forbidden: token does not own SPRINT_IMPLEMENTATION job for execution ${execution_id}`,
|
||||
)
|
||||
}
|
||||
if (
|
||||
execution.sprint_job.status !== 'CLAIMED' &&
|
||||
execution.sprint_job.status !== 'RUNNING'
|
||||
) {
|
||||
return toolError(
|
||||
`Sprint job is in terminal state ${execution.sprint_job.status}`,
|
||||
)
|
||||
}
|
||||
|
||||
const now = new Date()
|
||||
const updated = await prisma.sprintTaskExecution.update({
|
||||
where: { id: execution_id },
|
||||
data: {
|
||||
status,
|
||||
...(base_sha !== undefined ? { base_sha } : {}),
|
||||
...(head_sha !== undefined ? { head_sha } : {}),
|
||||
...(skip_reason !== undefined ? { skip_reason } : {}),
|
||||
...(status === 'RUNNING' ? { started_at: now } : {}),
|
||||
...(status === 'DONE' || status === 'FAILED' || status === 'SKIPPED'
|
||||
? { finished_at: now }
|
||||
: {}),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
status: true,
|
||||
base_sha: true,
|
||||
head_sha: true,
|
||||
verify_result: true,
|
||||
verify_summary: true,
|
||||
skip_reason: true,
|
||||
started_at: true,
|
||||
finished_at: true,
|
||||
},
|
||||
})
|
||||
|
||||
return toolJson({
|
||||
execution_id: updated.id,
|
||||
status: updated.status,
|
||||
base_sha: updated.base_sha,
|
||||
head_sha: updated.head_sha,
|
||||
verify_result: updated.verify_result,
|
||||
verify_summary: updated.verify_summary,
|
||||
skip_reason: updated.skip_reason,
|
||||
started_at: updated.started_at?.toISOString() ?? null,
|
||||
finished_at: updated.finished_at?.toISOString() ?? null,
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue