From a1354853393cf1cb6b0796aa853baeeb23d5d754 Mon Sep 17 00:00:00 2001 From: mika kuns Date: Thu, 23 Apr 2026 13:08:23 +0200 Subject: [PATCH] docs(superpowers): add default-agents plan and design spec --- .../plans/2026-04-23-default-agents.md | 852 ++++++++++++++++++ .../specs/2026-04-23-default-agents-design.md | 177 ++++ 2 files changed, 1029 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-23-default-agents.md create mode 100644 docs/superpowers/specs/2026-04-23-default-agents-design.md diff --git a/docs/superpowers/plans/2026-04-23-default-agents.md b/docs/superpowers/plans/2026-04-23-default-agents.md new file mode 100644 index 0000000..c428ea4 --- /dev/null +++ b/docs/superpowers/plans/2026-04-23-default-agents.md @@ -0,0 +1,852 @@ +# 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 + + + + + + + + +