M13: Veilige Claude-agent-workflow (Scrum4Me-side) (#26)

* feat: add pushed_at field to ClaudeJob schema

Nullable DateTime column to record when the agent's feature branch was
pushed to origin. Enables the UI to show a 'pushed' state independently
of DONE status.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: GitHub-link op DONE-card + pushed_at doorvoer

- lib/job-status-url.ts: getBranchUrl(repoUrl, branch) → GitHub tree URL
- JobState + ClaudeJobEvent: pushed_at? veld toegevoegd
- realtime/solo/route.ts: pushed_at in Prisma-select, JobPayload en mapping
- SoloBoardProps + TaskDetailDialog: repoUrl prop doorgevoerd
- task-detail-dialog: "Open op GitHub"-link als done + pushed_at + branch + repoUrl
- 3 unit-tests voor getBranchUrl; totaal 261 tests groen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add VerifyResult enum, verify_only on Task, verify_result on ClaudeJob

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add verify_result+pushed_at to JobState, VerifyResultApi type, SSE payload

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: verify_only field on SoloTask, PATCH route saves verify_only

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: TaskDetailDialog — verify_result display + verify_only checkbox

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: verify_only PATCH + verify_result dialog render + store fix

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: document VerifyResult enum, verify_only task field, pushed_at in architecture

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(M13): cron /api/cron/cleanup-agent-artifacts — hard-delete FAILED/CANCELLED jobs >7 days

* feat(M13): add auto_pr field to Product schema + migration

* feat(M13): auto_pr toggle in product settings — server action + UI component + tests

* feat(M13): add pr_url to ClaudeJob schema + migration

* feat(M13): UI — 'Open PR' link on DONE-card; pr_url in JobState + SSE + task-dialog

* feat(M13): add retry_count migration + regen erd

- Migration ALTER TABLE claude_jobs ADD COLUMN retry_count INT DEFAULT 0
  (schema.prisma was reeds bijgewerkt in eerdere commits)
- docs/erd.svg geregenereerd voor de complete M13-schema-wijzigingen
  (verify_result, verify_only, pushed_at, pr_url, auto_pr, retry_count)

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-05-01 13:42:18 +02:00 committed by GitHub
parent acb591266f
commit 9794a9baef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 725 additions and 20 deletions

View file

@ -0,0 +1,46 @@
// @vitest-environment jsdom
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, screen, fireEvent } from '@testing-library/react'
import '@testing-library/jest-dom'
vi.mock('@/actions/products', () => ({
updateAutoPrAction: vi.fn(),
}))
vi.mock('sonner', () => ({ toast: { error: vi.fn() } }))
import { updateAutoPrAction } from '@/actions/products'
import { AutoPrToggle } from '@/components/products/auto-pr-toggle'
const mockAction = updateAutoPrAction as ReturnType<typeof vi.fn>
beforeEach(() => {
vi.clearAllMocks()
mockAction.mockResolvedValue({ success: true })
})
describe('AutoPrToggle', () => {
it('renders in off state with aria-checked=false', () => {
render(<AutoPrToggle productId="prod-1" initialValue={false} />)
const toggle = screen.getByRole('switch')
expect(toggle).toHaveAttribute('aria-checked', 'false')
})
it('renders in on state with aria-checked=true', () => {
render(<AutoPrToggle productId="prod-1" initialValue={true} />)
const toggle = screen.getByRole('switch')
expect(toggle).toHaveAttribute('aria-checked', 'true')
})
it('calls updateAutoPrAction with true when toggled on', async () => {
render(<AutoPrToggle productId="prod-1" initialValue={false} />)
fireEvent.click(screen.getByRole('switch'))
expect(mockAction).toHaveBeenCalledWith('prod-1', true)
})
it('calls updateAutoPrAction with false when toggled off', async () => {
render(<AutoPrToggle productId="prod-1" initialValue={true} />)
fireEvent.click(screen.getByRole('switch'))
expect(mockAction).toHaveBeenCalledWith('prod-1', false)
})
})