Added pdevelopment docs
This commit is contained in:
parent
e6c3dc11b3
commit
7837652530
3 changed files with 481 additions and 1 deletions
133
docs/plans/ST-1114-copilot-reviews.md
Normal file
133
docs/plans/ST-1114-copilot-reviews.md
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
# Plan — ST-1114 · Copilot reviews op dashboard
|
||||
|
||||
## Context
|
||||
|
||||
Als ontwerper wil je een overzicht zien van GitHub Copilot's PR-reviews om per stuk te beslissen of je 'm implementeert of overslaat. De codebase heeft nu **nul** GitHub-integratie — alleen `product.repo_url` als string voor hyperlinks. We bouwen een minimale, hobby-vriendelijke architectuur.
|
||||
|
||||
## Architectuurkeuzes (via AskUserQuestion bevestigd)
|
||||
|
||||
- **Auth**: lokaal script met `GITHUB_TOKEN` — webapp heeft GEEN GitHub-credentials. Het script draai je lokaal wanneer je wil verversen.
|
||||
- **Fetch**: on-demand op dashboard-load (server-side `prisma.copilotReview.findMany`, geen externe call)
|
||||
- **Decision**: alleen visuele toggle in `localStorage` (geen DB-persistentie)
|
||||
- **Scope**: MVP — tonen + lokale toggle. Geen cron, geen webhook, geen GitHub-auth in productie.
|
||||
|
||||
## Architectuur
|
||||
|
||||
```
|
||||
┌──────────────┐ octokit ┌────────────┐ API token ┌─────────────┐
|
||||
│ scripts/ │ ──────────▶ │ GitHub │ │ Scrum4Me │
|
||||
│ sync-copilot │ │ REST API │ │ /api/ │
|
||||
│ -reviews.ts │ ◀────────── │ │ │ copilot- │
|
||||
└──────────────┘ reviews └────────────┘ POST batch │ reviews │
|
||||
│ │ │
|
||||
└──────────────────────────────────────────────────▶ DB upsert │
|
||||
└──────┬──────┘
|
||||
│
|
||||
┌──────▼──────┐
|
||||
│ /dashboard │
|
||||
│ server-side │
|
||||
│ findMany │
|
||||
└─────────────┘
|
||||
```
|
||||
|
||||
Het script is de enige plek waar GitHub-credentials nodig zijn. Productie kent alleen Scrum4Me-data.
|
||||
|
||||
## Datamodel
|
||||
|
||||
```prisma
|
||||
model CopilotReview {
|
||||
id String @id @default(cuid())
|
||||
product Product @relation(fields: [product_id], references: [id], onDelete: Cascade)
|
||||
product_id String
|
||||
pr_number Int
|
||||
pr_title String
|
||||
pr_url String
|
||||
pr_state String // 'open' | 'closed' | 'merged'
|
||||
author_login String // bv. 'copilot-pull-request-reviewer[bot]'
|
||||
review_state String // 'COMMENTED' | 'APPROVED' | 'CHANGES_REQUESTED'
|
||||
body String @db.Text
|
||||
submitted_at DateTime
|
||||
html_url String // directe link naar de review
|
||||
fetched_at DateTime @default(now())
|
||||
|
||||
@@unique([product_id, pr_number, submitted_at])
|
||||
@@index([product_id, submitted_at])
|
||||
@@map("copilot_reviews")
|
||||
}
|
||||
```
|
||||
|
||||
`@@unique` zorgt voor idempotency — script kan herhaald draaien zonder dupes. Geen `decision`-veld: dat staat in `localStorage`.
|
||||
|
||||
## Script
|
||||
|
||||
`scripts/sync-copilot-reviews.ts` — TypeScript via `tsx`, leest env, gebruikt Octokit, POST naar eigen API.
|
||||
|
||||
```bash
|
||||
GITHUB_TOKEN=ghp_... \
|
||||
SCRUM4ME_API_URL=http://localhost:3000 \
|
||||
SCRUM4ME_API_TOKEN=s4m_... \
|
||||
npx tsx scripts/sync-copilot-reviews.ts
|
||||
```
|
||||
|
||||
Stappen:
|
||||
1. `GET /api/products` (Bearer-auth) — lijst toegankelijke producten met `repo_url`
|
||||
2. Per product: parse `owner/repo` uit `repo_url`, `octokit.pulls.list({state: 'all', per_page: 50})`
|
||||
3. Per PR: `octokit.pulls.listReviews(...)`, filter op `user.type === 'Bot' && user.login.includes('copilot')`
|
||||
4. `POST /api/copilot-reviews` met `{ product_id, reviews: [...] }` — endpoint doet `deleteMany` + `createMany` per product (atomic replace)
|
||||
5. Print samenvatting: aantal reviews per product + totale runtime
|
||||
|
||||
## API endpoint
|
||||
|
||||
`app/api/copilot-reviews/route.ts`:
|
||||
|
||||
- **POST**: Bearer-auth, demo-block, payload `{ product_id, reviews: CopilotReview[] }`. Atomic transaction: delete-all-for-product → createMany. Validatie via Zod.
|
||||
- **GET**: niet nodig — dashboard leest direct via Prisma server-side. Endpoint kan komen voor toekomstige clients.
|
||||
|
||||
## Dashboard widget
|
||||
|
||||
Boven of onder de bestaande product-grid een nieuwe sectie "Copilot reviews".
|
||||
|
||||
`components/dashboard/copilot-reviews.tsx` (client component):
|
||||
- Props: `reviews: CopilotReview[]` (server-fetched)
|
||||
- Lijst met cards: PR-titel + nummer (link naar PR), Copilot's body (truncated of accordion), state-badge, "Implementeer" / "Skip"-knoppen
|
||||
- Decision-state in `localStorage` keyed op `review.id`: `'implement' | 'skip' | undefined` (default: ongezien)
|
||||
- Cards met decision='skip' visueel gedimmed; cards met 'implement' krijgen een groen randje
|
||||
- Filter-toggles bovenaan: "Alle / Te beoordelen / Implementeren / Skip"
|
||||
- Empty state: "Geen Copilot-reviews gevonden — draai het sync-script."
|
||||
|
||||
`app/(app)/dashboard/page.tsx` past `prisma.copilotReview.findMany({ where: { product_id: { in: accessibleIds } }, orderBy: { submitted_at: 'desc' } })` en geeft door.
|
||||
|
||||
## Voorgestelde sub-tasks
|
||||
|
||||
| Code | Onderwerp |
|
||||
|---|---|
|
||||
| ST-1114.2 | DB: `CopilotReview` model + migration |
|
||||
| ST-1114.3 | API: `POST /api/copilot-reviews` (Bearer-auth + demo-block + replace-by-product) |
|
||||
| ST-1114.4 | Script: `scripts/sync-copilot-reviews.ts` met octokit |
|
||||
| ST-1114.5 | UI: dashboard-widget met cards, localStorage-decision, filter-toggle |
|
||||
| ST-1114.6 | Tests: API endpoint (auth, demo-block, validation), dashboard-widget snapshot |
|
||||
| ST-1114.7 | Docs: README-sectie over script + env-vars; CLAUDE.md-update |
|
||||
|
||||
## M11-keuzes voor de implementerende sessie
|
||||
|
||||
Drie open beslissingen die niet kritiek zijn voor het plan zelf:
|
||||
|
||||
1. **PR-state filter**: alle PR's of alleen `state=open`? (closed-PRs hebben oude reviews die misschien niet meer relevant zijn)
|
||||
2. **Markdown-rendering**: react-markdown, of plain `<pre>`? (react-markdown is +35KB bundle)
|
||||
3. **localStorage-key-vorm**: `scrum4me:copilot-decision:{review_id}` per review, of één map-object onder één key?
|
||||
|
||||
## Branch + PR
|
||||
|
||||
- Branch: `feat/M14-copilot-reviews` (M14 = nieuwe milestone)
|
||||
- 6 commits (.2 t/m .7), één per laag
|
||||
- PR pas openen na handmatige test door gebruiker
|
||||
|
||||
## Verificatie (end-to-end)
|
||||
|
||||
1. `npm run dev`
|
||||
2. `GITHUB_TOKEN=... SCRUM4ME_API_TOKEN=... npx tsx scripts/sync-copilot-reviews.ts` — toont `n reviews opgeslagen`
|
||||
3. Browser refresht dashboard → "Copilot reviews"-sectie toont cards met PR-titels
|
||||
4. Klik "Implementeer" → kaart krijgt groen randje, decision in localStorage
|
||||
5. Refresh → state blijft (localStorage)
|
||||
6. Filter toggle "Alleen te beoordelen" → cards met decision verdwijnen
|
||||
7. Demo-user: kan reviews zien, maar `POST /api/copilot-reviews` weigert (al via middleware-guard van ST-1110)
|
||||
346
docs/plans/Tweede Claude Agent — Planning Agent.md
Normal file
346
docs/plans/Tweede Claude Agent — Planning Agent.md
Normal file
|
|
@ -0,0 +1,346 @@
|
|||
# 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`:**
|
||||
|
||||
```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:
|
||||
|
||||
```ts
|
||||
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:**
|
||||
|
||||
```ts
|
||||
// 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:
|
||||
|
||||
```ts
|
||||
{
|
||||
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/scrum4me-functional-spec.md` (functioneel kader)
|
||||
- `docs/scrum4me-architecture.md` (technisch kader)
|
||||
- `docs/patterns/*.md` (relevante patterns op basis van target-titel/-beschrijving)
|
||||
- `docs/scrum4me-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/scrum4me-architecture.md` | MODIFY | Sectie "Claude Agents" uitbreiden — twee rollen, schema, queue, prompts |
|
||||
| `docs/scrum4me-pbi-dialog.md` | MODIFY | Sectie "Speciale gedragingen → Planning-trigger" toevoegen |
|
||||
| `docs/scrum4me-story-dialog.md` | MODIFY | Idem |
|
||||
| `docs/scrum4me-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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue