diff --git a/src/ClaudeDo.Worker/CLAUDE.md b/src/ClaudeDo.Worker/CLAUDE.md index fafbf8b5..4a93cc23 100644 --- a/src/ClaudeDo.Worker/CLAUDE.md +++ b/src/ClaudeDo.Worker/CLAUDE.md @@ -40,7 +40,7 @@ Interfaces (e.g. `IQueueWaker`, `IPrimeClock`, `ITaskStateService`) live in an ` - `RunHistoryMcpTools` — `ListRuns`, `GetRun`, `GetTaskLog` (latest run's log, tail-capped at 256 KB) - `AgentMcpTools` — `ListAgents` - `LifecycleMcpTools` — `ResetFailedTask` - - `AppSettingsMcpTools` — `GetAppSettings` (read-only) + - `AppSettingsMcpTools` — `GetAppSettings` (read-only; includes `MaxParallelExecutions`) - `AttachmentMcpTools` — `AddTaskAttachment(taskId, fileName, textContent?|base64Content?)`, `ListTaskAttachments`, `RemoveTaskAttachment`. Re-attaching the same fileName overwrites; add/remove refuse on a Running task. - `ExternalMcpService` also exposes two daily-prep tools: - `GetDailyPrepCandidates` — returns Idle, non-blocked tasks in a git repo NOT excluded by `AppSettings.ReportExcludedPaths` and not already `IsMyDay`, plus the current Idle MyDay tasks and `maxTasks` (= `DailyPrepMaxTasks`). Repo-exclusion logic lives in the `DailyPrepFilter` helper (same file). diff --git a/src/ClaudeDo.Worker/External/AppSettingsMcpTools.cs b/src/ClaudeDo.Worker/External/AppSettingsMcpTools.cs index 8e065f5e..b7c8c72f 100644 --- a/src/ClaudeDo.Worker/External/AppSettingsMcpTools.cs +++ b/src/ClaudeDo.Worker/External/AppSettingsMcpTools.cs @@ -8,6 +8,7 @@ namespace ClaudeDo.Worker.External; public sealed record AppSettingsReadDto( string DefaultModel, int DefaultMaxTurns, string DefaultPermissionMode, + int MaxParallelExecutions, string WorktreeStrategy, string? CentralWorktreeRoot, bool WorktreeAutoCleanupEnabled, int WorktreeAutoCleanupDays); @@ -18,13 +19,14 @@ public sealed class AppSettingsMcpTools public AppSettingsMcpTools(IDbContextFactory dbFactory) => _dbFactory = dbFactory; - [McpServerTool, Description("Read the worker's app-level defaults (model, max turns, permission mode, worktree strategy). Read-only.")] + [McpServerTool, Description("Read the worker's app-level defaults (model, max turns, permission mode, max parallel execution slots, worktree strategy). Read-only.")] public async Task GetAppSettings(CancellationToken cancellationToken) { using var ctx = await _dbFactory.CreateDbContextAsync(cancellationToken); var row = await new AppSettingsRepository(ctx).GetAsync(cancellationToken); return new AppSettingsReadDto( row.DefaultModel, row.DefaultMaxTurns, row.DefaultPermissionMode, + row.MaxParallelExecutions, row.WorktreeStrategy, row.CentralWorktreeRoot, row.WorktreeAutoCleanupEnabled, row.WorktreeAutoCleanupDays); } diff --git a/tests/ClaudeDo.Worker.Tests/External/AppSettingsMcpToolsTests.cs b/tests/ClaudeDo.Worker.Tests/External/AppSettingsMcpToolsTests.cs new file mode 100644 index 00000000..cff17c50 --- /dev/null +++ b/tests/ClaudeDo.Worker.Tests/External/AppSettingsMcpToolsTests.cs @@ -0,0 +1,31 @@ +using ClaudeDo.Data.Models; +using ClaudeDo.Worker.External; +using ClaudeDo.Worker.Tests.Infrastructure; +using Microsoft.EntityFrameworkCore; + +namespace ClaudeDo.Worker.Tests.External; + +public sealed class AppSettingsMcpToolsTests : IDisposable +{ + private readonly DbFixture _db = new(); + + public void Dispose() => _db.Dispose(); + + [Fact] + public async Task GetAppSettings_ReturnsMaxParallelExecutionsFromDb() + { + using (var ctx = _db.CreateContext()) + { + var row = await ctx.AppSettings.FirstOrDefaultAsync(s => s.Id == AppSettingsEntity.SingletonId) + ?? throw new InvalidOperationException("Expected seeded app_settings row."); + row.MaxParallelExecutions = 3; + await ctx.SaveChangesAsync(); + } + + var sut = new AppSettingsMcpTools(_db.CreateFactory()); + + var result = await sut.GetAppSettings(CancellationToken.None); + + Assert.Equal(3, result.MaxParallelExecutions); + } +}