Files
ClaudeDo/docs/plan.md
Mika Kuns b6897df86e chore: add gitignore and finalize initial plan
- .gitignore for .NET output, IDE files, runtime artifacts (todo.db, worktrees, sandbox, logs)
- docs/plan.md: .NET worker spawning Claude CLI, per-list working_dir + git worktrees, 3NF schema, SignalR IPC, Windows-Service deployment path
2026-04-13 11:14:46 +02:00

22 KiB
Raw Blame History

ToDo-App mit autonomem Agent-Worker — Design

Context

Ziel: eine persönliche ToDo-App als Desktop-Anwendung, in der mehrere Listen verwaltet werden können. Ein Teil der Tasks soll autonom von Claude abgearbeitet werden (z.B. Recherche, Code-Aufgaben, Notizen-Verarbeitung). Die Autonomie läuft in einem getrennten Hintergrund-Prozess, damit die UI davon entkoppelt bleibt.

Motivation: Aufgaben "parken" und später von einem Agenten abarbeiten lassen, ohne dafür manuell einen Chat zu öffnen. Ergebnisse landen wieder an der Task, sodass die Liste die Single-Source-of-Truth bleibt.

Listen können ein eigenes working_dir (z.B. ein Projekt-Repo) haben. Tasks solcher Listen laufen in einem dedizierten Git-Worktree, der Worker commitet die Änderungen am Ende automatisch. Die UI zeigt Branch + Diff und erlaubt Merge/Keep/Discard.

Architektur-Überblick

Monorepo (C:\Private\ClaudeDo, gehostet auf git.kuns.dev), zwei Prozesse, gekoppelt über eine gemeinsame SQLite-Datenbank:

┌─────────────────────┐        ┌──────────────────────────────┐
│  UI (Avalonia .NET) │        │  Worker (.NET 8 Console)     │
│  Listen, Tasks,     │◄──────►│  BackgroundService           │
│  Diff/Merge-Dialog  │ SQLite │  spawnt: claude -p ...       │
└─────────────────────┘  WAL   │  cwd = Worktree der Task     │
         │                     └──────────┬───────────────────┘
         │                                │ stdout (ndjson)
         └─────── todo.db ────────────────┘
                  (~/.todo-app/todo.db)

  Ziel-Repo (z.B. LagerApp):
    C:\Private\LagerApp\                              (main repo, unangetastet)
    C:\Private\.claudedo-worktrees\<list>\<task-id>\  (Worktree pro Task)
  • UI (ClaudeDo.Ui, Avalonia MVVM): Listen & Tasks verwalten, Ergebnisse + Diffs anzeigen, Worktree-Aktionen.
  • Worker (ClaudeDo.Worker, .NET 8 Console mit Kestrel + SignalR): hostet einen SignalR-Hub auf localhost, verwaltet die Queue intern, spawnt Claude CLI als Child-Prozess pro Task, streamt Events live an die UI, commitet, persistiert Ergebnisse.
  • SQLite im WAL-Mode für persistente Daten (Listen, Tasks, Tags, Worktrees). Keine Koordinations-Felder — Live-Status läuft über SignalR.
  • SignalR über localhost-HTTP (Default-Port 47821, konfigurierbar) als Push-Channel zwischen Worker und UI. UI-Connection-State ⇒ "Worker online".

Datenmodell

Schema in 3NF. Keine Mehrwert-Felder (z.B. JSON-Arrays), keine transitiven Abhängigkeiten: Worktree-Attribute wandern in eine eigene Tabelle, Tags sind über Junctions modelliert, Worker-Slots sind eigene Rows.

lists

  • id TEXT PK (uuid)
  • name TEXT NOT NULL
  • created_at TIMESTAMP NOT NULL
  • working_dir TEXT NULL — absoluter Pfad. Gesetzt + Git-Repo → Worktree-Modus.
  • default_commit_type TEXT NOT NULL DEFAULT 'chore'

tasks

  • id TEXT PK (uuid)
  • list_id TEXT NOT NULL REFERENCES lists(id) ON DELETE CASCADE
  • title TEXT NOT NULL
  • description TEXT NULL
  • status TEXT NOT NULL — manual | queued | running | done | failed (running bleibt persistiert für Crash-Recovery: stale running-Tasks werden beim Worker-Start auf failed gesetzt)
  • scheduled_for TIMESTAMP NULL — "nicht vor"
  • result TEXT NULL (Markdown)
  • log_path TEXT NULL — Pfad zur ndjson-Log-Datei
  • created_at TIMESTAMP NOT NULL
  • started_at TIMESTAMP NULL
  • finished_at TIMESTAMP NULL
  • commit_type TEXT NOT NULL DEFAULT 'chore' — Conventional-Commit-Prefix (wird beim Anlegen aus list.default_commit_type befüllt, danach unabhängig).

