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:
parent
e6dcc91383
commit
07749ad9fb
10 changed files with 490 additions and 69 deletions
|
|
@ -201,6 +201,106 @@ describe('startSprintRunAction — pre-flight blockers', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('startSprintRunAction — SPRINT_BATCH', () => {
|
||||
const SPRINT_BATCH = {
|
||||
...SPRINT_OK,
|
||||
product: {
|
||||
id: 'prod-1',
|
||||
pr_strategy: 'SPRINT_BATCH',
|
||||
repo_url: 'https://github.com/example/main',
|
||||
},
|
||||
}
|
||||
|
||||
it('blokkeert task met afwijkende repo_url', async () => {
|
||||
mockPrisma.sprint.findUnique.mockResolvedValue(SPRINT_BATCH)
|
||||
mockPrisma.sprintRun.findFirst.mockResolvedValue(null)
|
||||
mockPrisma.story.findMany.mockResolvedValue([
|
||||
{
|
||||
...STORY_OK,
|
||||
tasks: [
|
||||
{
|
||||
id: 'task-1',
|
||||
code: 'T-1',
|
||||
title: 'In main repo',
|
||||
priority: 1,
|
||||
sort_order: 1,
|
||||
implementation_plan: 'plan',
|
||||
repo_url: null,
|
||||
},
|
||||
{
|
||||
id: 'task-2',
|
||||
code: 'T-2',
|
||||
title: 'Cross-repo',
|
||||
priority: 1,
|
||||
sort_order: 2,
|
||||
implementation_plan: 'plan',
|
||||
repo_url: 'https://github.com/example/other',
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
mockPrisma.claudeQuestion.findMany.mockResolvedValue([])
|
||||
|
||||
const result = await startSprintRunAction({ sprint_id: 'sprint-1' })
|
||||
|
||||
expect(result).toMatchObject({ ok: false, error: 'PRE_FLIGHT_BLOCKED' })
|
||||
if (result.ok === false && 'blockers' in result) {
|
||||
expect(result.blockers).toContainEqual({
|
||||
type: 'task_cross_repo',
|
||||
id: 'task-2',
|
||||
label: 'T-2: Cross-repo',
|
||||
})
|
||||
}
|
||||
expect(mockPrisma.sprintRun.create).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('staat tasks toe wanneer repo_url leeg is of gelijk aan product.repo_url', async () => {
|
||||
mockPrisma.sprint.findUnique.mockResolvedValue(SPRINT_BATCH)
|
||||
mockPrisma.sprintRun.findFirst.mockResolvedValue(null)
|
||||
mockPrisma.story.findMany.mockResolvedValue([
|
||||
{
|
||||
...STORY_OK,
|
||||
tasks: [
|
||||
{
|
||||
id: 'task-1',
|
||||
code: 'T-1',
|
||||
title: 'No override',
|
||||
priority: 1,
|
||||
sort_order: 1,
|
||||
implementation_plan: 'plan',
|
||||
repo_url: null,
|
||||
},
|
||||
{
|
||||
id: 'task-2',
|
||||
code: 'T-2',
|
||||
title: 'Same repo',
|
||||
priority: 1,
|
||||
sort_order: 2,
|
||||
implementation_plan: 'plan',
|
||||
repo_url: 'https://github.com/example/main',
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
mockPrisma.claudeQuestion.findMany.mockResolvedValue([])
|
||||
mockPrisma.sprintRun.create.mockResolvedValue({ id: 'run-batch' })
|
||||
mockPrisma.claudeJob.create.mockResolvedValue({ id: 'job-sprint' })
|
||||
|
||||
const result = await startSprintRunAction({ sprint_id: 'sprint-1' })
|
||||
|
||||
expect(result).toMatchObject({ ok: true, sprint_run_id: 'run-batch' })
|
||||
// Eén SPRINT_IMPLEMENTATION-job, niet per-task
|
||||
expect(mockPrisma.claudeJob.create).toHaveBeenCalledTimes(1)
|
||||
expect(mockPrisma.claudeJob.create).toHaveBeenCalledWith({
|
||||
data: expect.objectContaining({
|
||||
kind: 'SPRINT_IMPLEMENTATION',
|
||||
sprint_run_id: 'run-batch',
|
||||
product_id: 'prod-1',
|
||||
}),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('startSprintRunAction — guards', () => {
|
||||
it('weigert wanneer Sprint niet ACTIVE is', async () => {
|
||||
mockPrisma.sprint.findUnique.mockResolvedValue({ ...SPRINT_OK, status: 'COMPLETED' })
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue