diff --git a/docs/explore-notes/external-mcp.md b/docs/explore-notes/external-mcp.md index ad593bb0..e668ebce 100644 --- a/docs/explore-notes/external-mcp.md +++ b/docs/explore-notes/external-mcp.md @@ -1,8 +1,8 @@ # External MCP tool surface > **Explore-note — verify before trusting.** Distilled map of a subsystem, not authoritative. -> Last verified against commit `38af549` (2026-08-11). -> Drift check: `git log --oneline 38af549..HEAD -- src/ClaudeDo.Worker/External` +> Last verified against commit `cb775958` (2026-10-01). +> Drift check: `git log --oneline cb775958..HEAD -- src/ClaudeDo.Worker/External` > Stable structure only (no line numbers). See docs/explore-notes/README.md. Covers `src/ClaudeDo.Worker/External/` — the always-on MCP tools ClaudeDo exposes to general @@ -99,6 +99,7 @@ Daily prep: `GetDailyPrepCandidates`, `SetMyDay`. | `QueueStateMcpTools` | `GetQueueState` | | `AttachmentMcpTools` | `AddTaskAttachment`, `ListTaskAttachments`, `RemoveTaskAttachment` | | `ListHandlerMcpTools` | `StartListHandler` | +| `TicketMcpTools` | `GetTicketImportCandidates` (open assigned ticket-system items not yet imported + all lists + fallback list; `available=false` + `reason` when no ticket system), `ImportTicket` (one Idle task per item; no `list` → fallback list; `created=false` always carries `skippedReason`) | ## Per-tool behaviour worth knowing diff --git a/docs/open.md b/docs/open.md index 6406e337..2fad2d0c 100644 --- a/docs/open.md +++ b/docs/open.md @@ -74,6 +74,17 @@ Bleibt manuell zu prüfen (UI-Pfad, nie sichtbar gelaufen): - [ ] Base-URL leeren → Menüeintrag verschwindet, keine Netzwerkaufrufe mehr. - [ ] Sichtprüfung des Settings-Tabs + des Ticket-Projekt-Dropdowns (nie gerendert gesehen). +## Offene Verifikation (2026-10-01, Ticket-Import über alle Projekte) + +Build + Unit-Tests grün, **nicht visuell/live verifiziert**: + +- [ ] Sichtprüfung: Button „Tickets + Tag planen" im Prep-Panel (nur sichtbar mit Base-URL + Token), + Fallback-Listen-Dropdown in Settings → Ticketsystem, Radio „Tickets + Tag planen" im Prime-Tab. +- [ ] E2E: Button einmal gegen das echte Ticketsystem klicken — Items landen in sinnvollen Listen, die + Fallback-Liste fängt den Rest, danach ist MyDay gefüllt; ein zweiter Klick importiert nichts Neues. +- [ ] `PATCH /api/Ticket/{id}/status` auf einem **Task** (nicht Ticket) ist ungetestet — nachdem der erste + importierte Task den Status wechselt, im Worker-Log auf eine `TicketStatusSync`-Warnung prüfen. + ## Offene Verifikation (2026-07-27) - **Empty-Repo-Hardening (2026-07-30)** — Worker/App neu starten (laufende Instanz hat den diff --git a/src/ClaudeDo.Worker/CLAUDE.md b/src/ClaudeDo.Worker/CLAUDE.md index 8ba4789f..40eb9788 100644 --- a/src/ClaudeDo.Worker/CLAUDE.md +++ b/src/ClaudeDo.Worker/CLAUDE.md @@ -144,21 +144,25 @@ a follow-up prompt to an existing session via `--resume `. ## Prime Claude Every schedule carries a `PrimeActionKind` (`Ping` — the default for new schedules — `FillMyDay`, -or `Custom`). `PrimeRunner.FireAsync` holds one `SemaphoreSlim` gate for **all** kinds, then +`ImportTickets`, or `Custom`). `PrimeRunner.FireAsync` holds one `SemaphoreSlim` gate for **all** kinds, then branches: | Kind | Prompt | Args | Log + `Prep*` events | |---|---|---|---| | `Ping` | `PrimePrompts.PingPrompt`, fixed | `--max-turns 1 --strict-mcp-config` (no MCP server loads at all) | none | | `FillMyDay` | `DailyPrepPrompt.BuildPrompt` (+ the schedule's prompt as an *addition*) | the two MyDay MCP tools | `daily-prep.log`, truncated per run, + `PrepStarted`/`PrepLine`/`PrepFinished` | +| `ImportTickets` | `TicketImportPrompt.BuildPrompt`, then the FillMyDay prompt (override appended to both) | step 1: `get_ticket_import_candidates` + `import_ticket`, max-turns 60; step 2: as FillMyDay | one `daily-prep.log` for both steps, one `PrepStarted`/`PrepFinished` | | `Custom` | the schedule's prompt *verbatim* | `--allowedTools mcp__claudedo` — no Read/Write/Edit/Bash | none | Ping and Custom deliberately write **no** log and raise **no** `Prep*` events: the prep log -belongs to the MyDay selection and would otherwise be overwritten with ping noise. All three +belongs to the MyDay selection and would otherwise be overwritten with ping noise. All four kinds still broadcast `PrimeFired` and update `last_run_at`. `WorkerHub.RunDailyPrepNow` (the "Plan day" button) pins its synthetic schedule to `FillMyDay` — the button means "fill my day" no matter how the schedules are configured. +`WorkerHub.RunTicketImportNow` (the "Tickets + plan day" button) does the same for `ImportTickets`. +A failed import step never blocks the FillMyDay step; without a configured ticket system the kind +runs plain FillMyDay. `WorkerHub.UpsertPrimeSchedule` runs `PrimeScheduleValidation.Validate` first, so a `Custom` schedule can never be saved without a prompt. @@ -287,6 +291,12 @@ distinguish the two singletons). The token never touches `worker.config.json` or 2026-09-01), so the original filter imported nothing, ever. `personInChargeName` is the wrong source too: it holds the login on some rows and the display name ("Mika Kuns") on others, while `ticketAssignees[].assigneeName` matches `/pat/me`'s `userName` format exactly. +- **Cross-project import** (Prime `ImportTickets`, MCP `get_ticket_import_candidates` / `import_ticket`) — + `TicketImportService.GetCandidatesAsync`/`ImportItemAsync` sweep sidebar → every project board. Tickets + match on `ticketAssignees`, tasks (`tasks[]`) on `ticketTaskAssignees`; board tasks with `hasChildren` are + skipped. Dedupe on `TicketRef` is across all lists (also in the per-list `ImportAsync`). `import_ticket` + without `list` uses `AppSettings.TicketFallbackListId` (written only via `SetTicketFallbackListIdAsync` / + hub `SetTicketFallbackList`, never by `UpdateAsync`); with no fallback set the item is skipped. - **Write-back** (`TicketStatusSync`) hangs off `TaskStateService.NotifyAsync` — the one place every status transition passes through. Mapping: `Running`/`WaitingForReview` → ticket status `2 InBearbeitung`, `Done` → `3 Fertig`, everything else writes nothing (`1 Offen` is never written @@ -294,10 +304,11 @@ distinguish the two singletons). The token never touches `worker.config.json` or `bandel:`, is a no-op before any DB read. Per-process de-dupe (`taskId → last-written status`) skips the redundant `Running → WaitingForReview` PATCH. Never throws — a ticket-system failure logs a warning and does not block the status transition or the queue. -- `BandelTicketClient` wraps the four REST calls (`GET /api/ticketsystem/pat/me`, `GET - /api/Navigation/sidebar`, `GET /api/Board/project/{id}`, `PATCH /api/Ticket/{id}/status`); every +- `BandelTicketClient` wraps the five REST calls (`GET /api/ticketsystem/pat/me`, `GET + /api/Navigation/sidebar`, `GET /api/Board/project/{id}`, `GET /api/ticket/{id}` — works for tasks too, + live-verified 2026-10-01 — `PATCH /api/Ticket/{id}/status`); every response is unwrapped from `BandelEnvelope` and a non-2xx/`success:false` throws - `TicketApiException` with a readable message. All four are **live-verified (2026-09-01)** against + `TicketApiException` with a readable message. The first four are **live-verified (2026-09-01)** against `http://192.168.45.161`. ⚠️ `PATCH /api/Ticket/{id}/status` needs **both** `statusId` and `statusName` — omitting the name is a 400 (`"The StatusName field is required."`) and no status is written. The name is *not*