tags

  • id INTEGER PK AUTOINCREMENT
  • name TEXT NOT NULL UNIQUE — z.B. agent, manual, code, research

list_tags (Junction, Liste hat 0..n Tags)

  • list_id TEXT NOT NULL REFERENCES lists(id) ON DELETE CASCADE
  • tag_id INTEGER NOT NULL REFERENCES tags(id) ON DELETE CASCADE
  • PK (list_id, tag_id)

task_tags (Junction, Task hat 0..n Tags; überschreibt die List-Tags nicht automatisch — die Vereinigung aus list_tags task_tags bildet die effektive Tag-Menge der Task; Ausschluss über noch zu spezifizierenden Negations-Mechanismus, bis dahin additiv)

  • task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE
  • tag_id INTEGER NOT NULL REFERENCES tags(id) ON DELETE CASCADE
  • PK (task_id, tag_id)

worktrees (1:1 mit Tasks, die im Worktree-Modus laufen)

  • task_id TEXT PK REFERENCES tasks(id) ON DELETE CASCADE — Task hat höchstens einen Worktree.
  • path TEXT NOT NULL — absoluter Pfad zum Worktree.
  • branch_name TEXT NOT NULL — z.B. claudedo/<task-id-kurz>.
  • base_commit TEXT NOT NULL — SHA des working_dir-HEAD beim Anlegen.
  • head_commit TEXT NULL — SHA nach Auto-Commit (NULL bis Commit erfolgt).
  • diff_stat TEXT NULL — Output von git diff --stat base..head.
  • state TEXT NOT NULL DEFAULT 'active'active | merged | discarded | kept.
  • created_at TIMESTAMP NOT NULL

Die Abwesenheit einer worktrees-Zeile entspricht dem alten Wert 'none'. Non-Worktree-Tasks (Liste ohne working_dir) erzeugen keine Zeile.

Hinweis: Es gibt keine worker_heartbeat- oder worker_slots-Tabelle. Worker-Online-Status und aktive Slots laufen über SignalR (siehe Sektion "IPC").

Tag-Modell (Startset)

  • Tags leben in tags, Zuordnung via list_tags und task_tags (Junctions).
  • Effektive Tag-Menge einer Task = list_tags(task.list_id) task_tags(task.id) (additiv).
  • Minimal-Startset in tags:
    • manual → Worker ignoriert, reine Notiz/Checkliste.
    • agent → Worker pickt auf, wenn Status queued.
  • Weitere Profile (code, research …) später als zusätzliche Rows in tags + Handler im Worker.

IPC (SignalR)

Worker hostet Microsoft.AspNetCore.SignalR über Kestrel auf http://127.0.0.1:<port>/hub (Default 47821, in worker.config.json überschreibbar). Bindung nur an Loopback. Kein Auth-Layer.

Hub: WorkerHub

Server-Methoden (UI ruft auf):

  • GetActive()[{ slot: "queue"|"override", taskId, startedAt }] — initialer State-Sync nach Connect.
  • RunNow(taskId) → triggert sofortigen Override-Run; wirft, wenn Override-Slot belegt oder Task nicht existiert.
  • CancelTask(taskId) → killt laufenden CLI-Child-Prozess der Task.
  • WakeQueue() → Worker prüft Queue sofort (für instant pickup nach Task-Anlage).
  • Ping()"pong" + Worker-Version (Sanity-Check).

Client-Methoden (Worker pusht an UI):

  • TaskStarted(slot, taskId, startedAt)
  • TaskFinished(slot, taskId, status, finishedAt) — Status done | failed.
  • TaskMessage(taskId, ndjsonLine) — streamt jedes Claude-Event live (für Live-Log in TaskDetail).
  • WorktreeUpdated(taskId) — Auto-Commit fertig, UI lädt worktrees-Row neu.
  • TaskUpdated(taskId) — generisches Signal: UI lädt Task neu (z.B. nach Result-Persist).

Connection-State der UI ⇒ "Worker online" (kein Heartbeat in DB nötig).

