docs(superpowers): add default-agents plan and design spec

This commit is contained in:
mika kuns
2026-04-23 13:08:23 +02:00
parent 3c420acd54
commit a135485339
2 changed files with 1029 additions and 0 deletions

View File

@@ -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 `<ItemGroup>` blocks and before the final `<PropertyGroup>`, add:
```xml
<ItemGroup>
<Content Include="DefaultAgents\*.md">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
```
- [ ] **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<DefaultAgentSeeder>? _logger;
public DefaultAgentSeeder(string bundleDir, string targetDir, ILogger<DefaultAgentSeeder>? logger = null)
{
_bundleDir = bundleDir;
_targetDir = targetDir;
_logger = logger;
}
public async Task<SeedResult> 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 3639):
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<Microsoft.Extensions.Logging.ILogger<DefaultAgentSeeder>>()));
```
Then, after `var app = builder.Build();` and before `app.MapHub<WorkerHub>("/hub");`, add:
Find:
```csharp
using (var scope = app.Services.CreateScope())
{
ClaudeDoDbContext.MigrateAndConfigure(
scope.ServiceProvider.GetRequiredService<ClaudeDoDbContext>());
}
app.MapHub<WorkerHub>("/hub");
```
Replace with:
```csharp
using (var scope = app.Services.CreateScope())
{
ClaudeDoDbContext.MigrateAndConfigure(
scope.ServiceProvider.GetRequiredService<ClaudeDoDbContext>());
}
try
{
var seeder = app.Services.GetRequiredService<DefaultAgentSeeder>();
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<WorkerHub>("/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<ClaudeDoDbContext> _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<ClaudeDoDbContext> _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<ClaudeDoDbContext> 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<ClaudeDoDbContext> 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<SeedResultDto> 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<SeedResultDto?> RestoreDefaultAgentsAsync()
{
try
{
return await _hub.InvokeAsync<SeedResultDto>("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 `</StackPanel>` around line 185) and before the `ABOUT` section (`<!-- ABOUT -->` around line 187).
Find:
```xml
</StackPanel>
<!-- ABOUT -->
```
Replace with:
```xml
</StackPanel>
<!-- AGENTS -->
<StackPanel Spacing="0">
<TextBlock Classes="section-label" Text="AGENTS"/>
<Border Classes="section">
<StackPanel Spacing="8">
<TextBlock Text="Restore bundled default agents (code-reviewer, test-writer, debugger, security-reviewer, explorer, researcher). Existing files are not overwritten."
FontSize="11"
TextWrapping="Wrap"
Foreground="{DynamicResource TextDimBrush}"/>
<Button Content="Restore default agents"
Command="{Binding RestoreDefaultAgentsCommand}"
IsEnabled="{Binding !IsBusy}"
HorizontalAlignment="Left"/>
</StackPanel>
</Border>
</StackPanel>
<!-- ABOUT -->
```
- [ ] **Step 3: Build UI to verify compile**
Run: `dotnet build src/ClaudeDo.Ui/ClaudeDo.Ui.csproj`
Expected: build succeeds.
- [ ] **Step 4: Manual smoke test**
Start the worker and the UI. Open Settings (3-dots next to username). The AGENTS section should appear with a "Restore default agents" button.
1. With `~/.todo-app/agents/` empty (delete any existing `.md` files first, back them up if needed): click the button. Status should read "Restored N default agent(s)." The files should appear in the folder.
2. Click again. Status should read "All default agents already present."
3. Modify one of the restored files. Click restore. The modified file content should be preserved.
If any step fails, stop and fix before committing.
- [ ] **Step 5: Commit**
```bash
git add src/ClaudeDo.Ui/ViewModels/Modals/SettingsModalViewModel.cs src/ClaudeDo.Ui/Views/Modals/SettingsModalView.axaml
git commit -m "feat(ui): add Restore default agents button to Settings modal"
```
---
## Verification
At the end, run the full test suite and build all projects:
```bash
dotnet build src/ClaudeDo.Worker/ClaudeDo.Worker.csproj
dotnet build src/ClaudeDo.Ui/ClaudeDo.Ui.csproj
dotnet build src/ClaudeDo.Data/ClaudeDo.Data.csproj
dotnet test tests/ClaudeDo.Worker.Tests
```
Expected: all builds succeed, all tests pass.
Additionally, start the Worker once with an empty `~/.todo-app/agents/` folder and confirm the log line:
> `Default agents seeded: 6 copied, 0 already present`
Then confirm `~/.todo-app/agents/` contains all 6 markdown files.