# Default Agents Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Ship ClaudeDo with 6 default agents (code-reviewer, test-writer, debugger, security-reviewer, explorer, researcher) that seed into `~/.todo-app/agents/` on first launch, with a "Restore defaults" button in the settings modal. **Architecture:** Bundled `.md` files in `src/ClaudeDo.Worker/DefaultAgents/` are copied to the Worker output folder. A new `DefaultAgentSeeder` service copies any missing file into the user's agents dir — run once at startup, and again on demand via a new `WorkerHub.RestoreDefaultAgents` method invoked by a button in `SettingsModalView`. **Tech Stack:** .NET 8 / ASP.NET Core / SignalR / Avalonia 12 / CommunityToolkit.Mvvm / xUnit --- ## File Structure **Create:** - `src/ClaudeDo.Worker/DefaultAgents/code-reviewer.md` - `src/ClaudeDo.Worker/DefaultAgents/test-writer.md` - `src/ClaudeDo.Worker/DefaultAgents/debugger.md` - `src/ClaudeDo.Worker/DefaultAgents/security-reviewer.md` - `src/ClaudeDo.Worker/DefaultAgents/explorer.md` - `src/ClaudeDo.Worker/DefaultAgents/researcher.md` - `src/ClaudeDo.Worker/Services/DefaultAgentSeeder.cs` - `tests/ClaudeDo.Worker.Tests/Services/DefaultAgentSeederTests.cs` **Modify:** - `src/ClaudeDo.Worker/ClaudeDo.Worker.csproj` — add Content item group for `DefaultAgents\*.md` - `src/ClaudeDo.Worker/Program.cs` — register seeder, run `SeedMissingAsync()` once at startup - `src/ClaudeDo.Worker/Hub/WorkerHub.cs` — inject `DefaultAgentSeeder`, add `RestoreDefaultAgents` method - `src/ClaudeDo.Ui/Services/WorkerClient.cs` — add `RestoreDefaultAgentsAsync` method + `SeedResultDto` record - `src/ClaudeDo.Ui/ViewModels/Modals/SettingsModalViewModel.cs` — add `RestoreDefaultAgentsCommand` - `src/ClaudeDo.Ui/Views/Modals/SettingsModalView.axaml` — add button section - `tests/ClaudeDo.Worker.Tests/Hub/AgentSettingsHubTests.cs` — add seeder integration test --- ### Task 1: Bundle default agent markdown files **Files:** - Create: `src/ClaudeDo.Worker/DefaultAgents/code-reviewer.md` - Create: `src/ClaudeDo.Worker/DefaultAgents/test-writer.md` - Create: `src/ClaudeDo.Worker/DefaultAgents/debugger.md` - Create: `src/ClaudeDo.Worker/DefaultAgents/security-reviewer.md` - Create: `src/ClaudeDo.Worker/DefaultAgents/explorer.md` - Create: `src/ClaudeDo.Worker/DefaultAgents/researcher.md` - [ ] **Step 1: Write `code-reviewer.md`** ```markdown --- name: code-reviewer description: Reviews code changes for bugs, logic errors, and convention violations. Flags only high-confidence issues. --- You are a code reviewer. Your job is to inspect the diff for real problems, not nitpicks. Focus on: - Logic errors, off-by-one bugs, null/empty handling - Broken invariants, race conditions, resource leaks - Violations of the project's established conventions (read nearby code first) - Missing error handling at system boundaries (external input, IO, network) Skip: - Style preferences the codebase doesn't enforce - Speculative "what if" concerns - Renaming for its own sake Output: a short list of concrete issues with file:line references. If the diff is clean, say so in one sentence. Do not rewrite the code — call out the problem and let the implementer fix it. ``` - [ ] **Step 2: Write `test-writer.md`** ```markdown --- name: test-writer description: Generates unit and integration tests for existing or new code. Follows the project's test patterns and frameworks. --- You are a test-writer. Your job is to write focused, useful tests for code under review. Process: 1. Read the target code and identify the observable behavior. 2. Read existing tests nearby to match the framework, fixtures, naming, and assertion style. 3. Write tests covering the happy path, boundary conditions, and the specific failure modes that matter. Rules: - One behavior per test. Clear Arrange/Act/Assert. - No tests for private implementation details — exercise public API. - No mocks where real objects are cheap (in-memory DBs, temp dirs). - Skip trivially-correct tests (getter returns what you set). Output: the test file(s) ready to compile, matching the project's conventions. Include the command to run them. ``` - [ ] **Step 3: Write `debugger.md`** ```markdown --- name: debugger description: Systematic root-cause analysis for bugs, test failures, and unexpected behavior. Hypothesize, isolate, verify. --- You are a debugger. You do NOT guess at fixes — you find the root cause first. Process: 1. Reproduce. Get a minimal, deterministic repro. If you can't reproduce it, say so and stop. 2. Isolate. Narrow the failing path (bisect, binary search, or tracing). 3. Hypothesize. State a specific, falsifiable cause. 4. Verify. Prove the hypothesis by observation (logs, debugger, targeted print) — not by "this seems likely". 5. Fix at the root, not the symptom. If the only fix is a workaround, explain why. Anti-patterns to avoid: - Making changes to "see if it works" - Adding try/catch to silence errors - Declaring the bug fixed without reproducing the fix Output: repro steps, root cause, and the minimal fix. Include evidence (log excerpt, command output) that proves the cause. ``` - [ ] **Step 4: Write `security-reviewer.md`** ```markdown --- name: security-reviewer description: Audits code for OWASP-class security issues — auth, injection, input handling, secret exposure. --- You are a security reviewer. Focus on real, exploitable weaknesses — not theoretical hardening. Check for: - Injection: SQL, command, path traversal, XSS, template injection - Auth: missing authorization, token handling, session fixation - Input validation at system boundaries (HTTP, files, IPC) - Secrets: hardcoded credentials, tokens in logs, leaked env vars - Unsafe deserialization, XXE, SSRF - Cryptography misuse (custom crypto, weak algorithms, fixed IVs) Ignore: - Internal trust-boundary assumptions the project already documents - Defense-in-depth ideas with no concrete attack path Output: a prioritized list — severity, file:line, the exploit path, the fix. If nothing is wrong, say so plainly. ``` - [ ] **Step 5: Write `explorer.md`** ```markdown --- name: explorer description: Fast codebase navigation — find files, search for patterns, answer "where/how" questions. Terse output. --- You are an explorer. Your job is to find things in the codebase quickly and report back concisely. Use: - Glob/Grep for searches - Read only for files you need to quote from Do NOT: - Refactor, edit, or "improve" anything - Read files that aren't relevant to the question - Dump raw tool output — summarize Output style: - Lead with the answer in one sentence. - Back it up with file:line references. - If you found nothing, say "no match" and what you searched for. Keep responses short. The caller wants facts, not prose. ``` - [ ] **Step 6: Write `researcher.md`** ```markdown --- name: researcher description: General-purpose research and analysis for non-code tasks — summarize docs, investigate questions, draft prose. --- You are a researcher. You handle tasks that don't fit the code-review/test/debug shape. Good fits: - Summarizing documents, specs, or long outputs - Investigating an open question (what does X do, how does Y work, what are the tradeoffs) - Drafting non-code text (release notes, emails, docs) - Analyzing structured data (logs, CSV, JSON) and reporting findings Process: 1. Restate the task in one sentence so you know what "done" looks like. 2. Gather just enough information — stop when you can answer, not when you run out of sources. 3. Distinguish facts ("the file says X") from inference ("so likely Y"). 4. Cite sources (file:line, URL, log excerpt) for every claim. Output: direct answer first, supporting evidence second. Keep it short unless asked for depth. ``` - [ ] **Step 7: Commit** ```bash git add src/ClaudeDo.Worker/DefaultAgents/ git commit -m "feat(worker): add bundled default agent definitions" ``` --- ### Task 2: Wire bundled agents into build output **Files:** - Modify: `src/ClaudeDo.Worker/ClaudeDo.Worker.csproj` - [ ] **Step 1: Add Content item group for DefaultAgents** Edit `src/ClaudeDo.Worker/ClaudeDo.Worker.csproj`. After the existing `` blocks and before the final ``, add: ```xml PreserveNewest ``` - [ ] **Step 2: Build the worker and verify the files land in output** Run: `dotnet build src/ClaudeDo.Worker/ClaudeDo.Worker.csproj` Expected: build succeeds. Then verify output: Run: `ls src/ClaudeDo.Worker/bin/Debug/net8.0/DefaultAgents/` Expected: all 6 `.md` files present. - [ ] **Step 3: Commit** ```bash git add src/ClaudeDo.Worker/ClaudeDo.Worker.csproj git commit -m "build(worker): ship DefaultAgents folder in build output" ``` --- ### Task 3: Write DefaultAgentSeeder tests (failing) **Files:** - Create: `tests/ClaudeDo.Worker.Tests/Services/DefaultAgentSeederTests.cs` The service doesn't exist yet — these tests will fail to compile initially. That's fine; the next task implements it. - [ ] **Step 1: Write the test file** Create `tests/ClaudeDo.Worker.Tests/Services/DefaultAgentSeederTests.cs`: ```csharp using ClaudeDo.Worker.Services; namespace ClaudeDo.Worker.Tests.Services; public sealed class DefaultAgentSeederTests : IDisposable { private readonly string _bundleDir; private readonly string _targetDir; public DefaultAgentSeederTests() { var root = Path.Combine(Path.GetTempPath(), $"claudedo_seeder_{Guid.NewGuid():N}"); _bundleDir = Path.Combine(root, "bundle"); _targetDir = Path.Combine(root, "target"); Directory.CreateDirectory(_bundleDir); Directory.CreateDirectory(_targetDir); } public void Dispose() { try { Directory.Delete(Path.GetDirectoryName(_bundleDir)!, true); } catch { } } private async Task WriteBundleAsync(string name, string content) { await File.WriteAllTextAsync(Path.Combine(_bundleDir, name), content); } [Fact] public async Task SeedMissing_CopiesAllFiles_WhenTargetEmpty() { await WriteBundleAsync("a.md", "A"); await WriteBundleAsync("b.md", "B"); var seeder = new DefaultAgentSeeder(_bundleDir, _targetDir); var result = await seeder.SeedMissingAsync(); Assert.Equal(2, result.Copied); Assert.Equal(0, result.Skipped); Assert.True(File.Exists(Path.Combine(_targetDir, "a.md"))); Assert.True(File.Exists(Path.Combine(_targetDir, "b.md"))); } [Fact] public async Task SeedMissing_SkipsExistingFiles() { await WriteBundleAsync("a.md", "bundled"); await File.WriteAllTextAsync(Path.Combine(_targetDir, "a.md"), "user-modified"); var seeder = new DefaultAgentSeeder(_bundleDir, _targetDir); var result = await seeder.SeedMissingAsync(); Assert.Equal(0, result.Copied); Assert.Equal(1, result.Skipped); var content = await File.ReadAllTextAsync(Path.Combine(_targetDir, "a.md")); Assert.Equal("user-modified", content); } [Fact] public async Task SeedMissing_MixedState_CopiesOnlyMissing() { await WriteBundleAsync("a.md", "A"); await WriteBundleAsync("b.md", "B"); await File.WriteAllTextAsync(Path.Combine(_targetDir, "a.md"), "existing"); var seeder = new DefaultAgentSeeder(_bundleDir, _targetDir); var result = await seeder.SeedMissingAsync(); Assert.Equal(1, result.Copied); Assert.Equal(1, result.Skipped); Assert.Equal("existing", await File.ReadAllTextAsync(Path.Combine(_targetDir, "a.md"))); Assert.Equal("B", await File.ReadAllTextAsync(Path.Combine(_targetDir, "b.md"))); } [Fact] public async Task SeedMissing_ReturnsZero_WhenBundleDirMissing() { var missingBundle = Path.Combine(Path.GetTempPath(), $"claudedo_missing_{Guid.NewGuid():N}"); var seeder = new DefaultAgentSeeder(missingBundle, _targetDir); var result = await seeder.SeedMissingAsync(); Assert.Equal(0, result.Copied); Assert.Equal(0, result.Skipped); } [Fact] public async Task SeedMissing_CreatesTargetDir_IfMissing() { await WriteBundleAsync("a.md", "A"); var missingTarget = Path.Combine(_targetDir, "nested", "created"); var seeder = new DefaultAgentSeeder(_bundleDir, missingTarget); var result = await seeder.SeedMissingAsync(); Assert.Equal(1, result.Copied); Assert.True(File.Exists(Path.Combine(missingTarget, "a.md"))); } [Fact] public async Task SeedMissing_IgnoresNonMarkdownFiles() { await WriteBundleAsync("a.md", "A"); await File.WriteAllTextAsync(Path.Combine(_bundleDir, "readme.txt"), "not an agent"); var seeder = new DefaultAgentSeeder(_bundleDir, _targetDir); var result = await seeder.SeedMissingAsync(); Assert.Equal(1, result.Copied); Assert.False(File.Exists(Path.Combine(_targetDir, "readme.txt"))); } } ``` - [ ] **Step 2: Run tests to confirm they fail to compile** Run: `dotnet test tests/ClaudeDo.Worker.Tests --filter "FullyQualifiedName~DefaultAgentSeederTests"` Expected: compile error — `The type or namespace name 'DefaultAgentSeeder' could not be found`. This confirms the tests target the not-yet-written service. --- ### Task 4: Implement DefaultAgentSeeder **Files:** - Create: `src/ClaudeDo.Worker/Services/DefaultAgentSeeder.cs` - [ ] **Step 1: Write the service** Create `src/ClaudeDo.Worker/Services/DefaultAgentSeeder.cs`: ```csharp using Microsoft.Extensions.Logging; namespace ClaudeDo.Worker.Services; public sealed record SeedResult(int Copied, int Skipped); public sealed class DefaultAgentSeeder { private readonly string _bundleDir; private readonly string _targetDir; private readonly ILogger? _logger; public DefaultAgentSeeder(string bundleDir, string targetDir, ILogger? logger = null) { _bundleDir = bundleDir; _targetDir = targetDir; _logger = logger; } public async Task SeedMissingAsync(CancellationToken ct = default) { if (!Directory.Exists(_bundleDir)) { _logger?.LogWarning("DefaultAgents bundle dir not found: {Dir}", _bundleDir); return new SeedResult(0, 0); } Directory.CreateDirectory(_targetDir); int copied = 0; int skipped = 0; foreach (var src in Directory.EnumerateFiles(_bundleDir, "*.md")) { ct.ThrowIfCancellationRequested(); var fileName = Path.GetFileName(src); var dst = Path.Combine(_targetDir, fileName); if (File.Exists(dst)) { skipped++; continue; } try { using var input = File.OpenRead(src); using var output = File.Create(dst); await input.CopyToAsync(output, ct); copied++; } catch (Exception ex) { _logger?.LogWarning(ex, "Failed to copy default agent {File}", fileName); } } return new SeedResult(copied, skipped); } } ``` - [ ] **Step 2: Run the tests and verify they pass** Run: `dotnet test tests/ClaudeDo.Worker.Tests --filter "FullyQualifiedName~DefaultAgentSeederTests"` Expected: 6 tests pass. - [ ] **Step 3: Commit** ```bash git add src/ClaudeDo.Worker/Services/DefaultAgentSeeder.cs tests/ClaudeDo.Worker.Tests/Services/DefaultAgentSeederTests.cs git commit -m "feat(worker): add DefaultAgentSeeder for first-launch agent seeding" ``` --- ### Task 5: Wire seeder into Worker startup **Files:** - Modify: `src/ClaudeDo.Worker/Program.cs` - [ ] **Step 1: Register seeder and run SeedMissingAsync at startup** In `src/ClaudeDo.Worker/Program.cs`, replace the "Agent file management." block (currently lines 36–39): Find: ```csharp // Agent file management. var agentsDir = Path.Combine(ClaudeDo.Data.Paths.AppDataRoot(), "agents"); Directory.CreateDirectory(agentsDir); builder.Services.AddSingleton(new AgentFileService(agentsDir)); ``` Replace with: ```csharp // Agent file management. var agentsDir = Path.Combine(ClaudeDo.Data.Paths.AppDataRoot(), "agents"); Directory.CreateDirectory(agentsDir); builder.Services.AddSingleton(new AgentFileService(agentsDir)); var defaultAgentsBundleDir = Path.Combine(AppContext.BaseDirectory, "DefaultAgents"); builder.Services.AddSingleton(sp => new DefaultAgentSeeder( defaultAgentsBundleDir, agentsDir, sp.GetService>())); ``` Then, after `var app = builder.Build();` and before `app.MapHub("/hub");`, add: Find: ```csharp using (var scope = app.Services.CreateScope()) { ClaudeDoDbContext.MigrateAndConfigure( scope.ServiceProvider.GetRequiredService()); } app.MapHub("/hub"); ``` Replace with: ```csharp using (var scope = app.Services.CreateScope()) { ClaudeDoDbContext.MigrateAndConfigure( scope.ServiceProvider.GetRequiredService()); } try { var seeder = app.Services.GetRequiredService(); var seedResult = await seeder.SeedMissingAsync(); app.Logger.LogInformation( "Default agents seeded: {Copied} copied, {Skipped} already present", seedResult.Copied, seedResult.Skipped); } catch (Exception ex) { app.Logger.LogWarning(ex, "Default agent seeding failed"); } app.MapHub("/hub"); ``` - [ ] **Step 2: Build worker to verify compile** Run: `dotnet build src/ClaudeDo.Worker/ClaudeDo.Worker.csproj` Expected: build succeeds. - [ ] **Step 3: Commit** ```bash git add src/ClaudeDo.Worker/Program.cs git commit -m "feat(worker): seed default agents on startup" ``` --- ### Task 6: Add RestoreDefaultAgents hub method (TDD) **Files:** - Modify: `tests/ClaudeDo.Worker.Tests/Hub/AgentSettingsHubTests.cs` - Modify: `src/ClaudeDo.Worker/Hub/WorkerHub.cs` The existing `AgentSettingsHubTests` doesn't exercise `WorkerHub` directly (it tests the repository). We'll add a new test file that tests the seeder restore flow end-to-end without SignalR plumbing — constructing the seeder with temp dirs and asserting the `SeedResult` round-trip. This mirrors how the file is structured today and keeps tests simple. - [ ] **Step 1: Add a restore test** Add to `tests/ClaudeDo.Worker.Tests/Hub/AgentSettingsHubTests.cs`. Inside the existing `AgentSettingsHubTests` class (before the closing brace), add: ```csharp [Fact] public async Task RestoreDefaultAgents_CopiesMissingBundledFiles() { var root = Path.Combine(Path.GetTempPath(), $"claudedo_hub_restore_{Guid.NewGuid():N}"); var bundleDir = Path.Combine(root, "bundle"); var targetDir = Path.Combine(root, "target"); try { Directory.CreateDirectory(bundleDir); Directory.CreateDirectory(targetDir); await File.WriteAllTextAsync(Path.Combine(bundleDir, "code-reviewer.md"), "body"); var seeder = new ClaudeDo.Worker.Services.DefaultAgentSeeder(bundleDir, targetDir); var result = await seeder.SeedMissingAsync(); Assert.Equal(1, result.Copied); Assert.Equal(0, result.Skipped); Assert.True(File.Exists(Path.Combine(targetDir, "code-reviewer.md"))); } finally { try { Directory.Delete(root, true); } catch { } } } ``` - [ ] **Step 2: Run the test to confirm it passes (seeder already exists)** Run: `dotnet test tests/ClaudeDo.Worker.Tests --filter "FullyQualifiedName~AgentSettingsHubTests.RestoreDefaultAgents_CopiesMissingBundledFiles"` Expected: 1 test passes. - [ ] **Step 3: Add RestoreDefaultAgents to WorkerHub** Edit `src/ClaudeDo.Worker/Hub/WorkerHub.cs`. Add a new DTO record near the top with the other DTOs (after the `ListConfigDto` line on line 30): ```csharp public record SeedResultDto(int Copied, int Skipped); ``` Update the class field block. Find: ```csharp private readonly QueueService _queue; private readonly AgentFileService _agentService; private readonly HubBroadcaster _broadcaster; private readonly IDbContextFactory _dbFactory; private readonly WorktreeMaintenanceService _wtMaintenance; private readonly TaskResetService _resetService; private readonly TaskMergeService _mergeService; ``` Replace with: ```csharp private readonly QueueService _queue; private readonly AgentFileService _agentService; private readonly DefaultAgentSeeder _seeder; private readonly HubBroadcaster _broadcaster; private readonly IDbContextFactory _dbFactory; private readonly WorktreeMaintenanceService _wtMaintenance; private readonly TaskResetService _resetService; private readonly TaskMergeService _mergeService; ``` Update the constructor. Find: ```csharp public WorkerHub( QueueService queue, AgentFileService agentService, HubBroadcaster broadcaster, IDbContextFactory dbFactory, WorktreeMaintenanceService wtMaintenance, TaskResetService resetService, TaskMergeService mergeService) { _queue = queue; _agentService = agentService; _broadcaster = broadcaster; _dbFactory = dbFactory; _wtMaintenance = wtMaintenance; _resetService = resetService; _mergeService = mergeService; } ``` Replace with: ```csharp public WorkerHub( QueueService queue, AgentFileService agentService, DefaultAgentSeeder seeder, HubBroadcaster broadcaster, IDbContextFactory dbFactory, WorktreeMaintenanceService wtMaintenance, TaskResetService resetService, TaskMergeService mergeService) { _queue = queue; _agentService = agentService; _seeder = seeder; _broadcaster = broadcaster; _dbFactory = dbFactory; _wtMaintenance = wtMaintenance; _resetService = resetService; _mergeService = mergeService; } ``` Then add the hub method. After the existing `RefreshAgents` method (currently line 126): ```csharp public async Task RestoreDefaultAgents() { var result = await _seeder.SeedMissingAsync(); return new SeedResultDto(result.Copied, result.Skipped); } ``` - [ ] **Step 4: Build worker to verify compile** Run: `dotnet build src/ClaudeDo.Worker/ClaudeDo.Worker.csproj` Expected: build succeeds. - [ ] **Step 5: Run all Worker tests to confirm no regressions** Run: `dotnet test tests/ClaudeDo.Worker.Tests` Expected: all tests pass. - [ ] **Step 6: Commit** ```bash git add src/ClaudeDo.Worker/Hub/WorkerHub.cs tests/ClaudeDo.Worker.Tests/Hub/AgentSettingsHubTests.cs git commit -m "feat(worker): expose RestoreDefaultAgents hub method" ``` --- ### Task 7: Add RestoreDefaultAgentsAsync to WorkerClient **Files:** - Modify: `src/ClaudeDo.Ui/Services/WorkerClient.cs` - [ ] **Step 1: Add the DTO record** At the bottom of `src/ClaudeDo.Ui/Services/WorkerClient.cs`, alongside the other public record declarations (after `ListConfigDto`, currently line 350), add: ```csharp public sealed record SeedResultDto(int Copied, int Skipped); ``` - [ ] **Step 2: Add the client method** In the `WorkerClient` class, after the `RefreshAgentsAsync` method (currently line 232), add: ```csharp public async Task RestoreDefaultAgentsAsync() { try { return await _hub.InvokeAsync("RestoreDefaultAgents"); } catch { return null; } } ``` - [ ] **Step 3: Build UI to verify compile** Run: `dotnet build src/ClaudeDo.Ui/ClaudeDo.Ui.csproj` Expected: build succeeds. - [ ] **Step 4: Commit** ```bash git add src/ClaudeDo.Ui/Services/WorkerClient.cs git commit -m "feat(ui): add RestoreDefaultAgentsAsync to WorkerClient" ``` --- ### Task 8: Add restore button to Settings modal **Files:** - Modify: `src/ClaudeDo.Ui/ViewModels/Modals/SettingsModalViewModel.cs` - Modify: `src/ClaudeDo.Ui/Views/Modals/SettingsModalView.axaml` - [ ] **Step 1: Add the command to the viewmodel** In `src/ClaudeDo.Ui/ViewModels/Modals/SettingsModalViewModel.cs`, add a new command method. Place it after the existing `ConfirmResetAll` method (currently ending line 162), before the `OpenPath` method: ```csharp [RelayCommand] private async Task RestoreDefaultAgents() { IsBusy = true; StatusMessage = ""; try { var result = await _worker.RestoreDefaultAgentsAsync(); if (result is null) StatusMessage = "Worker offline."; else if (result.Copied == 0 && result.Skipped == 0) StatusMessage = "No default agents bundled."; else if (result.Copied == 0) StatusMessage = "All default agents already present."; else StatusMessage = $"Restored {result.Copied} default agent(s)."; await _worker.RefreshAgentsAsync(); } catch (Exception ex) { StatusMessage = $"Restore failed: {ex.Message}"; } finally { IsBusy = false; } } ``` - [ ] **Step 2: Add a button to the settings view** Edit `src/ClaudeDo.Ui/Views/Modals/SettingsModalView.axaml`. Add a new section after the `WORKTREES` StackPanel block (which ends with `` around line 185) and before the `ABOUT` section (`` around line 187). Find: ```xml ``` Replace with: ```xml