UI-Verhalten bei offline Worker:

  • StatusBar: "Worker offline".
  • "Run Now" deaktiviert (RPC nicht möglich).
  • Task-Anlage funktioniert weiter (DB-only); wird beim nächsten Worker-Start aufgenommen.

Queue-Semantik

  • Default: Tasks mit Tag agent und Status queued werden sequenziell abgearbeitet (FIFO, nach created_at). Worker-interner In-Memory-Slot _queueSlot.
  • Schedule: scheduled_for gesetzt → Worker überspringt, bis Zeit erreicht.
  • Override: UI ruft RunNow(taskId) per SignalR. Worker startet die Task in _overrideSlot parallel zur Queue. Max. 1 Queue + 1 Override = 2 gleichzeitige Runs.
  • Zweiter RunNow während Override-Slot belegt → SignalR-Methode wirft, UI zeigt "Override bereits aktiv".
  • Wake-up: Bei Task-Anlage ruft die UI WakeQueue(), damit der Worker nicht erst auf den nächsten Tick warten muss.

Worker-Komponenten (.NET)

Projekt: ClaudeDo.WorkerMicrosoft.NET.Sdk.Web Console-App (für Kestrel + SignalR), Teil von ClaudeDo.sln, referenziert ClaudeDo.Data.

  • Program.csWebApplication.CreateBuilder + AddSignalR() + AddHostedService<QueueService>(). app.MapHub<WorkerHub>("/hub") auf konfiguriertem Loopback-Port. Bindung: app.Urls.Add($"http://127.0.0.1:{cfg.Port}").
  • Hub/WorkerHub.cs — implementiert die in der IPC-Sektion beschriebenen Methoden. Delegiert RunNow/CancelTask/WakeQueue an QueueService. GetActive() liest aus den In-Memory-Slots.
  • Hub/HubBroadcaster.cs — Wrapper um IHubContext<WorkerHub> mit typisierten TaskStarted/TaskFinished/TaskMessage/WorktreeUpdated/TaskUpdated-Methoden. Wird in TaskRunner und ClaudeProcess injiziert.
  • Services/QueueService.csBackgroundService, interner Timer (z.B. 30s als Backstop, primärer Trigger ist WakeQueue). Wählt nächste Task (Tag agent via task_tags/list_tags, scheduled_for ≤ now, _queueSlot frei). Hält In-Memory-Slots _queueSlot, _overrideSlot. Sendet TaskStarted/TaskFinished über HubBroadcaster.
  • Services/StaleTaskRecovery.cs — beim Start: alle tasks.status='running''failed', Begründung "worker restart".
  • Runner/TaskRunner.cs — orchestriert eine Task-Ausführung (siehe Lifecycle unten).
  • Runner/WorktreeManager.cs — kapselt git worktree add/remove, git rev-parse, git diff --stat, git add -A && git commit.
  • Runner/ClaudeProcess.cs — kapselt System.Diagnostics.Process:
    • Cmd: claude -p --output-format stream-json --verbose --dangerously-skip-permissions
    • WorkingDirectory = worktreePath (bzw. Sandbox-Dir bei Listen ohne working_dir).
    • Prompt über stdin (vermeidet Quoting-Probleme mit Zeilenumbrüchen).
    • Async stdout-Reader, jede Zeile = 1 JSON-Event → an LogWriter, MessageParser und HubBroadcaster.TaskMessage(taskId, line).
    • stderr in Log mit [stderr]-Prefix.
    • Finales type:"result".result-Feld extrahieren (Markdown).
    • Exit != 0 oder fehlendes Result → Task failed, Fehler ins result-Feld.
  • Runner/LogWriter.cs — streamt ndjson nach ~/.todo-app/logs/<task-id>.ndjson.
  • Runner/CommitMessageBuilder.cs — baut Conventional-Commit-Message (siehe unten).
  • Config/WorkerConfig.cs — lädt ~/.todo-app/worker.config.json:
    {
      "db_path": "~/.todo-app/todo.db",
      "sandbox_root": "~/.todo-app/sandbox",
      "log_root": "~/.todo-app/logs",
      "worktree_root_strategy": "sibling",
      "central_worktree_root": "~/.todo-app/worktrees",
      "queue_backstop_interval_ms": 30000,
      "signalr_port": 47821,
      "claude_bin": "claude"
    }
    
    Kein API-Key — CLI nutzt die bestehende User-Session.

