refactor: drop the remaining single-implementation interfaces

IBaseDirtyChecker, IInteractiveLaunchSpecService and the LaunchSpec wrapper
had one implementation and one caller each; ProcessRunnerAdapter existed only
to give a static class an interface, and InstallArtifactLocator used
inheritance for two constructor arguments. The external MCP container now
shares its singletons through a Share<T> helper instead of 17 near-identical
registrations.
This commit is contained in:
mika kuns
2026-08-26 13:55:51 +02:00
parent c593be2f02
commit 0f007c5367
14 changed files with 103 additions and 151 deletions
+29 -29
View File
@@ -89,7 +89,7 @@ builder.Services.AddSingleton<PendingQuestionRegistry>();
builder.Services.AddSingleton<IRepoCloner, GitRepoCloner>();
builder.Services.AddSingleton<ISessionSkillRegistry, SessionSkillRegistry>();
builder.Services.AddSingleton<ISessionSkillSeeder, SessionSkillSeeder>();
builder.Services.AddSingleton<IInteractiveLaunchSpecService, InteractiveLaunchSpecService>();
builder.Services.AddSingleton<InteractiveLaunchSpecService>();
builder.Services.AddSingleton<TaskRunner>();
builder.Services.AddSingleton<WorktreeMaintenanceService>();
builder.Services.AddSingleton<TaskResetService>();
@@ -112,7 +112,7 @@ builder.Services.AddSingleton<Func<ITaskStateService>>(sp => () => sp.GetRequire
// PlanningMergeOrchestrator itself depends on ITaskStateService, so TaskStateService can only
// reach it lazily (Func<IActiveMergeState>) — same cycle-breaking shape as the Func above.
builder.Services.AddSingleton<Func<IActiveMergeState>>(sp => () => sp.GetRequiredService<PlanningMergeOrchestrator>());
builder.Services.AddSingleton<IBaseDirtyChecker, BaseDirtyChecker>();
builder.Services.AddSingleton<BaseDirtyChecker>();
builder.Services.AddSingleton<ITaskStateService>(sp => new TaskStateService(
sp.GetRequiredService<IDbContextFactory<ClaudeDoDbContext>>(),
sp.GetRequiredService<HubBroadcaster>(),
@@ -120,7 +120,7 @@ builder.Services.AddSingleton<ITaskStateService>(sp => new TaskStateService(
sp.GetRequiredService<PlanningChainCoordinator>(),
sp.GetRequiredService<RunCancellationRegistry>(),
sp.GetRequiredService<Func<IActiveMergeState>>(),
sp.GetRequiredService<IBaseDirtyChecker>(),
sp.GetRequiredService<BaseDirtyChecker>(),
sp.GetRequiredService<ILogger<TaskStateService>>()));
// Agent file management.
@@ -201,12 +201,6 @@ builder.Services.AddScoped<PlanningMcpService>();
builder.Services.AddScoped<FindingsStoreLocator>();
builder.Services.AddScoped<TaskRunFindingsMcpTools>();
builder.Services.AddMcpServer()
// Stateless: no Mcp-Session-Id, so a worker restart doesn't 404 the sessions that
// outlive it (ConPTY tiles in the UI process, externally registered claude sessions),
// and a 2026-07-28 client isn't refused back to the initialize handshake. Nothing here
// needs the stateful-only features (sampling, elicitation, resource subscriptions,
// unsolicited notifications); in-tool progress works in both modes. Implies no legacy
// SSE endpoint — setting EnableLegacySse alongside this throws at startup.
.WithHttpTransport(o => o.Stateless = true)
.WithTools<PlanningMcpService>()
.WithTools<TaskRunMcpService>()
@@ -308,27 +302,38 @@ WebApplication? externalApp = null;
if (cfg.ExternalMcpPort > 0)
{
var externalBuilder = WebApplication.CreateBuilder();
// Hand the second container the SAME singleton instances, so both apps operate on one
// runtime state instead of two parallel copies of the queue/git/merge stack.
void Share<T>() where T : class =>
externalBuilder.Services.AddSingleton(app.Services.GetRequiredService<T>());
externalBuilder.Services.AddSingleton(cfg);
externalBuilder.Services.AddSingleton(app.Services.GetRequiredService<HubBroadcaster>());
externalBuilder.Services.AddSingleton(app.Services.GetRequiredService<QueueService>());
externalBuilder.Services.AddSingleton(app.Services.GetRequiredService<OverrideSlotService>());
externalBuilder.Services.AddSingleton(app.Services.GetRequiredService<IDbContextFactory<ClaudeDoDbContext>>());
externalBuilder.Services.AddSingleton(app.Services.GetRequiredService<ITaskStateService>());
externalBuilder.Services.AddSingleton(app.Services.GetRequiredService<IQueueWaker>());
Share<HubBroadcaster>();
Share<QueueService>();
Share<OverrideSlotService>();
Share<IDbContextFactory<ClaudeDoDbContext>>();
Share<ITaskStateService>();
Share<IQueueWaker>();
Share<WorktreeManager>();
Share<AgentFileService>();
Share<TaskResetService>();
Share<GitService>();
Share<BaseDirtyChecker>();
Share<WorktreeMaintenanceService>();
Share<TaskMergeService>();
Share<PlanningMergeOrchestrator>();
Share<InteractiveReviewSubmissionService>();
Share<AttachmentStore>();
Share<FindingsStore>();
externalBuilder.Services.AddScoped<ClaudeDoDbContext>(sp =>
sp.GetRequiredService<IDbContextFactory<ClaudeDoDbContext>>().CreateDbContext());
externalBuilder.Services.AddScoped<TaskRepository>();
externalBuilder.Services.AddScoped<ListRepository>();
externalBuilder.Services.AddScoped<TaskRunRepository>();
externalBuilder.Services.AddSingleton(app.Services.GetRequiredService<WorktreeManager>());
externalBuilder.Services.AddSingleton(app.Services.GetRequiredService<AgentFileService>());
externalBuilder.Services.AddSingleton(app.Services.GetRequiredService<TaskResetService>());
externalBuilder.Services.AddSingleton(app.Services.GetRequiredService<GitService>());
externalBuilder.Services.AddSingleton(app.Services.GetRequiredService<IBaseDirtyChecker>());
externalBuilder.Services.AddSingleton(app.Services.GetRequiredService<WorktreeMaintenanceService>());
externalBuilder.Services.AddSingleton(app.Services.GetRequiredService<TaskMergeService>());
externalBuilder.Services.AddSingleton(app.Services.GetRequiredService<PlanningMergeOrchestrator>());
externalBuilder.Services.AddSingleton(app.Services.GetRequiredService<InteractiveReviewSubmissionService>());
externalBuilder.Services.AddScoped<TaskAttachmentRepository>();
externalBuilder.Services.AddSingleton<HandoffRoundTracker>();
externalBuilder.Services.AddScoped<FindingsStoreLocator>();
externalBuilder.Services.AddScoped<ExternalMcpService>();
externalBuilder.Services.AddScoped<BatchMcpTools>();
externalBuilder.Services.AddScoped<ListMcpTools>();
@@ -336,16 +341,11 @@ if (cfg.ExternalMcpPort > 0)
externalBuilder.Services.AddScoped<RunHistoryMcpTools>();
externalBuilder.Services.AddScoped<AgentMcpTools>();
externalBuilder.Services.AddScoped<LifecycleMcpTools>();
externalBuilder.Services.AddSingleton<HandoffRoundTracker>();
externalBuilder.Services.AddScoped<HandoffMcpTools>();
externalBuilder.Services.AddScoped<AppSettingsMcpTools>();
externalBuilder.Services.AddScoped<TaskWaitMcpTools>();
externalBuilder.Services.AddScoped<QueueStateMcpTools>();
externalBuilder.Services.AddSingleton(app.Services.GetRequiredService<AttachmentStore>());
externalBuilder.Services.AddScoped<TaskAttachmentRepository>();
externalBuilder.Services.AddScoped<AttachmentMcpTools>();
externalBuilder.Services.AddSingleton(app.Services.GetRequiredService<FindingsStore>());
externalBuilder.Services.AddScoped<FindingsStoreLocator>();
externalBuilder.Services.AddScoped<FindingsMcpTools>();
externalBuilder.Services.AddMcpServer()
.WithHttpTransport(o => o.Stateless = true)