7.8 KiB
ClaudeDo
A desktop task management app that executes tasks autonomously via Claude CLI in isolated git worktrees.
Architecture
Two-process system communicating over SignalR (127.0.0.1:47821):
- ClaudeDo.App — Avalonia desktop entry point, DI container setup
- ClaudeDo.Ui — Views, ViewModels, SignalR client (MVVM with CommunityToolkit.Mvvm)
- ClaudeDo.Data — SQLite data layer, repositories, models, GitService
- ClaudeDo.Worker — ASP.NET Core hosted service, task queue, Claude CLI runner
- ClaudeDo.Localization —
locales/en.json+locales/de.jsonand the lookup service - ClaudeDo.Releases — Gitea release client (
IReleaseClient), used by the Ui update check and the Installer - ClaudeDo.Installer — WPF (
UseWPF) setup app; install/update/uninstall step pipeline - tests/ — six xUnit projects (Worker, Data, Ui, Localization, Installer, Releases); Worker.Tests run real SQLite and real git
Per-project CLAUDE.md files exist for App, Data, Installer, Ui, Worker, and Worker.Tests —
those are the living per-project docs. Localization, Releases, and the other five test projects
have none; this file plus the code is all there is for them.
Tech Stack
- .NET 8.0, Avalonia 12.0.0 (Fluent theme)
- SQLite (WAL mode) via Entity Framework Core (Microsoft.EntityFrameworkCore.Sqlite)
- SignalR for real-time IPC
- CommunityToolkit.Mvvm (
[ObservableProperty],[RelayCommand]) - Git worktrees for task isolation
Key Paths
- DB:
~/.claudeDo/todo.db - UI config:
~/.claudeDo/ui.config.json - Worker config:
~/.claudeDo/worker.config.json - Logs:
~/.claudeDo/logs/ - Worktrees: configured per worker (sibling or central strategy)
Conventions
- Repository pattern — each entity has its own async repository
- All data operations are async with CancellationToken support
- EF Core migrations manage schema (Migrations/ folder in ClaudeDo.Data)
IDbContextFactory<ClaudeDoDbContext>used by singleton consumers (e.g. Worker)- Entity configuration via
IEntityTypeConfiguration<T>in Configuration/ folder - Task status flow:
Idle | Queued -> Running -> WaitingForReview -> Done | Failed | Cancelled; a task with children passes throughWaitingForChildrenfirst. Approve is the single review+merge action (no separate "Merge all"), and in the detail pane it's gated behind opening the diff. Full transition table →src/ClaudeDo.Worker/CLAUDE.md; merge/review/gate mechanics →docs/explore-notes/review-merge.md. - Worktree state flow: Active -> Merged | Discarded | Kept
- The queue picker claims tasks by
Status=Queued(withBlockedByTaskId IS NULL); the legacy tag system was removed - Interfaces live in an
Interfaces/subfolder beside their consumers (namespace unchanged) - Small single-consumer helper types live in their consumer's file, not standalone files
- Commit messages use conventional format:
{commitType}(slug): title - Views use compiled bindings (
x:DataType) - ViewModels use
[ObservableProperty]and[RelayCommand]source generators
Working style (autonomous)
For any non-trivial feature, bug, or change, run this loop without hand-holding:
- Brainstorm first (superpowers:brainstorming) — ask clarifying questions one at a time, propose 2–3 options with a recommendation, present a short design, get approval before building.
- Write it down — a spec in
docs/superpowers/specs/YYYY-MM-DD-<topic>-design.mdand a step-by-step plan indocs/superpowers/plans/(superpowers:writing-plans). Commit the docs. - Implement on main with superpowers:subagent-driven-development — one subagent per task, TDD, build + test, commit per task with Conventional Commits. Once the plan is approved, do NOT pause for re-approval between tasks; only stop for genuine decisions or blockers.
- Trust but verify — read each subagent's diff and run the build/tests yourself before marking a task done.
- Bugs → superpowers:systematic-debugging (find the root cause before any fix).
- Never claim UI works without running it — explicitly flag visual-verification gaps for the user to check.
Commit freely (per task + the spec/plan docs). Never push without asking.
Building & Testing
Build the whole solution, test only what you touched. Always -c Release — a running Worker locks the Debug output.
dotnet build ClaudeDo.slnx -c Release # ~40s, catches cross-project breaks
dotnet test tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj -c Release --no-build \
--filter "FullyQualifiedName~Handoff" # seconds, not minutes
.slnx needs SDK 9+ (this machine is on 10). On an older SDK build the projects individually:
src/ClaudeDo.App (pulls in Ui + Data) and src/ClaudeDo.Worker.
Do NOT run the full test suite to finish a task
Measured 2026-09-02, sequentially after one build: Worker.Tests 507s, Ui.Tests 33s, Data.Tests 8s, Localization/Installer/Releases 3s each, build 42s — ~10 min for all six. Run in parallel across projects it is worse, not better (Worker.Tests 801s, Ui.Tests 82s): these tests create real git worktrees and spawn processes, so they thrash rather than share.
With up to 5 implementer tasks running at once, a full suite per task costs hours — and cannot find
what it costs that for. Each task works in its own worktree containing only its own change, so a
collision between two parallel tasks is invisible to both. Collisions surface after the merge, which
is exactly where verify_merges runs the list's verify command once per batch. The full suite is
the merge gate's job, not the task's.
So, per task:
- Build: the whole solution, always. It's cheap and it catches the compile break your change caused three projects over.
- Tests:
--no-build --filterscoped to what you touched. Add a filter for the classes covering your change; if you genuinely can't scope it, run the one relevant test project, never all six. - Report what you ran and what you did not, so the merge session knows what is still unverified.
- Never report "all tests pass" when you ran a filtered subset. Say which filter you used.
Gotchas
- Subagents: use the
sonnetmodel; stage files explicitly by path — nevergit add -A(parallel sessions often leave unrelated WIP in the tree). - Icons: Material Design Icons via
mdi:Name+ theIconKeyconverter (a misspelled name silently renders nothing —MdiIconNamesTestsguards it). Colour comes from theICON PAINTINGblock inIslandStyles.axaml(class + state); settingForeground/Strokeinline kills the hover state. - Localization:
locales/en.jsonandlocales/de.jsonkeys must stay in parity (Localization.Tests enforces it). - Test fakes: changing
IWorkerClient/WorkerHub/ ViewModel constructors breaks hand-rolled fakes in both test projects — update them.
Docs
docs/open.md— open verification items and remaining code TODOs (the only doc kept current besides the CLAUDE.md files)docs/plan.md— original design spec (historical; tag-queue/schema.sql parts are outdated)docs/improvement-plan.md— improvement snapshot from 2026-04-13 (historical)docs/prompts-inventory.md,docs/mailbox-proposal.md— reference material (mailbox integration is parked)CHANGELOG.md— Keep a Changelog format, maintained on releasedocs/explore-notes/— distilled maps of complex subsystems (detail too fine for a CLAUDE.md, read on demand). Before deep-exploring a subsystem, check for a matching note first; after a deep explore, distill durable findings back and bump its "verified against" commit. Always verify against current code before trusting. Seedocs/explore-notes/README.md. Current notes:worker-task-pipeline,usage-monitoring,external-mcp,review-merge,conpty-sessions,installer-preflight.