Scrum4Me/docs/plans/tweede-claude-agent-planning.md
Janpeter Visser e10f8f81bc
Phase 2 — Normalize file naming (#59)
* docs(naming): drop scrum4me- prefix from doc filenames

Rename 10 docs/scrum4me-*.md files to unprefixed kebab-case names.
Update every internal link in docs/, CLAUDE.md, AGENTS.md, README.md.

* docs(naming): lowercase API.md and MD3 filenames

Rename docs/API.md → docs/api.md and
docs/MD3_Color_Scheme_Documentation.md → docs/md3-color-scheme.md.
Update all internal links across 7 files.

* docs(naming): rename plan file to kebab-case ASCII

Rename "docs/plans/Tweede Claude Agent — Planning Agent.md"
→ docs/plans/tweede-claude-agent-planning.md. No external links needed updating.

* docs(naming): rename middleware.md to proxy.md (next 16)

docs/patterns/middleware.md → docs/patterns/proxy.md following
the Next.js 16 proxy.ts rename. Update link in CLAUDE.md.

* docs(naming): polish CLAUDE.md doc-index after renames

Fix doubled scrum4me-scrum4me-mcp repo references (cascade from
prior sed) in CLAUDE.md, docs/architecture.md, backlog.md,
agent-instruction-audit.md, and plans/ST-1109. Update
'Middleware' label to 'Proxy middleware' in patterns table.
2026-05-03 03:00:47 +02:00

18 KiB

Plan: Tweede Claude Agent — Planning Agent (PBI/Story → children)

Eerder goedgekeurd plan in deze file: Scrum4Me v1.0 Release (mobile shell + sprint-snapshots + release-discipline). Beschikbaar in chat-history; te verhuizen naar docs/plans/v1-release.md op een later moment. Dit nieuwe plan vervangt de plan-file inhoudelijk niet — het v1.0-werk blijft van kracht parallel hieraan.


Context

Wat de gebruiker wil: twee gespecialiseerde Claude-agents, elk met eigen context.

Agent Doel Context Status
Implementation Agent Codeert één task af → branch + PR Code-repo (filesystem), implementation_plan, story, pbi, sprint, repo_url Live (M13 / ST-1111)
Planning Agent (nieuw) Genereert children: PBI→stories of story→tasks (incl. implementation_plan per task) Specs + architectuur + patterns (filesystem in Scrum4Me-checkout), parent-record, bestaande children Te bouwen

Wat al staat (hergebruikbaar):

  • ClaudeJob-model met state-machine QUEUED→CLAIMED→RUNNING→DONE/FAILED, CANCELLED-pad, stale-cleanup >30min
  • ClaudeWorker-model voor presence-heartbeat
  • SSE-pijplijn op /api/realtime/solo met payload-routing op user_id + product_id
  • MCP-tools wait_for_job, update_job_status, get_claude_context, create_pbi, create_story, create_task, update_task_plan, log_implementation
  • Idempotency-pattern: max 1 actieve job per resource

Vastgelegde keuzes (uit AskUserQuestion-sessies):

  1. Agent kan beide niveaus: PBI→stories én story→tasks (één agent, twee modi via target_type)
  2. Output: items direct in DB aanmaken via bestaande create_*-tools — geen review-stap in v1
  3. Context: lokaal draaien + filesystem-toegang tot Scrum4Me-checkout (zoals impl-agent al doet)
  4. Rol-scheiding: ClaudeJob.kind enum (IMPLEMENTATION | PLANNING) — één table, polymorf
  5. Bestaande children: aanvullen — agent leest bestaande titels en voegt alleen ontbrekende toe
  6. Live feedback: stille SSE + status-pill op de PBI/Story-card; geen aparte modal
  7. MCP-shape: bestaande wait_for_job uitbreiden met accept_kinds: string[]-arg, default ['IMPLEMENTATION'] (backwards-compat)

Approach (8 stappen)

Stap 1 — Schema-uitbreiding

prisma/schema.prisma:

enum ClaudeJobKind {
  IMPLEMENTATION
  PLANNING
}

enum PlanningTargetType {
  PBI
  STORY
}

enum ApiTokenKind {
  IMPLEMENTATION
  PLANNING
  // beide kinds simultaan = afzonderlijke tokens; eenvoudiger dan multi-kind-flag
}

model ClaudeJob {
  // ... bestaande velden ...
  kind                ClaudeJobKind   @default(IMPLEMENTATION)
  task_id             String?         // wordt nullable — planning-jobs hebben geen task
  task                Task?           @relation(fields: [task_id], references: [id], onDelete: Cascade)
  planning_target_type PlanningTargetType?
  planning_target_id   String?

  @@index([kind, status])
  @@index([planning_target_type, planning_target_id, status])
}

model ApiToken {
  // ... bestaande velden ...
  kind  ApiTokenKind  @default(IMPLEMENTATION)
}

model ClaudeWorker {
  // ... bestaande velden ...
  // accepted_kinds wordt afgeleid uit token.kind (geen extra kolom nodig)
}

Constraint via DB-check (CHECK constraint of app-level): een ClaudeJob heeft óf task_id (kind=IMPLEMENTATION) óf planning_target_* (kind=PLANNING). Nooit beide leeg, nooit beide gevuld.

Migratie: alle bestaande rijen krijgen kind=IMPLEMENTATION + apiToken.kind=IMPLEMENTATION als default. Backwards-compatible.

Bestand: prisma/migrations/<date>_planning_job_kind/migration.sql

Stap 2 — Status-mappers + Zod-schemas

  • lib/claude-job-status.ts — voeg kind toe aan API-shape (lowercase: implementation | planning)
  • lib/schemas/claude-job.ts (NEW of MODIFY) — discriminated union op kind
  • lib/schemas/planning-target.ts (NEW) — { type: 'PBI'|'STORY', id: string } validator

Stap 3 — Server actions

actions/claude-jobs.ts uitbreiden:

export async function enqueuePlanningJobAction(input: {
  productId: string
  target: { type: 'PBI' | 'STORY', id: string }
}): Promise<EnqueueResult>

Logica:

  1. Auth-scope-check (productAccessFilter) — target moet binnen product zitten
  2. Demo-block (session.isDemo → 403)
  3. Idempotency: weiger als er al een PLANNING-job actief is voor dit (target_type, target_id)
  4. Insert ClaudeJob met kind=PLANNING, task_id=null, planning_target_* ingevuld
  5. pg_notify('scrum4me_changes', { type: 'claude_job_enqueued', kind: 'planning', ... })

cancelClaudeJobAction (bestaand) blijft werken — accepteert nu ook PLANNING-jobs (zelfde state-machine).

Stap 4 — SSE-routing

app/api/realtime/solo/route.ts:

  • Bestaande claude_job_*-events krijgen kind in payload
  • Bij connect: claude_jobs_initial-event bevat ook actieve PLANNING-jobs van vandaag
  • Filter blijft user_id + product_id — geen extra topic nodig

Stap 5 — UI: triggers + status-pills

Trigger in beide dialog-profielen (geprofileerd in PR #45):

Locatie Knop-label Target
components/backlog/story-dialog.tsx (edit-mode) 🤖 Genereer taken met Claude { type: 'STORY', id: story.id }
components/backlog/pbi-dialog.tsx (edit-mode) 🤖 Genereer stories met Claude { type: 'PBI', id: pbi.id }

Knop-gedrag:

  • <DemoTooltip show={isDemo}> rond knop (laag 3 demo-policy)
  • disabled als er al een PLANNING-job actief is voor deze target (live via SSE-store)
  • Tooltip bij disabled: "Plan-job al gestart — wachten op resultaat"
  • Klik → enqueuePlanningJobAction → toast "Plan gestart" → dialog blijft open zodat user resultaat ziet binnenkomen

Status-pill component (NEW):

components/shared/planning-job-pill.tsx — kleine badge die de status van een lopende PLANNING-job toont:

  • QUEUED — grijs, "In wachtrij"
  • CLAIMED / RUNNING — blauw met spinner, "Plan wordt gegenereerd…"
  • DONE — groen, fade-out na 5s
  • FAILED — rood, klikbaar voor error-detail
  • CANCELLED — niet getoond (verwijdert pill)

Plaatsing:

  • Op PbiList-card naast PBI-titel (rechts)
  • Op StoryPanel-card naast story-titel (rechts)
  • In PbiDialog / StoryDialog-header (edit-mode) als large variant

Live updates: bestaande useClaudeJobsStore (Zustand, populated uit SSE) — alleen kind toevoegen aan filter-helpers.

Stap 6 — MCP-tools (scrum4me-mcp repo, aparte PR)

Wijziging 1 — bestaande tool uitbreiden:

// wait_for_job tool input schema
{
  accept_kinds?: ('IMPLEMENTATION' | 'PLANNING')[]  // default: ['IMPLEMENTATION']
  wait_seconds?: number                             // bestaand
}

Server-side: WHERE kind = ANY($1) AND status = 'QUEUED' in de FOR UPDATE SKIP LOCKED-query. Token-kind moet ook compatibel zijn (token.kind IN accept_kinds-overlap).

Response-shape voegt kind toe; voor PLANNING-jobs vervangt task door planning_target met embedded record:

{
  job_id: string
  kind: 'IMPLEMENTATION' | 'PLANNING'
  product: { id, name, repo_url, ... }
  // IMPL-only:
  task?: { ..., implementation_plan, story, pbi, sprint }
  // PLANNING-only:
  planning_target?: {
    type: 'PBI' | 'STORY'
    pbi?: { id, code, title, description, priority, status, existing_stories: [{ id, code, title, priority }] }
    story?: { id, code, title, description, acceptance_criteria, priority, status, pbi: {...}, existing_tasks: [{ id, title, priority, status }] }
  }
}

Wijziging 2 — nieuwe MCP-tool:

get_planning_context(target_type, target_id) — losstaande lookup voor agent die handmatig een planning wil starten zonder job-claim. Optioneel; wait_for_job retourneert dezelfde data al.

Geen nieuwe write-tools nodig: bestaande create_story + create_task + update_task_plan werken al.

Schema-sync: vendor/scrum4me submodule update na Scrum4Me-PR merge.

Stap 7 — Agent-prompt + lokale Claude-command

In de Scrum4Me-checkout (of in een gedeelde plek voor agent-prompts) twee Claude Code commands:

/implement-next-story — bestaand, gebruikt wait_for_job({ accept_kinds: ['IMPLEMENTATION'] })

/generate-plan — nieuw:

Korte prompt-flow:

  1. wait_for_job({ accept_kinds: ['PLANNING'], wait_seconds: 600 }) — claim
  2. Lees planning_target uit response (PBI of STORY) + existing_*
  3. Lees lokale docs uit Scrum4Me-checkout:
    • docs/functional.md (functioneel kader)
    • docs/architecture.md (technisch kader)
    • docs/patterns/*.md (relevante patterns op basis van target-titel/-beschrijving)
    • docs/styling.md als target UI-werk betreft
  4. Bedenk children:
    • Voor STORY-target: 3-7 taken met titel, korte beschrijving, implementation_plan (verwijst naar relevante patterns + bestanden), priority
    • Voor PBI-target: 2-5 stories met titel, beschrijving in user-story-format, acceptance_criteria, priority
  5. Filter ontbrekende items: skip wat overlapt met existing_* (titel-match)
  6. Voor elk: create_task of create_story via MCP
  7. update_job_status({ status: 'DONE', summary: 'Aangemaakt: 4 taken / 0 overgeslagen (titel-overlap)' })

Bij failure: update_job_status({ status: 'FAILED', error }) + toast voor user.

Mens-rolverdeling: twee Claude Code-sessies tegelijk draaien (één met /implement-next-story running, één met /generate-plan running). Beide claimen alleen hun eigen kind via accept_kinds. Dezelfde gebruiker-token of twee aparte (afhankelijk van hoe je workers wilt scheiden).

Stap 8 — Tests

Test Locatie
enqueuePlanningJobAction — auth, demo, idempotency, scope __tests__/actions/claude-jobs-planning.test.ts
Schema-mapper voor kind + planning_target_* __tests__/lib/claude-job-status.test.ts
SSE-event format met kind __tests__/api/realtime-solo-planning.test.ts
Status-pill rendering per status __tests__/components/shared/planning-job-pill.test.tsx
Knop disabled-state in StoryDialog/PbiDialog bij actieve job __tests__/components/backlog/dialog-planning-button.test.tsx

MCP-tools testen in scrum4me-mcp repo (aparte PR).


Critical files

Scrum4Me-repo

File Action Reden
prisma/schema.prisma MODIFY ClaudeJobKind, PlanningTargetType, ApiTokenKind enums + nullable task_id + planning_target_* velden
prisma/migrations/<date>_planning_job_kind/ NEW Migratie + check-constraint
lib/claude-job-status.ts MODIFY kind in API-shape
lib/schemas/claude-job.ts NEW/MODIFY Discriminated union op kind
lib/schemas/planning-target.ts NEW Target-validator
actions/claude-jobs.ts MODIFY enqueuePlanningJobAction toevoegen, idempotency uitbreiden
app/api/realtime/solo/route.ts MODIFY kind in payload, initial-state ook PLANNING-jobs
stores/claude-jobs-store.ts (of vergelijkbaar) MODIFY kind-filter helpers
components/backlog/story-dialog.tsx MODIFY "Genereer taken"-knop + status-pill in header
components/backlog/pbi-dialog.tsx MODIFY "Genereer stories"-knop + status-pill in header
components/backlog/story-panel.tsx MODIFY Status-pill op story-card
components/backlog/pbi-list.tsx MODIFY Status-pill op pbi-card
components/shared/planning-job-pill.tsx NEW Generic pill-component
docs/patterns/claude-agent-roles.md NEW Pattern-doc: één table, kind-enum, accept_kinds-arg, lokale agent-prompts
docs/architecture.md MODIFY Sectie "Claude Agents" uitbreiden — twee rollen, schema, queue, prompts
docs/pbi-dialog.md MODIFY Sectie "Speciale gedragingen → Planning-trigger" toevoegen
docs/story-dialog.md MODIFY Idem
docs/task-dialog.md MODIFY Vermelden dat tasks ook door planning-agent kunnen ontstaan
__tests__/actions/claude-jobs-planning.test.ts NEW
__tests__/lib/claude-job-status.test.ts MODIFY kind-mapping testen
__tests__/api/realtime-solo-planning.test.ts NEW
__tests__/components/shared/planning-job-pill.test.tsx NEW
__tests__/components/backlog/dialog-planning-button.test.tsx NEW

scrum4me-mcp repo (aparte PR, na Scrum4Me-merge)

File Action
src/tools/wait_for_job.ts MODIFY — accept_kinds-arg + polymorf response
src/tools/get_planning_context.ts NEW (optioneel, helper)
src/types/job.ts MODIFY — kind + planning_target
src/prompts/generate-plan.md NEW — Claude command-prompt
vendor/scrum4me/ (submodule) UPDATE — na Scrum4Me-merge

Volgorde van uitvoering

  1. Schema + migratie + status-mapper (Stap 1+2) — eigen commit, geen UI-impact
  2. Server action enqueuePlanningJobAction + tests (Stap 3) — werkt headless via curl/test
  3. SSE-payload uitbreiden + claude-jobs-store (Stap 4) — backend pipe klaar
  4. Status-pill component + tests (Stap 5a) — losstaande primitive
  5. Trigger-knoppen in StoryDialog + PbiDialog (Stap 5b) — UI-trigger werkt, agent nog niet
  6. Pause — verifieer end-to-end met handmatig insert in claude_jobs (kind=PLANNING) of via mock-MCP-call
  7. MCP-PR in scrum4me-mcp repo (Stap 6) — wait_for_job uitbreiden, types updaten
  8. Lokaal /generate-plan-command schrijven + testen (Stap 7) — agent claimt, leest, schrijft
  9. End-to-end test (Stap 8) — story → klik knop → agent rendert taken → SSE → live in TaskPanel
  10. Docs-PR — pattern-doc claude-agent-roles.md, architecture-update, dialog-profielen aanvullen

Branch-naming: feat/M15-planning-agent (Scrum4Me) + feat/planning-agent (scrum4me-mcp).

Conform CLAUDE.md "branch-per-milestone": commits accumuleren lokaal, pushen pas na gebruikerstest.


Verification

  1. npm run lint && npm test && npm run build groen
  2. Schema-migratie: bestaande claude_jobs-rijen krijgen kind=IMPLEMENTATION; check-constraint blokkeert ongeldige combinaties
  3. Idempotency: twee keer klikken op "Genereer taken" → tweede klik geeft toast "Plan-job al gestart", knop disabled
  4. Demo-block: demo-user ziet knop disabled met DemoTooltip; server action returnt 403 als je 'm toch aanroept
  5. SSE live: trigger planning-job → status-pill verschijnt op story-card binnen 1s zonder refresh
  6. End-to-end: lokale /generate-plan agent claimt job, leest target via wait_for_job, leest 3-4 docs uit Scrum4Me-checkout, maakt 3-5 taken via create_task, status DONE → taken zichtbaar in TaskPanel zonder refresh
  7. Cancel-flow: gebruiker kan vanuit dialog een running PLANNING-job cancellen → status-pill verdwijnt, agent ziet job-status CANCELLED bij volgende update_job_status
  8. Cross-kind isolation: een implementation-agent met accept_kinds=['IMPLEMENTATION'] (default) ziet PLANNING-jobs niet; idem omgekeerd
  9. Aanvullen-policy: trigger op story die al 2 taken heeft — agent voegt alleen ontbrekende toe (logged in summary: "Aangemaakt: 3 / Overgeslagen: 2 (titel-overlap)")
  10. Documentatie: docs/patterns/claude-agent-roles.md beschrijft beide agent-rollen, hun job-flow, en hoe je een derde agent zou toevoegen

Open punten (na approval expliciet maken)

  1. Token-strategie: krijgt elke agent-rol een eigen ApiToken (cleaner, twee credentials), of bestaat er één multi-kind-token per user? Voorstel: aparte tokens — één per kind. Gebruiker beheert ze in Settings.
  2. Concurrency in 1 worker: mag accept_kinds: ['IMPLEMENTATION', 'PLANNING'] (één worker pakt allebei)? Voorstel: ja, technisch toegestaan, maar in v1 raad je af want context-pollution. Documenteren als "kan, maar gebruik gescheiden processen".
  3. Doc-selectie: hoe bepaalt de agent welke docs/patterns/*.md relevant zijn? Voorstel: lees docs/patterns/-index op + match op keywords uit target-titel/-beschrijving. Geen embeddings in v1.
  4. Hoeveel children per run? Voorstel: hard cap 8 in de prompt (anders gaat 'ie speculeren). Gebruiker kan opnieuw klikken voor meer.
  5. Editable plan-text: wanneer agent implementation_plan invult op een nieuwe task, kan de gebruiker die later via TaskDialog editen — dat werkt al, geen extra werk.
  6. Failure-recovery: wat als agent halverwege crasht? Stale-cleanup >30min werkt al; partial-children blijven aangemaakt. Voorstel: accepteer partial — gebruiker kan opnieuw triggeren, aanvullen-policy filtert duplicaten.

Out of scope (v1 van deze feature)

  • Diff-review-flow (vervangen-modus uit eerdere AskUserQuestion)
  • Live streaming-output van agent (alleen status-events, geen tekst-stream)
  • Meerdere parallele PLANNING-jobs op dezelfde target (idempotency blokkeert)
  • Custom prompts per product (vaste prompt-template /generate-plan)
  • Embeddings / vector-search over docs (agent leest plain files)
  • Cross-user planning (agent werkt altijd binnen eigen product-scope)
  • Auto-trigger (bv. "elke nieuwe lege story krijgt automatisch een plan-job") — handmatige trigger blijft regel
  • Agent-tot-agent-orkestratie (planning-agent kan geen impl-agent triggeren) — gebruiker blijft in the loop
  • Planning op product-niveau (PBI's genereren) — pas zinvol als product-spec-input bestaat
  • Planning-agent UI in Solo Paneel (Solo blijft impl-only) — triggers zitten in backlog-dialogs

Migratie-pad voor toekomstige derde agent (referentie)

Als er ooit een derde agent komt (bv. review-agent die een PR review't):

  1. ClaudeJobKind enum uitbreiden met REVIEW
  2. ApiTokenKind enum uitbreiden met REVIEW
  3. enqueueReviewJobAction aanmaken (kopieert pattern van planning)
  4. wait_for_job accepteert nieuwe kind automatisch via accept_kinds
  5. Pattern-doc claude-agent-roles.md uitbreiden met de derde rol

Geen schema-revolutie nodig — kind-enum is het uitbreidingspunt.