docs: Ticketsystem-Anbindung in den CLAUDE.md-Dateien

Tickets/-Ordner, TaskStateService.NotifyAsync, ticket_api_base_url und TicketRef/
TicketProjectId dokumentiert. Verbatim-Copy-Warnung um TicketProjectId ergänzt und
den PermissionMode-Vorbestandsbug (SetConfigAsync liess das Feld im Update-Zweig
aus, gefixt in f0a3a186) vermerkt. docs/open.md um die fuenf offenen Punkte aus der
manuellen Verifikation des Specs ergänzt, inkl. Hinweis dass die Bandel-Response-
Shapes nie live verifiziert wurden.
This commit is contained in:
mika kuns
2026-08-27 13:40:29 +02:00
parent e91ade9cad
commit fb1ceac192
3 changed files with 66 additions and 10 deletions
+40 -7
View File
@@ -36,6 +36,10 @@ Worker/
Prime/ — "Prime Claude" schedules: PrimeScheduler, PrimeRunner, PrimePrompts,
DailyPrepPrompt, PrimeScheduleValidation, NextDueCalculator, PrimeScheduleSignal
Online/ — optional Online Inbox sync (off by default; zero network when disabled)
Tickets/ — Bandel ticket-system integration (per-list import + status write-back),
inactive without ticket_api_base_url: TicketSystemConfig/TicketClientFactory,
BandelTicketClient, TicketDtos, TicketStatusMap, TicketStatusSync,
TicketImportService
Usage/ — OAuth usage monitor, gate, throttle, per-session token reader;
TokenTracker/ = the external analytics backend (cost + per-model/per-task breakdown)
```
@@ -46,7 +50,7 @@ subfolder within their area; the namespace stays the area namespace.
## Architecture
- **Program.cs** — loads config, inits schema, registers DI, configures SignalR on `/hub`, binds to `127.0.0.1:47821`
- **TaskStateService** — the **only** component that writes `Status`, `PlanningPhase`, `BlockedByTaskId`, `DependsOnTaskId`. All transitions return a `TransitionResult` (no exceptions on invalid moves). Wakes the queue and broadcasts `TaskUpdated` automatically; advances the planning chain on child terminal transitions. `SetDependsOnAsync` rejects a self-reference, an unknown dependency id, or a link that would create a cycle (walks the proposed predecessor's own `DependsOnTaskId` chain).
- **TaskStateService** — the **only** component that writes `Status`, `PlanningPhase`, `BlockedByTaskId`, `DependsOnTaskId`. All transitions return a `TransitionResult` (no exceptions on invalid moves). Wakes the queue and advances the planning chain on child terminal transitions. Every transition broadcasts via a private `NotifyAsync(taskId)` — never call `_broadcaster.TaskUpdated` directly from this class, `NotifyAsync` also drives `TicketStatusSync.SyncAsync` (see Tickets below) and a direct call skips that transition's ticket write-back. `SetDependsOnAsync` rejects a self-reference, an unknown dependency id, or a link that would create a cycle (walks the proposed predecessor's own `DependsOnTaskId` chain).
- **IQueueWaker / IQueuePicker / QueueService** — waker is a singleton `SemaphoreSlim`; picker performs the atomic `Queued → Running` claim filtered by `BlockedByTaskId IS NULL`, `is_manual = 0`, schedule, and (`DependsOnTaskId IS NULL` OR the dependency's `Status = 'done'`); QueueService is a thin `BackgroundService` looping on the waker, dispatching via `TaskRunner`. Per tick it also applies the usage throttle and gate → [usage-monitoring](../../docs/explore-notes/usage-monitoring.md).
- **RunCancellationRegistry** — taskId → running-run CTS. Lets `TaskStateService.CancelAsync` kill a cancelled task's process without a DI cycle.
- **OverrideSlotService** — owns `RunNow` / `ContinueTask`; goes through `TaskStateService.StartRunningAsync` (caller-driven, serialized by slot lock).
@@ -245,10 +249,12 @@ non-obvious, behaviour-changing. A fixed bug is git history, not a finding.
- `db_path`, `sandbox_root`, `log_root`
- `worktree_root_strategy` (`sibling` | `central`), `central_worktree_root`
- `queue_backstop_interval_ms` (30000) — also the gate/throttle recovery timer
- `signalr_port` (47821), `claude_bin` `claude_bin` is the one field the UI can write
(Settings → Dateien → CLAUDE CLI, hub `GetClaudeBin`/`SetClaudeBin`). It edits the DI-singleton
`WorkerConfig` in place *and* persists via `SaveClaudeBin`, so it applies to the next spawn
without a restart. Every other field here is hand-edit only.
- `signalr_port` (47821), `claude_bin`, `ticket_api_base_url` the two UI-writable fields
(`claude_bin`: Settings → Dateien → CLAUDE CLI, hub `GetClaudeBin`/`SetClaudeBin`;
`ticket_api_base_url`: Settings → Ticketsystem, hub `GetTicketSettings`/`SetTicketApiBaseUrl`).
Both edit the DI-singleton `WorkerConfig` in place *and* persist via their own `SaveKey` call
(`SaveClaudeBin`/`SaveTicketApiBaseUrl`), so each applies without a restart. Every other field
here is hand-edit only.
- `usage_poll_interval_seconds` (60, clamped to min 15 on load)
- `online_inbox``enabled` (false by default; when false the entire `Online/` stack is not registered), `api_base_url` (must be HTTPS or loopback, validated at startup), `poll_interval_seconds` (60), `zitadel.authority`/`client_id`/`scopes`. The refresh token is **not** in this file — DPAPI-encrypted at `~/.claudeDo/online-inbox.token`.
@@ -258,8 +264,35 @@ Per-list config (`list_config` in DB) provides defaults for `model`, `system_pro
`get_effective_run_config` can't drift). `verify_command` and `serialize_on_file_overlap` are
list-only — no task-level override. `verify_command` is written via `set_list_config`'s
`verifyCommand` parameter (`ConfigMcpTools`) and the UI's list config editor;
`serialize_on_file_overlap` and `permission_mode` are UI-only (`set_list_config` preserves but
does not expose them).
`serialize_on_file_overlap`, `permission_mode`, and `ticket_project_id` are UI-only
(`set_list_config` preserves but does not expose them).
## Tickets (Bandel ticket system)
Optional, off by default. `TicketSystemConfig.IsConfigured` is the single gate (`worker.config.json`'s
`ticket_api_base_url` set **and** a PAT present) — false costs nothing: no DB query, no network. The
PAT is DPAPI-encrypted at `~/.claudeDo/ticket.pat` via `DpapiTokenStore` (same class the Online Inbox
refresh token uses, one instance per file, DI markers `OnlineRefreshTokenStore`/`TicketPatStore`
distinguish the two singletons). The token never touches `worker.config.json` or the DB.
- **Import** (`TicketImportService`, hub `ImportTickets`) — per-list, manual only (no polling): pulls
the linked project's board (`ListConfigEntity.TicketProjectId`), keeps tickets that are `StatusId
== 1` (Offen) *and* assigned to the PAT owner (case-insensitive name match), skips any `TicketRef`
already present on the list, and creates one `Idle` task per remainder via `TaskRepository.AddAsync`
(so it gets a `Number` like every other task). Already-imported tickets are never updated — a
re-import would overwrite task notes.
- **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
back — it's the entry state). A task without a `TicketRef`, or a `TicketRef` not shaped
`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
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
response is unwrapped from `BandelEnvelope<T>` and a non-2xx/`success:false` throws
`TicketApiException` with a readable message. ⚠️ The endpoint shapes were taken from the
`Bandel.APIs` source, never confirmed against a live call — see `docs/open.md`.
## Notes