7.6 KiB
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/)
Sweep über alle Projekt-Boards — nur Routen, die BandelTicketClient schon hat und die seit
2026-09-01 live verifiziert sind: GET /api/Navigation/sidebar (alle sichtbaren Projekte) →
pro Projekt GET /api/Board/project/{id}. Filter, statusId ∈ {1, 2}:
- Ticket (Board-Item):
ticketAssignees[].assigneeName == userName - Task (
tasks[]am Board-Item):ticketTaskAssignees[].assigneeName== userName — unabhängig davon, wem das Parent-Ticket gehört (730/732 hängen an 728 von marcel.sievers).
Projekt-Titel kommt aus der Sidebar, Parent aus dem umschließenden Board-Item.
TicketBoardItem bekommt Tasks (id, title, statusId, hasChildren,
ticketTaskAssignees).
Live-Befund 2026-10-01 (7 Projekte, ~1,2 s): exakt die 10 offenen Items, die das Ticket-MCP
list_assigned_items liefert — 665, 593 (+595/596/598/668), 1014, 1016, 1017, 730, 732.
Verworfen wurde GET /api/board/global/{userId}: liefert kein Projekt, braucht eine userId, die
/pat/me nicht hergibt (/users/username/{u} = 401 mit PAT), und ist nicht auf den
Token-Inhaber beschränkt (global/22 lieferte 147 fremde Items).
Subtasks (Ebene 3): live existiert keiner (hasChildren überall false). Board-Tasks mit
hasChildren = true werden übersprungen.
Offen, manuell zu prüfen: PATCH /api/Ticket/{id}/status auf einem Task (nicht Ticket) —
nicht live getestet, weil es ein Schreibzugriff aufs Firmensystem ist. Laut Bandel.APIs-Breadcrumb
gleiche Tabelle, Statusrecht u.a. für Assignees. Scheitert es, loggt TicketStatusSync nur eine
Warnung (wirft nie) → beim ersten echten Lauf im Worker-Log prüfen.
Details pro Item für die Beschreibung: GET /api/ticket/{id} (live: liefert description,
parentTicketID).
2. TicketImportService
GetCandidatesAsync(ct)→ zugewiesene offene Items, derenbandel:<id>noch in keiner Liste alsTicketRefexistiert, plus alle User-Listen (Id, Name, WorkingDir, verknüpftes Ticket-Projekt) als Zuordnungskontext.ImportItemAsync(itemId, listId?, ct)→ lädt Details viaGET /api/ticket/{id}(Beschreibung, HTML→Markdown über das bestehendeHtmlToMarkdown), legt einenIdle-Task an:TicketRef = bandel:<id>,CreatedBy = "ticketsystem", Beschreibung endet mitTicket #<id>und bei Kind-ItemsTeil 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 überTicketReflistenübergreifend statt nurt.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 = falsemit Grund, wenn das Ticketsystem nicht eingerichtet ist.import_ticket(ticketId, list = null)→ Bestätigungs-Record (created,taskNumber,listName,usedFallback,skippedReason).listakzeptiert 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-Wertimport_tickets(Converter inPrimeScheduleEntityConfiguration).TicketImportPrompt(analogDailyPrepPrompt): Prompt überPromptFiles(PromptKind.TicketImport), Args--allowedToolsnur die zwei Tools,--permission-mode acceptEdits. Prompt-Kern: Kandidaten holen, jedes Item der inhaltlich passenden Liste zuordnen (Projekt-Verknüpfung = Hinweis), bei Unsicherheitlistweglassen, 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 fixImportTickets, wieRunDailyPrepNow);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 wiePlanDayCommand. - 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 überticketAssignees, 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).