Dependencies: Microsoft.AspNetCore.App (Kestrel + SignalR via Microsoft.NET.Sdk.Web), Microsoft.Extensions.Hosting, Microsoft.Data.Sqlite (via ClaudeDo.Data), System.Text.Json.

Task-Lifecycle (Worktree-Modus)

Nur wenn list.working_dir gesetzt und ein Git-Repo ist:

  1. Worktree anlegen (WorktreeManager.CreateAsync):
    • base = git -C <working_dir> rev-parse HEAD
    • branch = claudedo/<task-id-kurz>
    • worktreePath = <working_dir>/../.claudedo-worktrees/<list-slug>/<task-id>/
    • git -C <working_dir> worktree add -b <branch> <worktreePath> <base>
    • Insert in worktrees: task_id, path, branch_name, base_commit, state='active', created_at=now.
  2. Claude-Run via ClaudeProcess.RunAsync(prompt, worktreePath, ...).
  3. Auto-Commit (WorktreeManager.CommitAsync), nur wenn Run erfolgreich UND Änderungen vorhanden:
    • git -C <worktreePath> add -A
    • Nichts staged → skippen (head = base).
    • Sonst: git commit -m <conventional-message> (Author aus User-Git-Config).
    • head = rev-parse HEAD, diff_stat = git diff --stat base..head.
    • Update worktrees: head_commit, diff_stat.
  4. Status finalisieren: tasks.status='done', tasks.result = Claude-Result-Markdown, tasks.finished_at=now.
  5. Kein Auto-Cleanup. worktrees.state bleibt 'active', bis der User über die UI eine Aktion wählt.

Fehlerpfade:

  • Run failed / Exit != 0 → tasks.status='failed', kein Auto-Commit, worktrees-Row bleibt mit state='active' zur Inspektion.
  • working_dir ist kein Git-Repo → Task failed mit klarer Meldung; keine worktrees-Row, keine Fallback-Sandbox.
  • Worktree-Anlegen failed (Branch/Dir existiert) → Task failed; keine worktrees-Row geschrieben; Runner räumt nicht auf.

Non-Worktree-Modus (list.working_dir NULL): cwd = ~/.todo-app/sandbox/<task-id>/, kein Git, Standard-Toolset wie bisher.

Conventional Commit Message

Format: {type}({list-slug}): {title}

  • type = task.commit_type (Default chore).
  • {list-slug} = list.name lowercased, whitespace→-, nicht-alphanumerisch entfernt.
  • {title} = task.title, truncated auf 60 Zeichen.
  • Body (optional, zweizeilig nach Leerzeile):
    {task.description, max 400 chars}
    
    ClaudeDo-Task: <task-id>
    

Beispiel: feat(lager-app): add barcode scan retry logic

UI-Komponenten (Avalonia)

  • MainWindow mit 2-Pane-Layout: links Listen, rechts Tasks der gewählten Liste.
  • ListEditor — Feld Working Directory (Ordner-Picker), Default Commit Type Dropdown.
  • TaskItemView zeigt Titel, Tags, Status (Badge), "Run Now"-Button (→ ruft WorkerHub.RunNow(taskId); deaktiviert wenn Worker offline).
  • TaskDetailPane — Description, Result-Markdown (AvaloniaEdit oder Markdown.Avalonia), Link zum ndjson-Log.
    • Bei vorhandener worktrees-Row mit state='active': Branch, diff_stat, Buttons Open Worktree, Show Diff, Merge into main, Keep as branch, Discard.
    • Merge into main: git -C <working_dir> merge --ff-only <branch> (UI warnt bei non-ff) → worktrees.state='merged', Worktree + Branch entfernen.
    • Keep as branch: Worktree entfernen, Branch behalten → worktrees.state='kept'.
    • Discard: git worktree remove --force + git branch -Dworktrees.state='discarded'.
  • TaskEditor — Dropdown Commit Type (Default aus Liste).
  • StatusBar — Worker-Status aus SignalR-Connection-State (Connected ⇒ online), aktive Tasks aus In-Memory-State (initial via GetActive(), danach via TaskStarted/TaskFinished-Events).
  • TaskDetailPane zeigt bei laufender Task den Live-Stream der TaskMessage-Events (rolling Log).
  • WorkerClient (ClaudeDo.Ui/Services/WorkerClient.cs) — kapselt HubConnection, Auto-Reconnect, exponiert IObservable<…>-Events bzw. INotifyPropertyChanged-Properties für die ViewModels.
  • SettingsDialog — Default-Tags, Pfad zur todo.db, SignalR-Port (muss zum Worker passen).

