PBI-50: SPRINT_IMPLEMENTATION single-session sprint runner (Scrum4Me-side) (#139)

* PBI-50 F1: SPRINT_BATCH execution-strategy + cross-repo blocker + branch-resume

Schema-migratie + Scrum4Me-side wiring voor de nieuwe SPRINT_IMPLEMENTATION-flow:

- prisma: PrStrategy ADD VALUE 'SPRINT_BATCH'; ClaudeJobKind ADD VALUE
  'SPRINT_IMPLEMENTATION'; nieuwe enum SprintTaskExecutionStatus; ClaudeJob.lease_until
  + status_lease_until index; SprintRun.previous_run_id (self-relation
  SprintRunChain) voor branch-hergebruik bij resume; nieuwe sprint_task_executions
  tabel met frozen plan_snapshot + verify_required_snapshot per task in scope.
- actions/sprint-runs.ts startSprintRunCore: nieuwe blocker-type 'task_cross_repo'
  voor SPRINT_BATCH (pre-flight rejecteert sprints met cross-repo task_url).
  Bij SPRINT_BATCH: één SPRINT_IMPLEMENTATION ClaudeJob (geen per-task loop).
- actions/sprint-runs.ts resumePausedSprintRunAction: SPRINT_BATCH-pad met
  remaining-execution-check; bij onafgemaakt werk → nieuwe SprintRun met
  previous_run_id + run.branch hergebruikt + nieuwe SPRINT_IMPLEMENTATION-job.
  Oude SprintRun → CANCELLED. Bestaande PBI-49 P0 scope-DONE pad ongewijzigd.
- actions/products.ts updatePrStrategyAction: accepteert SPRINT_BATCH.
- components/products/pr-strategy-select.tsx: drie opties met helptekst,
  gebruikt @prisma/client PrStrategy ipv lokaal type.
- components/sprint/sprint-run-controls.tsx: BLOCKER_LABELS + blockerHref
  voor task_cross_repo.

Migratie applied op Neon. Type-check + 532 tests groen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* PBI-50 F5: cross-repo blocker test voor SPRINT_BATCH

- task_cross_repo blocker fires bij task.repo_url ≠ product.repo_url
- happy path: tasks zonder repo_url-override of met match → één
  SPRINT_IMPLEMENTATION-job (niet per-task).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* PBI-50 F5: docs/architecture/sprint-execution-modes.md

Vergelijking PER_TASK vs SPRINT_BATCH met trade-offs, datamodel-
toevoegingen (SprintTaskExecution, lease_until, SprintRunChain) en
MCP-tools-matrix per modus. Toegevoegd aan breadcrumb in
docs/architecture.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-05-07 13:05:02 +02:00 committed by GitHub
parent e6dcc91383
commit 07749ad9fb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 490 additions and 69 deletions

View file

@ -2,6 +2,7 @@
import { useState, useTransition } from 'react'
import { toast } from 'sonner'
import type { PrStrategy } from '@prisma/client'
import { updatePrStrategyAction } from '@/actions/products'
import {
Select,
@ -10,29 +11,33 @@ import {
SelectTrigger,
} from '@/components/ui/select'
type PrStrategy = 'SPRINT' | 'STORY'
interface PrStrategySelectProps {
productId: string
initialValue: PrStrategy
}
const STRATEGY_LABELS: Record<PrStrategy, string> = {
SPRINT: 'Per sprint — één PR voor de hele sprint, klaar voor review aan eind',
STORY: 'Per story — auto-merge na CI groen, één PR per story',
SPRINT:
'Per sprint — één PR voor de hele sprint, klaar voor review aan eind. Per task een eigen claude-sessie.',
STORY:
'Per story — auto-merge na CI groen, één PR per story. Per task een eigen claude-sessie.',
SPRINT_BATCH:
'Sprint batch — één claude-sessie voor de hele sprint, sneller en behoudt context. Vereist alle tasks in de product-hoofdrepo.',
}
const VALID_VALUES: ReadonlyArray<PrStrategy> = ['SPRINT', 'STORY', 'SPRINT_BATCH']
export function PrStrategySelect({ productId, initialValue }: PrStrategySelectProps) {
const [value, setValue] = useState<PrStrategy>(initialValue)
const [isPending, startTransition] = useTransition()
function handleChange(next: string | null) {
if (next !== 'SPRINT' && next !== 'STORY') return
if (!next || !VALID_VALUES.includes(next as PrStrategy)) return
if (next === value) return
const previous = value
setValue(next)
setValue(next as PrStrategy)
startTransition(async () => {
const result = await updatePrStrategyAction(productId, next)
const result = await updatePrStrategyAction(productId, next as PrStrategy)
if ('error' in result && result.error) {
setValue(previous)
toast.error(typeof result.error === 'string' ? result.error : 'Opslaan mislukt')
@ -49,6 +54,7 @@ export function PrStrategySelect({ productId, initialValue }: PrStrategySelectPr
<SelectContent>
<SelectItem value="SPRINT">{STRATEGY_LABELS.SPRINT}</SelectItem>
<SelectItem value="STORY">{STRATEGY_LABELS.STORY}</SelectItem>
<SelectItem value="SPRINT_BATCH">{STRATEGY_LABELS.SPRINT_BATCH}</SelectItem>
</SelectContent>
</Select>
</div>