Scrum4Me/docs/plans/auto-pr-deploy-sync.md
Madhura68 deb70a9e20 feat(T-572): map SKIPPED in lib/job-status + alle terminal-checks
- lib/job-status.ts: SKIPPED ↔ 'skipped' mapping in beide richtingen
- components/shared/job-status.ts: label "Overgeslagen" + neutrale italic styling
- actions/admin/jobs.ts: cancel-guard erkent SKIPPED als eindstatus
- app/api/cron/cleanup-agent-artifacts: SKIPPED ook opruimen na 7d
- lib/insights/agent-throughput: SKIPPED telt mee als terminal

ACTIVE_JOB_STATUSES bewust ongewijzigd — SKIPPED is afgerond.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 23:10:14 +02:00

486 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Plan — Auto-PR + selectieve deploy-controle + sync-zicht (end-to-end batch flow)
> Bij merge: dit plan verplaatsen naar `docs/plans/auto-pr-deploy-sync.md`
> conform feedback-memory (plans in `docs/plans/`).
## Context
Drie samenhangende problemen rond de "idee → uitvoeren"-keten:
1. **Worker stopt bij `commit`.** De Scrum4Me NAS-worker werkt lokaal:
commits blijven op de machine staan totdat de gebruiker zelf pusht en
een PR aanmaakt. Voor batch-uitvoer van story-jobs is dit een harde
menselijke gate.
2. **Deploy is alles-of-niets.** `.github/workflows/ci.yml` deployt nu
**elke** push naar `main` automatisch naar productie en **elke** PR
naar preview. `vercel.json` heeft geen `git.deploymentEnabled: false`,
dus Vercel's eigen Git-integratie deployt waarschijnlijk parallel mee
→ dubbele deploys en geen selectieve controle.
3. **Geen zicht op voortgang per Idea/PBI.** Concreet getest geval:
PBI-33 wordt nu de eerste sprint-batch — er is **geen git-voetafdruk**
(geen branch/commit/PR met "PBI-33"), **geen activiteitenlog-entry**,
en geen UI-pagina die per Story toont of er een ClaudeJob loopt, een
commit gepusht is, of een PR open/merged is. De data zit in
`Story.status`, `ClaudeJob.pushed_at/branch/pr_url`,
`Pbi.pr_url/pr_merged_at` — er is alleen geen view die het joint.
Doel: de complete keten **plan → job → commit → push → PR → auto-merge →
deploy** in één coherent ontwerp leggen, met (a) selectieve
deploy-controle als veiligheidsklep en (b) een sync-tab die per Idea
laat zien wat er werkelijk in git/PR-land gebeurd is.
## Vastgelegde keuzes
### Deploy-controle
1. **Mechanisme**: PR-labels (B) + path-filter (C) gecombineerd.
2. **Eigenaar**: GitHub Actions-workflow (A). Vercel Git-integratie uit.
3. **Defaults**: PR → preview, push naar `main` → productie.
4. **Override-richtingen**:
- `skip-deploy` label: voorkomt preview-deploy op een PR.
- `force-deploy` label: forceert deploy ook als path-filter doc-only
zegt.
### Auto-PR (uit IDEA-007-grill)
5. **Triggers in worker**: na elke succesvolle `update_job_status('done')`
pusht de worker; na laatste story van een PBI maakt de worker een PR
aan en activeert auto-merge (SQUASH).
6. **Auth**: `GITHUB_TOKEN` als omgevingsvariabele op de worker; geen UI
of GitHub App in v1.
7. **Foutafhandeling**: push/PR-aanmaak-fail → `update_job_status('failed',
error: …)`; geen force-push, geen automatische retry.
### Interactie tussen beide
8. **Worker-PRs gebruiken hetzelfde labelsysteem als alle andere PRs.**
Default = preview deploy, auto-merge wacht op CI groen, na merge
prod-deploy (mits path-filter zegt "code"). De worker zet **geen**
labels automatisch — als je batch-output zonder preview wilt mergen
moet je `skip-deploy` zelf toevoegen, of preview later uitzetten via
een product-instelling (out-of-scope v1).
9. **Implementatievolgorde**: eerst deploy-controle (infra,
onafhankelijk), daarna auto-PR (afhankelijk van stabiele deploy-flow).
## Architectuur in één plaat
```
auto-merge wacht op
[story-job DONE] ─push branch─┐ deploy-preview groen
▼ │
[laatste story?]──ja──[PR + auto-merge]──CI──┴──merge naar main
[job: ci] altijd
[paths-filter]
├ PR → deploy-preview
│ if code && !skip-deploy
│ || force-deploy
└ push → deploy-production
if code
```
---
## Deel A — Deploy-controle
### A.1 `vercel.json` — Vercel Git-deploy uitzetten
```json
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"git": { "deploymentEnabled": false },
"crons": [
{ "path": "/api/cron/expire-questions", "schedule": "0 4 * * *" },
{ "path": "/api/cron/cleanup-agent-artifacts", "schedule": "0 3 * * *" }
]
}
```
Effect: Vercel deployt niet meer automatisch op git-events. Alleen
`vercel deploy` vanuit de workflow (met `VERCEL_TOKEN`) maakt nog
deployments.
### A.2 `.github/workflows/ci.yml` — path-filter + label-checks
Triggers uitbreiden met `workflow_dispatch`:
```yaml
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
inputs:
target:
type: choice
description: Deploy target
options: [preview, production]
default: preview
```
Nieuwe job vóór de deploy-jobs:
```yaml
changes:
name: Detect deploy-relevant changes
runs-on: ubuntu-latest
needs: ci
outputs:
code: ${{ steps.filter.outputs.code }}
steps:
- uses: actions/checkout@v5
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
code:
- 'app/**'
- 'components/**'
- 'lib/**'
- 'actions/**'
- 'stores/**'
- 'prisma/**'
- 'public/**'
- 'package.json'
- 'package-lock.json'
- 'next.config.ts'
- 'tsconfig.json'
- 'vercel.json'
- 'proxy.ts'
- 'middleware.ts'
- '.github/workflows/**'
```
`deploy-preview` if-conditie aanpassen:
```yaml
deploy-preview:
needs: [ci, changes]
if: |
github.event_name == 'pull_request' && (
(needs.changes.outputs.code == 'true'
&& !contains(github.event.pull_request.labels.*.name, 'skip-deploy'))
|| contains(github.event.pull_request.labels.*.name, 'force-deploy')
)
```
`deploy-production` if-conditie aanpassen:
```yaml
deploy-production:
needs: [ci, changes]
if: |
github.ref == 'refs/heads/main'
&& github.event_name == 'push'
&& needs.changes.outputs.code == 'true'
```
Nieuwe `deploy-manual` job voor `workflow_dispatch` met `inputs.target`
→ `vercel deploy` of `vercel deploy --prod`.
### A.3 GitHub-labels aanmaken
```bash
gh label create skip-deploy --color BFBFBF --description "Preview-deploy overslaan"
gh label create force-deploy --color 0E8A16 --description "Forceer deploy ondanks path-filter"
```
### A.4 Documentatie
`docs/runbooks/deploy-control.md` — triggers, labels, path-filter,
voorbeelden. `CLAUDE.md` § Deployment-regel verwijst naar runbook.
---
## Deel B — Auto-PR (worker → GitHub)
### B.1 Acceptatiecriteria (uit IDEA-007)
- **AC 1 — Push per story**: Na succesvolle `update_job_status('done')`
pusht de worker via HTTPS (`https://$GITHUB_TOKEN@github.com/…`) naar
origin. Push-timestamp via nieuwe MCP-call in `ClaudeJob.pushed_at`.
- **AC 2 — Detectie laatste story**: Nieuwe MCP-call `check_pbi_complete`
retourneert `{ complete: boolean, pbi_id }`.
- **AC 3 — PR aanmaken**: Op `complete: true` POST naar
`/repos/{owner}/{repo}/pulls`; titel/body uit PBI-naam + voltooide
stories; PR-URL via `set_pbi_pr`.
- **AC 4 — Auto-merge activeren**: Direct na PR-aanmaak GraphQL
`enablePullRequestAutoMerge` (SQUASH).
- **AC 5 — Foutafhandeling**: push/PR-fail →
`update_job_status('failed', error)`; PR-URL blijft bewaard voor
handmatige inspectie.
### B.2 Server-side wijzigingen (Scrum4Me-repo)
Velden bestaan al in schema:
- `Product.auto_pr Boolean @default(false)` (regel 176)
- `Pbi.pr_url String?` + `Pbi.pr_merged_at DateTime?` (regel 207208)
- `ClaudeJob.pushed_at DateTime?` + `ClaudeJob.pr_url String?` +
`ClaudeJob.branch String?` (regel 335, 338, 339)
Geen migratie nodig.
Server actions / REST: bestaande `set_pbi_pr` en `mark_pbi_pr_merged`
MCP-tools blijven. Nieuwe action:
- `actions/jobs.ts` → `recordJobPushedAtAction(jobId)` voor
`pushed_at`-write (als die nog niet via MCP gaat).
### B.3 MCP-laag (`scrum4me-mcp`-repo)
Nieuwe tool:
- `check_pbi_complete(pbi_id) → { complete: boolean, pbi_id }`. Leest
alle ClaudeJobs gelinkt aan PBI; aggregeert status. `complete = true`
als **alle** story-jobs status DONE hebben.
Uitbreiding bestaande tool:
- `update_job_status`: bij `status: 'done'` ook `pushed_at` accepteren
(worker geeft timestamp door).
- `set_pbi_pr`: ongewijzigd, bestaat al.
Schema-drift watchdog (`docs/runbooks/mcp-integration.md`) moet groen
voor merge.
### B.4 Worker-laag (lokaal Claude-CLI worker)
Nieuwe stappen na elke story:
```
1. update_job_status('done', pushed_at: null) ← bestaand
2. git push https://$GITHUB_TOKEN@github.com/$OWNER/$REPO.git $BRANCH
3. record_pushed_at(job_id, now) ← nieuwe MCP-call
4. { complete } = check_pbi_complete(pbi_id)
5. if complete:
prNumber = POST /repos/.../pulls
set_pbi_pr(pbi_id, pr_url)
enablePullRequestAutoMerge(prNumber, MERGE_METHOD: SQUASH)
6. on any HTTP/git failure → update_job_status('failed', error)
```
GITHUB_TOKEN-scope: `repo` voor private, `public_repo` voor public.
Documenteer in worker-readme.
### B.5 Repo-instellingen (handmatig, one-time)
- GitHub repo Settings → General → "Allow auto-merge" → **aanvinken**.
- Branch protection op `main`: required CI checks = `ci`,
`deploy-preview` is **niet** required (kan skipped zijn door label).
---
## Deel C — Interactie & demo-policy
### C.1 Interactie deploy-controle ↔ auto-PR
| Scenario | Preview-deploy | Prod-deploy bij merge |
|--------------------------------------------------|----------------|------------------------|
| Worker maakt PR met code-changes (default) | ✅ runt | ✅ runt |
| Worker maakt PR met `skip-deploy` (manueel toegevoegd) | ❌ skipped | ✅ runt |
| Worker maakt PR met enkel docs-changes (path-filter) | ❌ skipped | ❌ skipped |
| User voegt `force-deploy` toe aan doc-only PR | ✅ runt | ✅ runt (path-filter) of ❌ (doc-only push) |
Auto-merge wacht op required CI checks. `deploy-preview` mag skipped
zijn — branch protection markeert hem niet als required.
### C.2 Demo-policy
Auto-PR-flow draait op de worker, niet vanuit de webapp. Geen
demo-sessie kan deze code triggeren — geen extra proxy.ts of
`session.isDemo`-guards nodig. Wel: `check_pbi_complete` MCP-call moet
`requireWriteAccess` doen (consistent met andere write-MCP-tools), zodat
demo-tokens hem niet kunnen aanroepen.
---
---
## Deel D — Sync-tab op Idea-detail (zicht op voortgang)
### D.1 Wat bestaat al
- `model StoryLog` (`prisma/schema.prisma:251`) met types
`IMPLEMENTATION_PLAN | TEST_RESULT | COMMIT`, plus `commit_hash`,
`commit_message`, `metadata`. **Dit is de activiteitenlog.**
- MCP-tools `log_implementation`, `log_commit`, `log_test_result`
schrijven naar deze tabel.
- UI-component `components/shared/story-log.tsx` rendert
`StoryLogEntry[]` met type-styling.
- `Story.status`, `ClaudeJob.pushed_at/branch/pr_url`,
`Pbi.pr_url/pr_merged_at` zijn al gevuld door bestaande flows.
Geen nieuwe tabellen, geen migraties.
### D.2 Nieuwe tab op `/ideas/[id]`
Voeg vijfde tab **Sync** toe (naast Idee · Grill · Plan · Timeline) op
Idea-detail-page. Alleen zichtbaar als `Idea.status === 'PLANNED'` en
`pbi_id` gevuld.
Layout per tab-content:
- Header: PBI-link + `pr_url` + `pr_merged_at` als badge.
- Per Story (volgorde uit PBI): collapsible card met:
- **Story-header**: code · titel · status-badge.
- **Job-rij**: voor elke `ClaudeJob` (kind=TASK_IMPLEMENTATION) gelinkt
aan een Task van deze Story → status, `branch`, `pushed_at`,
`pr_url`. Toont "geen job" als nog niets gequeued.
- **Activity-log**: `<StoryLog logs={logs} repoUrl={product.repo_url} />`
— bestaande component, ongewijzigd.
### D.3 Server-laag
Nieuwe loader in `app/(app)/ideas/[id]/page.tsx` (of nieuw
`sync-tab-server.ts`):
```ts
async function loadIdeaSyncData(ideaId: string, userId: string) {
// Auth-scope: idea.user_id === userId (M12-keuze 2)
return prisma.idea.findFirst({
where: { id: ideaId, user_id: userId },
include: {
pbi: {
include: {
stories: {
orderBy: { sort_order: 'asc' },
include: {
tasks: { include: { claude_jobs: true } },
logs: { orderBy: { created_at: 'desc' } },
},
},
},
},
},
})
}
```
Server-only. Nooit importeren in client component (zie hardstop
`*-server.ts` regel).
### D.4 Realtime refresh
Sync-tab abonneert op bestaande SSE-streams:
- `app/api/realtime/solo/route.ts` — `JobPayload` voor job-status-updates
(al uitgebreid met `kind` en `idea_id` per Deel B).
- `app/api/realtime/notifications/route.ts` — voor StoryLog-inserts; als
story_logs nog geen pg_notify-trigger heeft, voeg er een toe (nieuwe
migratie, payload `{op: 'INSERT', entity: 'story_log', id, story_id}`).
Op event → `router.refresh()` of `revalidate` van Sync-tab data.
### D.5 PBI-33 als live testgeval
PBI-33 is **nu** in TODO + gequeued als ClaudeJobs (gebruiker bevestigt:
"taken op TODO gezet en claude-job aangemaakt"). Verwacht gedrag zodra
deze sprint live is:
| Moment | Sync-tab toont |
|----------------------------|-----------------------------------------------|
| Job QUEUED | "Wachtend op worker" |
| Job RUNNING | Status RUNNING + log-entry IMPLEMENTATION_PLAN|
| Worker commit | log-entry COMMIT (hash + message) |
| Worker test | log-entry TEST_RESULT (status) |
| Worker push (Deel B AC 1) | `branch` + `pushed_at` zichtbaar |
| Laatste story → PR | PBI.`pr_url` zichtbaar |
| Auto-merge | PBI.`pr_merged_at` zichtbaar |
Als één van deze niet verschijnt: bug in MCP-tool of worker (niet in
sync-tab zelf).
---
## Bestanden
| Wijziging | Pad |
|-------------------|--------------------------------------------------|
| Edit | `vercel.json` |
| Edit | `.github/workflows/ci.yml` |
| Nieuw | `docs/runbooks/deploy-control.md` |
| Edit | `CLAUDE.md` (verwijzing toevoegen) |
| Nieuw (mcp-repo) | `src/tools/check-pbi-complete.ts` |
| Edit (mcp-repo) | `src/tools/update-job-status.ts` (pushed_at) |
| Edit | `actions/jobs.ts` (optioneel: record-pushed-at) |
| Edit | Worker-script (post-story-hook + PR-aanmaak) |
| Doc | `docs/runbooks/auto-pr-flow.md` (worker-flow) |
| Nieuw | `app/(app)/ideas/[id]/sync-tab-server.ts` |
| Nieuw | `components/ideas/idea-sync-tab.tsx` |
| Edit | `app/(app)/ideas/[id]/page.tsx` (5e tab toevoegen) |
| Migratie | `prisma/migrations/<ts>_story_logs_notify/migration.sql` (pg_notify-trigger op story_logs) |
| Edit | `app/api/realtime/notifications/route.ts` (story_log-payload doorlaten) |
| GitHub (extern) | Labels `skip-deploy`, `force-deploy` aanmaken |
| GitHub (extern) | Repo Settings → "Allow auto-merge" aan |
| Vercel-dashboard | `git.deploymentEnabled: false` actief verifiëren |
## Implementatievolgorde
1. **Deel A — Deploy-controle**
1. `vercel.json` aanpassen
2. `ci.yml` uitbreiden (path-filter, labels, dispatch)
3. Labels op GitHub aanmaken
4. Runbook + CLAUDE.md-verwijzing
5. Test-PR voor elk scenario (zie Verificatie)
2. **Deel D — Sync-tab** (kan parallel met B; alleen DB-reads + UI)
1. `loadIdeaSyncData` server-loader
2. `idea-sync-tab.tsx` component met `<StoryLog>`-hergebruik
3. 5e tab in `app/(app)/ideas/[id]/page.tsx`
4. pg_notify-trigger op `story_logs` + SSE-route uitbreiden
5. **Live test op PBI-33** (sprint loopt al — check of activity
verschijnt zodra worker logs schrijft)
3. **Deel B — Auto-PR**
1. MCP `check_pbi_complete` + `update_job_status(pushed_at)` PR
(parallel-repo, schema-drift-watchdog groen)
2. Worker-hook: push na done, PR + auto-merge bij complete
3. Repo-instelling "Allow auto-merge" aan
4. End-to-end smoke met één test-PBI
## Verificatie
Lokaal:
```bash
npm run lint && npm test && npm run build
```
Workflow-syntax:
```bash
gh workflow view ci.yml
```
End-to-end deploy-controle:
1. **Doc-only PR** → `deploy-preview` skipped.
2. **Doc-only PR + `force-deploy`** → `deploy-preview` runt.
3. **Code-PR + `skip-deploy`** → `deploy-preview` skipped.
4. **Code-PR zonder labels** → `deploy-preview` runt.
5. **Push naar `main` met code-change** → `deploy-production` runt.
6. **Push naar `main` doc-only** → `deploy-production` skipped.
7. **`workflow_dispatch` target=production** → manuele prod.
8. **Vercel dashboard** → geen auto-deploy bij geforceerde test-push.
End-to-end auto-PR:
9. Maak een test-PBI met 1 story + 1 task.
10. Worker draait → na `done`: `pushed_at` gevuld, branch op origin
zichtbaar.
11. `check_pbi_complete` → `complete: true`.
12. PR verschijnt op GitHub met titel = PBI-naam, body = story-list.
13. Auto-merge actief; CI groen → squash-merge.
14. `mark_pbi_pr_merged` getriggerd door `pull_request: closed`-webhook
(al bestaand) → `Pbi.pr_merged_at` gevuld.
15. Push-event op `main` → `deploy-production` runt (path-filter ja).
16. **Failure-test**: revoke GITHUB_TOKEN tijdelijk → push faalt →
`update_job_status('failed')` met error; geen PR aangemaakt.
## Out-of-scope (v1)
- UI-toggle voor `auto_pr` per product (veld bestaat, geen UI-wiring).
- GitHub App-installatie (per-repo tokens, scopes-finetuning).
- Multi-repo PBI's (huidig ontwerp: één `repo_url` per PBI).
- Force-push / non-fast-forward retry-flow.
- Notificaties (Slack, e-mail) bij merge of CI-failure.
- Rollback-flow bij gemergende regressie.
- Migratie naar `vercel.ts` (knowledge-update beveelt het aan; later).
- Auto-skip preview-deploy specifiek voor worker-PRs op basis van
product-instelling.