DB-Zugriff via Microsoft.Data.Sqlite + Repository-Layer (TaskRepository, ListRepository). Git-Operationen (UI + Worker) über gemeinsamen GitService in ClaudeDo.Data. MVVM via CommunityToolkit.Mvvm.

Worker als Windows-Service (Ziel-Deployment)

Initial läuft der Worker als Console-Prozess (lokales Dev-Setup). Im Endzustand soll er als Windows-Service automatisch starten.

Code-seitig:

  • Paket Microsoft.Extensions.Hosting.WindowsServices referenzieren.
  • In Program.cs: builder.Host.UseWindowsService(o => o.ServiceName = "ClaudeDoWorker").
  • Logging zusätzlich über EventLog (builder.Logging.AddEventLog(...)), damit Service-Fehler im Windows Event Viewer landen.
  • Alle Pfade in worker.config.json absolut auflösen (%USERPROFILE% / ~ expandieren) — der Service-Working-Directory ist standardmäßig C:\Windows\System32.
  • StaleTaskRecovery (siehe oben) sorgt nach Service-Restart automatisch für das Aufräumen hängender running-Tasks.
  • Restart-Verhalten via sc.exe failure-Konfig oder beim Install.

Install:

  • Veröffentlichen mit dotnet publish -c Release -r win-x64 --self-contained false.
  • Service registrieren:
    sc.exe create ClaudeDoWorker binPath= "C:\Path\To\ClaudeDo.Worker.exe" start= auto
    sc.exe failure ClaudeDoWorker reset= 60 actions= restart/5000/restart/10000/restart/30000
    
  • Später optional: kleines ClaudeDo.Installer-Projekt (WiX oder MSIX), das das auch macht.

Auth-Konflikt mit "User-CLI-Session" beachten: Der Worker-Service läuft per Default unter LocalSystem — der hat keinen Zugriff auf die ~/.claude/-Session des interaktiven Users, in der der CLI-Login liegt. Optionen:

  1. Empfohlen: Service unter dem User-Account laufen lassen (sc.exe config ClaudeDoWorker obj= ".\<username>" password= "..." oder via services.msc → "Log On As"). Dann greift die bestehende claude login-Session des Users. Voraussetzung: User-Account hat das Recht "Log on as a service".
  2. Fallback: Wieder auf API-Key wechseln (ANTHROPIC_API_KEY als Umgebungsvariable des Service oder im worker.config.json). Dann ist der Service unabhängig vom User-Profil — verliert aber den Vorteil "kein Key-Handling".

Entscheidung wird beim Service-Deployment getroffen, bleibt für die initiale Console-Variante irrelevant. Service-Modus erfordert keine Schema- oder API-Änderungen am Worker.

SignalR im Service-Modus: Bindung bleibt 127.0.0.1:47821. Da die UI auf demselben Rechner läuft, ist Loopback-Erreichbarkeit gegeben — Windows-Firewall greift bei Loopback nicht.

Project-Layout (Monorepo)

Repo: ClaudeDo auf git.kuns.dev, lokal C:\Private\ClaudeDo

/ClaudeDo
  ClaudeDo.sln
  /src
    /ClaudeDo.App              Avalonia Entry (App.axaml, Program.cs)
    /ClaudeDo.Ui               Views, ViewModels, Worktree-Dialoge
    /ClaudeDo.Data             Repositories, Models, SqliteConnectionFactory, GitService
    /ClaudeDo.Worker           Console/BackgroundService
  /tests
    /ClaudeDo.Worker.Tests     xUnit, In-Memory-SQLite + temp-Repo für Worktree-Tests
  /schema
    schema.sql                 Single source of truth für DB-Schema
    migrations/                (später, falls nötig)
  /docs
    plan.md, architecture.md
  .gitignore                   bin/, obj/, *.db, worker.config.json, logs/

Vorteil Monorepo: gemeinsames schema.sql, atomische Änderungen über UI+Worker, ein Clone reicht, einheitlicher .NET-Stack.

