- new enums IdeaStatus, ClaudeJobKind, IdeaLogType
- new models Idea (with @@unique([user_id, code]) + pbi_id @unique) and IdeaLog
- User.idea_code_counter Int @default(0) for IDEA-{nnn} code generation
- ClaudeJob.task_id nullable; new idea_id + kind fields + index
- ClaudeQuestion.story_id nullable; new idea_id field + index
- existing call sites narrowed to story-questions / task-jobs (idea-paths come in T-502+)
- includes the M12 plan doc copied from /Users/janpetervisser/.claude/plans
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
15 KiB
15 KiB
| title | status | audience | language |
|---|---|---|---|
| M12 — Idea entity + Grill/Plan Claude jobs | planned | implementation | nl |
M12 — Idea entity + Grill/Plan Claude jobs
Context
Scrum4Me ondersteunt Todo als lichtgewicht voorstel-laag, en kan dat handmatig promoveren naar PBI/Story. Dat slaat het denkproces niet vast: waarom werd iets een PBI, welke alternatieven zijn afgewogen, welke randvoorwaarden waren er.
Doel: een nieuw concept Idee dat:
- werkt als een Todo (top-level lijst, privé per gebruiker), met een Grill Me- en Make Plan-knop;
- via de bestaande Claude-job/worker-infrastructuur een gestructureerd plan oplevert;
- het hele planningsproces vastlegt (Q&A, beslissingen, grill-md, plan-md, link naar PBI);
- na goedkeuring deterministisch materialiseert tot PBI + stories + taken (incl.
implementation_plan).
Vastgelegde keuzes (uit grill-sessie)
- UI-plek: top-level
/ideas, naast/todos. - Auth-scope: strikt
user_id-only (privé, ook náPLANNED). GeenproductAccessFilterop idea-acties; geenpbi.idea_id-veld nodig. - Product-binding: een idee mag bestaan zonder product, maar Grill Me én Make Plan vereisen een product met
repo_url(de worker leest sources/docs uit de repo).claude_jobs.product_idblijft NOT NULL. - Executie-model: bestaand worker-model.
ClaudeJob{kind:IDEA_*}QUEUED → lokale Claude-CLI claimt viawait_for_job. Knoppen zijn disabled alsconnectedWorkers === 0(exact zoalssolo/task-detail-dialog.tsx). - Skill-afhankelijkheid: embedded prompts in
lib/idea-prompts/{grill,make-plan}.md; meegestuurd in payload. Geen externeanthropic-skills:grill-me-plugin-vereiste op de worker. - Make-Plan flow: preview-en-bevestigen. Job produceert
Idea.plan_md, status →PLAN_READY. Aparte knop "Materialiseer plan" parseert md → entiteiten in één Prisma-transactie, status →PLANNED. - Plan-md formaat: YAML-frontmatter (structuur) + markdown-body (overwegingen, alternatieven, vrije reasoning).
- Make-Plan-job: single-pass (geen
ask_user_question). Twijfels → terug naar grill (append-context). - Backward transitions:
- Re-grill vanuit
GRILLED/PLAN_READY: nieuweIDEA_GRILL-job met append-context (oudegrill_mdals input); oude versie naarIdeaLog{type:GRILL_RESULT}als history. - Re-plan vanuit
PLAN_READY: idem voorplan_md. - PBI-verwijdering vanuit
PLANNED: expliciete user-actie "Re-link plan" (geen DB-trigger). Zetpbi_id=null, statusPLAN_READY. - Failed grill/plan: dedicated states
GRILL_FAILED/PLAN_FAILED(zichtbaar voor user), niet stilzwijgend resetten.
- Re-grill vanuit
- Logging-model:
IdeaLogsmal (DECISION | NOTE | GRILL_RESULT | PLAN_RESULT | STATUS_CHANGE | JOB_EVENT). Q&A blijft uitsluitend inclaude_questions. Timeline-tab in UI doetUNION ALLover beide bronnen. - Opslag md-bestanden: alleen DB (
Idea.grill_md,Idea.plan_md). Geen auto-commit naar repo (zou strict-private auth-keuze ondergraven). UI biedt "Download .md". - Editability: beide md's bewerkbaar door user in hun ready-states (
GRILLEDvoor grill_md,PLAN_READYvoor plan_md). BijPLANNED: read-only. Yaml-frontmatter wordt zod-gevalideerd-on-save voorplan_md. - Promotie vanuit Todo: nieuwe
promoteTodoToIdeaAction(Todo → DRAFT-Idea + Todo wordtarchived=true). Bestaande Todo→PBI/Story-acties blijven onaangetast. - Demo-policy (3-laag, zoals Todo): create/edit/archive mag; Grill / Make Plan / Materialiseer / promote-from-Todo zijn geblokkeerd (proxy.ts 403 +
session.isDemo-guard +<DemoTooltip>). - Idea-code:
Idea.code = "IDEA-{nnn}",@@unique([user_id, code]), counter opUser.idea_code_counter. - Realtime-store: nieuwe
stores/idea-store.ts.connectedWorkersdirect selecten viauseSoloStore(s => s.connectedWorkers)(lift naar shared store is opvolg-refactor). - Sidebar: nieuwe entry Ideeën (
Lightbulb-icon) direct boven Todo's. - Q&A-expiry: 24h aanhouden (consistent met bestaand). Verlopen → re-grill (append-context).
State machine
┌──── re-grill ────┐
▼ │
DRAFT ──Grill Me──▶ GRILLING ─done──▶ GRILLED ─Make Plan─▶ PLANNING ─done──▶ PLAN_READY ─Materialiseer─▶ PLANNED
│ fail │ fail │ ▲ │
▼ ▼ │ │ │
GRILL_FAILED PLAN_FAILED └──┘ re-plan │
│ │
└────── retry/edit ──────────────────────────────────────── PBI verwijderd ──────────┘
+ "Re-link plan"
archived: boolean is orthogonaal en kan vanuit elke status.
Datamodel
Nieuwe enums
enum IdeaStatus {
DRAFT
GRILLING
GRILL_FAILED
GRILLED
PLANNING
PLAN_FAILED
PLAN_READY
PLANNED
}
enum ClaudeJobKind {
TASK_IMPLEMENTATION
IDEA_GRILL
IDEA_MAKE_PLAN
}
enum IdeaLogType {
DECISION
NOTE
GRILL_RESULT
PLAN_RESULT
STATUS_CHANGE
JOB_EVENT
}
User
- Veld toevoegen:
idea_code_counter Int @default(0).
Nieuwe tabel ideas
model Idea {
id String @id @default(cuid())
user User @relation(fields: [user_id], references: [id], onDelete: Cascade)
user_id String
product Product? @relation(fields: [product_id], references: [id], onDelete: SetNull)
product_id String?
code String @db.VarChar(30)
title String
description String? @db.VarChar(4000)
grill_md String? @db.Text
plan_md String? @db.Text
pbi Pbi? @relation(fields: [pbi_id], references: [id], onDelete: SetNull)
pbi_id String? @unique
status IdeaStatus @default(DRAFT)
archived Boolean @default(false)
created_at DateTime @default(now())
updated_at DateTime @updatedAt
questions ClaudeQuestion[]
jobs ClaudeJob[]
logs IdeaLog[]
@@unique([user_id, code])
@@index([user_id, archived, status])
@@index([user_id, product_id])
@@map("ideas")
}
Nieuwe tabel idea_logs
model IdeaLog {
id String @id @default(cuid())
idea Idea @relation(fields: [idea_id], references: [id], onDelete: Cascade)
idea_id String
type IdeaLogType
content String @db.Text
metadata Json?
created_at DateTime @default(now())
@@index([idea_id, created_at])
@@map("idea_logs")
}
Aanpassingen claude_jobs
task_id→ nullable.idea_id String?toegevoegd, FK →Idea,onDelete: Cascade.kind ClaudeJobKind @default(TASK_IMPLEMENTATION)toegevoegd.product_idblijft NOT NULL.- Raw-SQL check-constraint:
(task_id IS NOT NULL) <> (idea_id IS NOT NULL). - Index:
@@index([idea_id, status]).
Aanpassingen claude_questions
story_id→ nullable.idea_id String?toegevoegd, FK →Idea,onDelete: Cascade.- Raw-SQL check-constraint:
(story_id IS NOT NULL) <> (idea_id IS NOT NULL). - Index:
@@index([idea_id, status]). - pg_notify-trigger payload uitbreiden met
idea_id(nullable). SSE-filter laat idea-payloads alleen door naaridea.user_id === session.user_id.
Server-laag
Schemas + helpers
lib/schemas/idea.ts—ideaCreateSchema,ideaUpdateSchema,ideaPlanMdFrontmatterSchema.lib/idea-status.ts— DB-enum ↔ API-string mapping.lib/idea-plan-parser.ts— synchroon:parsePlanMd(md): ParsedPlan | ZodError. Gebruiktyaml-package + zod.lib/idea-code.ts— atomairnextIdeaCode(userId)via Prisma-transactie.
Embedded prompts
lib/idea-prompts/grill.md— eigen scrum4me-versie. Instrueert: gebruikask_user_questionMCP, schrijf viaupdate_idea_grill_mdaan eind.lib/idea-prompts/make-plan.md— strict yaml-frontmatter-format. Instrueert: leesgrill_md, gebruik repo-files, geen vragen, eindig metupdate_idea_plan_md.
Server actions — actions/ideas.ts
Volg docs/patterns/server-action.md: auth → demo-check → zod → user-id-scope-check → write → revalidatePath.
createIdeaAction(input)—nextIdeaCode(userId), statusDRAFT.updateIdeaAction(id, input)— alleenDRAFT|GRILL_FAILED|GRILLED|PLAN_FAILED|PLAN_READY.archiveIdeaAction(id)/unarchiveIdeaAction(id).deleteIdeaAction(id)— geweigerd alspbi_idgevuld.updateGrillMdAction(id, md)— alleen inGRILLED|PLAN_READY. LogtIdeaLog{NOTE}.updatePlanMdAction(id, md)— alleen inPLAN_READY. EerstparsePlanMd(md); bij parse-fail → 422 met line-info.startGrillJobAction(id)— vereist product metrepo_url,connectedWorkers > 0.ClaudeJob{kind:IDEA_GRILL, idea_id, product_id, QUEUED}. Status →GRILLING. Demo: 403.startMakePlanJobAction(id)— vereistGRILLED|PLAN_FAILED|PLAN_READYvoor re-plan, product met repo, worker. Status →PLANNING. Demo: 403.cancelIdeaJobAction(id)— actieve job CANCELLED, idea-status terug naar vorige.materializeIdeaPlanAction(id)—PLAN_READY→parsePlanMd→ Prisma-$transaction:- Counters incrementeren (PBI/Story/Task).
- INSERT PBI + N stories + M tasks (incl.
implementation_plan). - UPDATE idea:
pbi_id,status:PLANNED. - INSERT
IdeaLog{type:PLAN_RESULT, metadata}. Rollback bij ANY fail. Demo: 403.
relinkIdeaPlanAction(id)— alleen alsstatus===PLANNED && pbi_id===null. Status →PLAN_READY.downloadIdeaMdAction(id, kind: 'grill'|'plan')— server returnt md.
Promote van Todo → Idea
actions/todos.ts: nieuwepromoteTodoToIdeaAction(todoId)— auth + demo + scope. Maakt Idea (DRAFT) met title/description; zet Todoarchived=true. Demo: 403.
REST-routes
app/api/ideas/route.ts(GET, POST) enapp/api/ideas/[id]/route.ts(GET, PATCH).
proxy.ts (demo-laag)
- 403 op
POST/PATCH/DELETE /api/ideas*voor demo-token. - 403 op grill/make-plan/materialize-endpoints.
MCP-laag (scrum4me-mcp-repo)
Nieuwe tools
get_idea_context(idea_id)—{idea, product, repo_url, grill_md_so_far, open_questions, prompt_text}.update_idea_grill_md(idea_id, markdown)— schrijft veld; status →GRILLED; logtIdeaLog{GRILL_RESULT}.update_idea_plan_md(idea_id, markdown)— schrijft veld; parser draait server-side; status →PLAN_READYofPLAN_FAILED.log_idea_decision(idea_id, type, content, metadata?)— types:DECISION | NOTE.
Uitbreiding bestaande tools
ask_user_question: contract uitbreiden — exact één vanstory_idofidea_id.wait_for_job: response uitbreiden:kind: 'TASK_IMPLEMENTATION' | 'IDEA_GRILL' | 'IDEA_MAKE_PLAN'- bij
IDEA_*:idea,product,repo_url,prompt_text.
update_job_status: bijfailedvoor IDEA_*-jobs zet idea-statusGRILL_FAILED/PLAN_FAILED.
Schema-drift
docs/runbooks/mcp-integration.md:62: schema-drift-watchdog moet groen zijn vóór merge. MCP-server-PR parallel.
Realtime-laag
app/api/realtime/notifications/route.ts— idea-questions alleen aanidea.user_id === session.user_id.app/api/realtime/solo/route.ts—JobPayloaduitbreiden metkindenidea_id. Idea-jobs opuser_id.stores/idea-store.ts(nieuw).connectedWorkersdirect uituseSoloStore.
UI-laag
Routing
app/(app)/ideas/page.tsx— top-level lijst.app/(app)/ideas/[id]/page.tsx— detailpagina met tabs Idee · Grill · Plan · Timeline.- Sidebar-entry:
Lightbulb, label "Ideeën", boven Todo's.
Componenten — components/ideas/
idea-list.tsx— TanStack Table; kolommen code/title/product/status/archived. Bulk-archive.idea-row-actions.tsx— Grill Me / Make Plan / Materialiseer / Edit / Archive met disabled-rules.idea-dialog.tsx+components/dialogs/idea-dialog.tsx(wrapper) volgens dialog-pattern.idea-md-editor.tsx— markdown editor met yaml-validate voor plan_md.idea-timeline.tsx— UNION-view IdeaLog + claude_questions.idea-pbi-link-card.tsx— incl. "Re-link plan"-banner.download-md-button.tsx.
Promote-from-Todo UI
components/todos/todo-list.tsx: extra menu-item "Promote naar Idee".
Profiel-doc
docs/specs/dialogs/idea.md— verplicht volgens dialog-pattern.
Te raken / aan te maken bestanden
| Laag | Bestand |
|---|---|
| Schema | prisma/schema.prisma |
| Migratie | prisma/migrations/<ts>_add_ideas/migration.sql |
| Schemas | lib/schemas/idea.ts, lib/idea-status.ts, lib/idea-plan-parser.ts, lib/idea-code.ts |
| Prompts | lib/idea-prompts/grill.md, lib/idea-prompts/make-plan.md |
| Actions | actions/ideas.ts, uitbreiding actions/todos.ts |
| API | app/api/ideas/route.ts, app/api/ideas/[id]/route.ts, proxy.ts |
| Realtime | app/api/realtime/notifications/route.ts, app/api/realtime/solo/route.ts |
| Pages | app/(app)/ideas/page.tsx, app/(app)/ideas/[id]/page.tsx |
| UI | components/ideas/*.tsx, components/dialogs/idea-dialog.tsx, sidebar-update |
| Store | stores/idea-store.ts |
| Docs | docs/specs/dialogs/idea.md, docs/runbooks/mcp-integration.md, docs/backlog/index.md |
| MCP-server | madhura68/scrum4me-mcp (parallel-PR) |
Implementatievolgorde
- DB & migratie
- Lib + schemas + prompts
- Server actions + Todo-promote
- REST + proxy demo-laag
- Realtime SSE + idea-store
- MCP-server tools (extern repo, parallel)
- UI lijst + row-actions
- UI detail + dialog + tabs
- UI promote-from-Todo + sidebar-entry
- End-to-end smoke + docs
Verificatie
npm run lint && npm test && npm run build
End-to-end:
npm run dev+ lokale Claude-CLI metwait_for_job-loop.- Maak idee, koppel aan product. Status
DRAFT. - Grill Me → vragen via answer-modal →
update_idea_grill_md→GRILLED. - Edit grill_md handmatig →
IdeaLog{NOTE}. - Make Plan →
update_idea_plan_md→PLAN_READY. - Yaml-fout in plan_md → save geblokkeerd.
- Materialiseer → PBI + stories + taken in transactie.
idea.pbi_idgezet,PLANNED. - PBI verwijderen → "Re-link plan"-banner →
PLAN_READY. - Demo-test: knoppen geblokkeerd via DemoTooltip + 403.
- Failure-test: kill worker →
GRILL_FAILED/PLAN_FAILED. - Promote-test: Todo → "Promote naar Idee" → DRAFT-Idea, Todo archived.
Open punten (niet-blokkerend)
- Concrete copy-finetuning van prompt-md's tijdens implementatie.
- Lift
connectedWorkersnaar gedeeldeworker-presence-store(opvolg-refactor). - Optionele "Commit plan_md naar repo"-knop (buiten v1).