* 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.
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.mdop 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-machineQUEUED→CLAIMED→RUNNING→DONE/FAILED,CANCELLED-pad, stale-cleanup >30minClaudeWorker-model voor presence-heartbeat- SSE-pijplijn op
/api/realtime/solomet payload-routing opuser_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):
- Agent kan beide niveaus: PBI→stories én story→tasks (één agent, twee modi via
target_type) - Output: items direct in DB aanmaken via bestaande
create_*-tools — geen review-stap in v1 - Context: lokaal draaien + filesystem-toegang tot Scrum4Me-checkout (zoals impl-agent al doet)
- Rol-scheiding:
ClaudeJob.kindenum (IMPLEMENTATION|PLANNING) — één table, polymorf - Bestaande children: aanvullen — agent leest bestaande titels en voegt alleen ontbrekende toe
- Live feedback: stille SSE + status-pill op de PBI/Story-card; geen aparte modal
- MCP-shape: bestaande
wait_for_jobuitbreiden metaccept_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— voegkindtoe aan API-shape (lowercase:implementation|planning)lib/schemas/claude-job.ts(NEW of MODIFY) — discriminated union opkindlib/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:
- Auth-scope-check (
productAccessFilter) — target moet binnen product zitten - Demo-block (
session.isDemo→ 403) - Idempotency: weiger als er al een
PLANNING-job actief is voor dit(target_type, target_id) - Insert
ClaudeJobmetkind=PLANNING,task_id=null,planning_target_*ingevuld 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 krijgenkindin 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)disabledals 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 5sFAILED— rood, klikbaar voor error-detailCANCELLED— 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:
wait_for_job({ accept_kinds: ['PLANNING'], wait_seconds: 600 })— claim- Lees
planning_targetuit response (PBI of STORY) +existing_* - 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.mdals target UI-werk betreft
- 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
- Voor
- Filter ontbrekende items: skip wat overlapt met
existing_*(titel-match) - Voor elk:
create_taskofcreate_storyvia MCP 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
- Schema + migratie + status-mapper (Stap 1+2) — eigen commit, geen UI-impact
- Server action
enqueuePlanningJobAction+ tests (Stap 3) — werkt headless via curl/test - SSE-payload uitbreiden + claude-jobs-store (Stap 4) — backend pipe klaar
- Status-pill component + tests (Stap 5a) — losstaande primitive
- Trigger-knoppen in StoryDialog + PbiDialog (Stap 5b) — UI-trigger werkt, agent nog niet
- Pause — verifieer end-to-end met handmatig insert in
claude_jobs(kind=PLANNING) of via mock-MCP-call - MCP-PR in scrum4me-mcp repo (Stap 6) —
wait_for_jobuitbreiden, types updaten - Lokaal
/generate-plan-command schrijven + testen (Stap 7) — agent claimt, leest, schrijft - End-to-end test (Stap 8) — story → klik knop → agent rendert taken → SSE → live in TaskPanel
- 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
npm run lint && npm test && npm run buildgroen- Schema-migratie: bestaande
claude_jobs-rijen krijgenkind=IMPLEMENTATION; check-constraint blokkeert ongeldige combinaties - Idempotency: twee keer klikken op "Genereer taken" → tweede klik geeft toast "Plan-job al gestart", knop disabled
- Demo-block: demo-user ziet knop disabled met DemoTooltip; server action returnt 403 als je 'm toch aanroept
- SSE live: trigger planning-job → status-pill verschijnt op story-card binnen 1s zonder refresh
- End-to-end: lokale
/generate-planagent claimt job, leest target viawait_for_job, leest 3-4 docs uit Scrum4Me-checkout, maakt 3-5 taken viacreate_task, status DONE → taken zichtbaar in TaskPanel zonder refresh - Cancel-flow: gebruiker kan vanuit dialog een running PLANNING-job cancellen → status-pill verdwijnt, agent ziet job-status
CANCELLEDbij volgendeupdate_job_status - Cross-kind isolation: een implementation-agent met
accept_kinds=['IMPLEMENTATION'](default) ziet PLANNING-jobs niet; idem omgekeerd - Aanvullen-policy: trigger op story die al 2 taken heeft — agent voegt alleen ontbrekende toe (logged in summary: "Aangemaakt: 3 / Overgeslagen: 2 (titel-overlap)")
- Documentatie:
docs/patterns/claude-agent-roles.mdbeschrijft beide agent-rollen, hun job-flow, en hoe je een derde agent zou toevoegen
Open punten (na approval expliciet maken)
- 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.
- 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". - Doc-selectie: hoe bepaalt de agent welke
docs/patterns/*.mdrelevant zijn? Voorstel: leesdocs/patterns/-index op + match op keywords uit target-titel/-beschrijving. Geen embeddings in v1. - Hoeveel children per run? Voorstel: hard cap 8 in de prompt (anders gaat 'ie speculeren). Gebruiker kan opnieuw klikken voor meer.
- Editable plan-text: wanneer agent
implementation_planinvult op een nieuwe task, kan de gebruiker die later via TaskDialog editen — dat werkt al, geen extra werk. - 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):
ClaudeJobKindenum uitbreiden metREVIEWApiTokenKindenum uitbreiden metREVIEWenqueueReviewJobActionaanmaken (kopieert pattern van planning)wait_for_jobaccepteert nieuwekindautomatisch viaaccept_kinds- Pattern-doc
claude-agent-roles.mduitbreiden met de derde rol
Geen schema-revolutie nodig — kind-enum is het uitbreidingspunt.