From 0ac49653c99f912c148afbd1f0fdfc9db65288b8 Mon Sep 17 00:00:00 2001 From: mika kuns Date: Thu, 1 Oct 2026 08:05:58 +0200 Subject: [PATCH] docs(tickets): design for cross-project ticket import with day planning --- .../2026-10-01-ticket-import-all-design.md | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 docs/superpowers/specs/2026-10-01-ticket-import-all-design.md diff --git a/docs/superpowers/specs/2026-10-01-ticket-import-all-design.md b/docs/superpowers/specs/2026-10-01-ticket-import-all-design.md new file mode 100644 index 00000000..07604ee2 --- /dev/null +++ b/docs/superpowers/specs/2026-10-01-ticket-import-all-design.md @@ -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:` 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:`, `CreatedBy = "ticketsystem"`, Beschreibung endet mit + `Ticket #` und bei Kind-Items `Teil von # `. + `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).