docs(tickets): document cross-project ticket import and open checks
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
# External MCP tool surface
|
# External MCP tool surface
|
||||||
|
|
||||||
> **Explore-note — verify before trusting.** Distilled map of a subsystem, not authoritative.
|
> **Explore-note — verify before trusting.** Distilled map of a subsystem, not authoritative.
|
||||||
> Last verified against commit `38af549` (2026-08-11).
|
> Last verified against commit `cb775958` (2026-10-01).
|
||||||
> Drift check: `git log --oneline 38af549..HEAD -- src/ClaudeDo.Worker/External`
|
> Drift check: `git log --oneline cb775958..HEAD -- src/ClaudeDo.Worker/External`
|
||||||
> Stable structure only (no line numbers). See docs/explore-notes/README.md.
|
> 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
|
Covers `src/ClaudeDo.Worker/External/` — the always-on MCP tools ClaudeDo exposes to general
|
||||||
@@ -99,6 +99,7 @@ Daily prep: `GetDailyPrepCandidates`, `SetMyDay`.
|
|||||||
| `QueueStateMcpTools` | `GetQueueState` |
|
| `QueueStateMcpTools` | `GetQueueState` |
|
||||||
| `AttachmentMcpTools` | `AddTaskAttachment`, `ListTaskAttachments`, `RemoveTaskAttachment` |
|
| `AttachmentMcpTools` | `AddTaskAttachment`, `ListTaskAttachments`, `RemoveTaskAttachment` |
|
||||||
| `ListHandlerMcpTools` | `StartListHandler` |
|
| `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
|
## Per-tool behaviour worth knowing
|
||||||
|
|
||||||
|
|||||||
@@ -74,6 +74,17 @@ Bleibt manuell zu prüfen (UI-Pfad, nie sichtbar gelaufen):
|
|||||||
- [ ] Base-URL leeren → Menüeintrag verschwindet, keine Netzwerkaufrufe mehr.
|
- [ ] Base-URL leeren → Menüeintrag verschwindet, keine Netzwerkaufrufe mehr.
|
||||||
- [ ] Sichtprüfung des Settings-Tabs + des Ticket-Projekt-Dropdowns (nie gerendert gesehen).
|
- [ ] 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)
|
## Offene Verifikation (2026-07-27)
|
||||||
|
|
||||||
- **Empty-Repo-Hardening (2026-07-30)** — Worker/App neu starten (laufende Instanz hat den
|
- **Empty-Repo-Hardening (2026-07-30)** — Worker/App neu starten (laufende Instanz hat den
|
||||||
|
|||||||
@@ -144,21 +144,25 @@ a follow-up prompt to an existing session via `--resume <session_id>`.
|
|||||||
## Prime Claude
|
## Prime Claude
|
||||||
|
|
||||||
Every schedule carries a `PrimeActionKind` (`Ping` — the default for new schedules — `FillMyDay`,
|
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:
|
branches:
|
||||||
|
|
||||||
| Kind | Prompt | Args | Log + `Prep*` events |
|
| Kind | Prompt | Args | Log + `Prep*` events |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `Ping` | `PrimePrompts.PingPrompt`, fixed | `--max-turns 1 --strict-mcp-config` (no MCP server loads at all) | none |
|
| `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` |
|
| `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 |
|
| `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
|
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`.
|
kinds still broadcast `PrimeFired` and update `last_run_at`.
|
||||||
|
|
||||||
`WorkerHub.RunDailyPrepNow` (the "Plan day" button) pins its synthetic schedule to `FillMyDay` —
|
`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.
|
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`
|
`WorkerHub.UpsertPrimeSchedule` runs `PrimeScheduleValidation.Validate` first, so a `Custom`
|
||||||
schedule can never be saved without a prompt.
|
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
|
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
|
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.
|
`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
|
- **Write-back** (`TicketStatusSync`) hangs off `TaskStateService.NotifyAsync` — the one place every
|
||||||
status transition passes through. Mapping: `Running`/`WaitingForReview` → ticket status `2
|
status transition passes through. Mapping: `Running`/`WaitingForReview` → ticket status `2
|
||||||
InBearbeitung`, `Done` → `3 Fertig`, everything else writes nothing (`1 Offen` is never written
|
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:<id>`, is a no-op before any DB read. Per-process de-dupe (`taskId → last-written status`)
|
`bandel:<id>`, 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
|
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.
|
a warning and does not block the status transition or the queue.
|
||||||
- `BandelTicketClient` wraps the four REST calls (`GET /api/ticketsystem/pat/me`, `GET
|
- `BandelTicketClient` wraps the five REST calls (`GET /api/ticketsystem/pat/me`, `GET
|
||||||
/api/Navigation/sidebar`, `GET /api/Board/project/{id}`, `PATCH /api/Ticket/{id}/status`); every
|
/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<T>` and a non-2xx/`success:false` throws
|
response is unwrapped from `BandelEnvelope<T>` 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`.
|
`http://192.168.45.161`.
|
||||||
⚠️ `PATCH /api/Ticket/{id}/status` needs **both** `statusId` and `statusName` — omitting the name
|
⚠️ `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*
|
is a 400 (`"The StatusName field is required."`) and no status is written. The name is *not*
|
||||||
|
|||||||
Reference in New Issue
Block a user