Files
ClaudeDo/docs/superpowers/specs/2026-10-01-ticket-import-all-design.md

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, 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).