Verification

  1. Schema-Init: Worker erstellt todo.db bei erstem Start mit WAL, legt alle Tabellen an (lists, tasks, tags, list_tags, task_tags, worktrees). Initial werden in tags die Rows 'agent' und 'manual' eingefügt. → sqlite3 todo.db ".schema" zeigt die erwartete Struktur in 3NF. 1a. SignalR-Endpoint: Worker startet, curl -i http://127.0.0.1:47821/hub antwortet (HTTP 400 Negotiate-Fehler ohne SignalR-Handshake ist OK — Hauptsache Port lauscht und nur auf Loopback). 1b. Hub-Roundtrip (UI- oder Test-Client): connection.InvokeAsync<string>("Ping") liefert "pong".
  2. CLI-Preflight: claude --version läuft, User eingeloggt. Worker-Startup-Check failed laut, wenn nicht erfüllt.
  3. Smoke-Spawn (Unit-Test): claude -p --output-format stream-json --dangerously-skip-permissions, Prompt "ping" via stdin → result-Message erhalten + geparst.
  4. End-to-End Happy Path (Non-Worktree):
    • UI: Liste "Test" (kein working_dir), Task mit Tag agent + Status queued, Description "Schreibe eine Haiku über Intralogistik".
    • Worker erkennt Task binnen 5s (Log: picked up task <id>), startet CLI-Run, schreibt Result zurück.
    • UI zeigt Status done + Result-Markdown.
  5. Worktree-Happy-Path (Integrations-Test mit temp-Repo):
    • Liste mit working_dir=<temp-repo>, Task "add hello.txt with content hi".
    • Nach Run: Worktree unter <temp-repo>/../.claudedo-worktrees/<slug>/<id>/, hello.txt drin, 1 Commit chore(<slug>): add hello.txt auf Branch claudedo/<id-kurz>. worktrees-Row für die Task: head_commit != base_commit, diff_stat gesetzt, state='active'.
  6. No-Changes-Run: Prompt, der nichts ändert → tasks.status='done', kein Commit, worktrees.head_commit IS NULL (oder = base_commit).
  7. Kein Git-Repo: working_dir auf normalen Ordner → Task failed mit klarer Meldung, keine worktrees-Row.
  8. Merge-UI: nach erfolgreichem Run Button "Merge into main" → <working_dir> HEAD enthält den Commit, Branch + Worktree weg, worktrees.state='merged'.
  9. Override-Parallelität: Zwei queued Tasks → nur eine läuft. Bei zweiter "Run Now" klicken → WorkerHub.RunNow succeeded, beide laufen parallel, UI bekommt zwei TaskStarted-Events (slots queue + override), in Worktree-Listen zwei getrennte Worktrees. Dritter RunNow → SignalR-Exception "override slot busy".
  10. Schedule: Task mit scheduled_for = now + 2min → Worker wartet; nach Ablauf startet sie.
  11. Worker-Offline-Erkennung: Worker-Prozess killen → SignalR-Connection bricht ab, UI-StatusBar wechselt sofort auf "offline" (kein Polling-Lag). "Run Now"-Buttons werden disabled. Laufende Task bleibt running → beim nächsten Worker-Start setzt StaleTaskRecovery sie auf failed, worktrees-Row bleibt zur Inspektion. UI reconnected automatisch und ruft erneut GetActive() für den State-Sync.
  12. Live-Stream: Während eine Task läuft, UI öffnet TaskDetailPane → ndjson-Events erscheinen in Echtzeit (jedes TaskMessage-Event wird angehängt).
  13. Wake-up: Task in UI anlegen mit Tag agent, Status queued → UI ruft WakeQueue() → Worker startet die Task in < 1s (nicht erst nach Backstop-Intervall).
  14. Unit-Tests (Worker): QueueService-Selection-Logik (Override-Slot-Belegung, Schedule-Filter, Sequenzialität) mit In-Memory-SQLite. Hub-Methoden mit TestServer + HubConnectionBuilder.

Offene Punkte für später (nicht Scope dieses Plans)

  • Zusätzliche Tag-Profile mit spezialisierten Toolsets (code → engere Permissions, research → Netzwerk).
  • MCP-Server-Integration für externe Dienste.
  • Notification bei Task-fertig (Windows Toast).
  • Non-ff Merges (Rebase-/Squash-Option im UI).
  • Bulk-Discard alter Worktrees.
  • Anzeige der ndjson-Message-Chronik im UI.
  • Windows Job Objects für garantierten Child-Cleanup beim Worker-Crash.
  • Installer-Projekt (ClaudeDo.Installer, WiX/MSIX), das den Service registriert + UI shortcut anlegt.