# 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 207–208) - `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**: `` — 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/_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 ``-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.