docs(tickets): design for cross-project ticket import with day planning
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
# Ticket-Import über alle Projekte + Tagesplanung — Design
|
||||
|
||||
**Datum:** 2026-10-01 · **Status:** abgenommen
|
||||
|
||||
## Ziel
|
||||
|
||||
Ein Lauf holt **alle** Mika zugewiesenen, offenen Ticket-Items (Tickets, Tasks, Subtasks) aus dem
|
||||
Bandel-Ticketsystem, lässt Claude jedes Item nach **Inhalt** der passenden ClaudeDo-Liste zuordnen
|
||||
und plant danach den Tag (bestehender FillMyDay-Lauf). Auslösbar per Button und als Prime-Schedule.
|
||||
|
||||
## Warum nicht der bestehende Import
|
||||
|
||||
`TicketImportService.ImportAsync` arbeitet pro Liste über die feste Verknüpfung
|
||||
`ListConfigEntity.TicketProjectId` und nur für Board-Items vom Typ Ticket. Live-Stand 2026-10-01:
|
||||
|
||||
- 6 von 10 offenen, zugewiesenen Items sind Tasks/Subtasks (z.B. 595/596/598/668 unter
|
||||
„Lager Technik Plugin“) — der heutige Import sieht sie nie.
|
||||
- Projekt „Lager“ (493) enthält Items für mehrere Repos (Hub Lagertechnik, SorterController) und
|
||||
ist mit zwei Listen verknüpft. Projekt→Liste ist die falsche Zuordnungsachse; Inhalt ist die
|
||||
richtige.
|
||||
- DueDate ist fast überall null, Priorität fast überall „Niedrig“ — was heute dran ist, entscheidet
|
||||
weiterhin FillMyDay, nicht die Ticketdaten.
|
||||
|
||||
## Entscheidungen
|
||||
|
||||
| Frage | Entscheidung |
|
||||
|---|---|
|
||||
| Zuordnung | Claude nach Inhalt (Titel, Parent, Projekt). Projekt-Verknüpfung einer Liste ist nur ein Hinweis. |
|
||||
| Granularität | Flach: jedes zugewiesene Ticket/Task/Subtask wird ein eigener ClaudeDo-Task. Parent steht in der Beschreibung. Keine Hierarchie. |
|
||||
| Ablauf | Ein Lauf, zwei Schritte: Zuordnungs-Lauf, danach FillMyDay — im selben `PrimeRunner`-Gate. |
|
||||
| Kein Treffer | Item geht in die **Fallback-Liste** (App-Setting). Signal für Mika: dafür fehlt eine Liste. Ist keine gesetzt → übersprungen und in der Zusammenfassung genannt. |
|
||||
| Fallback anwenden | Der **Worker**, nicht Claude: `import_ticket` ohne `list` → Fallback-Liste. |
|
||||
|
||||
## Komponenten
|
||||
|
||||
### 1. Datenquelle (`Worker/Tickets/`)
|
||||
|
||||
`BandelTicketClient.GetAssignedItemsAsync(ct)`:
|
||||
`GET /api/ticketsystem/pat/me` (userName) → `GET /api/ticketsystem/users/username/{u}` (userId) →
|
||||
`GET /api/board/global/{userId}`. Filter wie bisher: `ticketAssignees[].assigneeName == userName`
|
||||
und `statusId ∈ {1, 2}`.
|
||||
|
||||
> ⚠️ **Erster Implementierungsschritt: live verifizieren**, bevor ein DTO geschrieben wird —
|
||||
> liefert das globale Board Tasks/Subtasks (itemType 2/3) samt `parentId`/Parent-Titel und
|
||||
> Projekt? Feldweise über alle Datensätze prüfen, nicht an einem Sample (siehe Memory
|
||||
> `ticketsystem-integration`: `assigneeName` war im DTO vorhanden und live immer null).
|
||||
> Fallback, falls nicht: das Ticket-MCP `list_assigned_items` zeigt, dass die Daten existieren —
|
||||
> dessen Route in `Bandel.APIs`/TicketSystem-MCP nachschlagen.
|
||||
|
||||
Ebenfalls live prüfen: `PATCH /api/ticket/{id}/status` für ein Item vom Typ Task (Write-back muss
|
||||
für Tasks/Subtasks funktionieren; laut Bandel.APIs-Breadcrumb dieselbe Tabelle).
|
||||
|
||||
### 2. `TicketImportService`
|
||||
|
||||
- `GetCandidatesAsync(ct)` → zugewiesene offene Items, deren `bandel:<id>` noch **in keiner Liste**
|
||||
als `TicketRef` existiert, plus alle User-Listen (Id, Name, WorkingDir, verknüpftes
|
||||
Ticket-Projekt) als Zuordnungskontext.
|
||||
- `ImportItemAsync(itemId, listId?, ct)` → lädt Details via `GET /api/ticket/{id}` (Beschreibung,
|
||||
HTML→Markdown über das bestehende `HtmlToMarkdown`), legt einen `Idle`-Task an:
|
||||
`TicketRef = bandel:<id>`, `CreatedBy = "ticketsystem"`, Beschreibung endet mit
|
||||
`Ticket #<id>` und bei Kind-Items `Teil von #<parentId> <parentTitle>`.
|
||||
`listId == null` → `AppSettings.TicketFallbackListId`; ist die auch null → kein Task, Ergebnis
|
||||
meldet „übersprungen“. Bereits importiert (global) → No-op mit entsprechender Meldung.
|
||||
- **Fix im bestehenden `ImportAsync`:** Dedupe über `TicketRef` **listenübergreifend** statt nur
|
||||
`t.ListId == listId` — sonst legt „Aus Ticketsystem aktualisieren“ ein bereits global
|
||||
zugeordnetes Item ein zweites Mal an.
|
||||
|
||||
### 3. MCP-Tools (External-MCP, neue `External/TicketMcpTools`)
|
||||
|
||||
- `get_ticket_import_candidates()` → `{ available, items[], lists[], fallbackList }`.
|
||||
`available = false` mit Grund, wenn das Ticketsystem nicht eingerichtet ist.
|
||||
- `import_ticket(ticketId, list = null)` → Bestätigungs-Record (`created`, `taskNumber`,
|
||||
`listName`, `usedFallback`, `skippedReason`). `list` akzeptiert Name oder Id.
|
||||
|
||||
Konventionen aus `src/ClaudeDo.Worker/CLAUDE.md` gelten (C#-Default für optionale Parameter,
|
||||
kein nacktes `Task`-Return, Tool-Doku nach `McpToolDocs`).
|
||||
|
||||
### 4. Ablauf (`Worker/Prime/`)
|
||||
|
||||
- `PrimeActionKind.ImportTickets`, DB-Wert `import_tickets` (Converter in
|
||||
`PrimeScheduleEntityConfiguration`).
|
||||
- `TicketImportPrompt` (analog `DailyPrepPrompt`): Prompt über `PromptFiles`
|
||||
(`PromptKind.TicketImport`), Args `--allowedTools` nur die zwei Tools, `--permission-mode
|
||||
acceptEdits`. Prompt-Kern: Kandidaten holen, jedes Item der inhaltlich passenden Liste zuordnen
|
||||
(Projekt-Verknüpfung = Hinweis), bei Unsicherheit `list` weglassen, am Ende Zusammenfassung
|
||||
(importiert pro Liste, Fallback, übersprungen).
|
||||
- `PrimeRunner.RunImportTicketsAsync`: Log einmal leeren, `PrepStarted`, Zuordnungs-Lauf, dann den
|
||||
FillMyDay-Teil (bestehende Logik ohne erneutes Leeren/`PrepStarted`), `PrepFinished`. Schlägt
|
||||
der Import fehl, läuft FillMyDay trotzdem (bestehende Tasks sind weiter planbar); das Outcome
|
||||
meldet den Importfehler.
|
||||
- Ist das Ticketsystem nicht eingerichtet, fällt der Lauf auf reines FillMyDay zurück.
|
||||
|
||||
### 5. Hub + UI
|
||||
|
||||
- `WorkerHub.RunTicketImportNow()` (synthetischer Schedule, Kind fix `ImportTickets`, wie
|
||||
`RunDailyPrepNow`); `IWorkerClient`/`WorkerClient` + Test-Fakes nachziehen.
|
||||
- Prep-Panel (`DetailsIslandView`, neben „Plan day“): Button „Tickets + Tag planen“, nur sichtbar
|
||||
wenn das Ticketsystem eingerichtet ist; gleiche Enable-Logik wie `PlanDayCommand`.
|
||||
- Prime-Tab: neuer Aktionstyp wählbar.
|
||||
- Settings → Ticketsystem: Dropdown „Fallback-Liste“ (User-Listen + „keine“).
|
||||
- `AppSettingsEntity.TicketFallbackListId` (nullable) + EF-Migration. Locale-Keys en + de.
|
||||
|
||||
## Tests
|
||||
|
||||
- `GetCandidatesAsync`: Status-Filter, Assignee-Filter über `ticketAssignees`, globale Dedupe.
|
||||
- `ImportItemAsync`: explizite Liste, Fallback, keine Fallback-Liste → übersprungen, Duplikat.
|
||||
- `ImportAsync`: Item bereits in anderer Liste → nicht erneut angelegt.
|
||||
- `PrimeRunner`: `ImportTickets` → zwei Claude-Läufe in Reihenfolge, ein Gate, Import-Fehler
|
||||
blockiert FillMyDay nicht.
|
||||
- Fake-Client/Fake-Claude, **kein** echtes Claude. Live-Verifikation (Schritt 1) und
|
||||
Sichtprüfung der UI sind manuelle Punkte → `docs/open.md`.
|
||||
|
||||
## Bewusst nicht drin
|
||||
|
||||
- Rückabgleich „Ticket im Ticketsystem erledigt, Task in ClaudeDo noch offen“.
|
||||
- Ticket-Hierarchie als ClaudeDo-Parent/Subtasks.
|
||||
- Aktualisieren bereits importierter Items (würde Notizen überschreiben — gleiche Regel wie heute).
|
||||
Reference in New Issue
Block a user