refactor: collapse single-implementation interfaces
Nine interfaces had exactly one implementation and no test double — they existed only to be named twice in a DI registration: IFindingsStore, IFindingsStoreLocator, IPrimeScheduleSignal, IRefineRunner, IWeekReportService, IMergeCoordinator, IMissionControlPane, IOnlineLoginService, ITaskListFilter. Consumers now depend on the concrete type; the DTO records that shared those files moved next to their implementation. IInteractiveLaunchSpecService stays — it carries 54 lines of contract documentation, which is not ceremony. IMergeCoordinator in particular had a redundant null object: MergeCoordinator with a null Handler already no-ops, and every test used the real class with Handler set. Filtering/ collapses from 8 files to 1. ITaskListFilter and TaskListFilterBase were a double abstraction over four predicates, with MatchesAsContext => false declared in both. SmartFlagFilter also compiled its expression twice (its own _flag plus the inherited Matches cache) — it now uses the cache. StaticTokenAuthProvider was in src but production uses ZitadelAuthProvider; it is a test double, so it moves to the test project. Its own test goes away with it.
This commit is contained in:
+3
-3
@@ -11,10 +11,10 @@ public sealed record SaveFindingResult(
|
||||
[McpServerToolType]
|
||||
public sealed class FindingsMcpTools
|
||||
{
|
||||
private readonly IFindingsStore _store;
|
||||
private readonly IFindingsStoreLocator _locator;
|
||||
private readonly FindingsStore _store;
|
||||
private readonly FindingsStoreLocator _locator;
|
||||
|
||||
public FindingsMcpTools(IFindingsStore store, IFindingsStoreLocator locator)
|
||||
public FindingsMcpTools(FindingsStore store, FindingsStoreLocator locator)
|
||||
{
|
||||
_store = store;
|
||||
_locator = locator;
|
||||
|
||||
@@ -4,13 +4,29 @@ using ClaudeDo.Worker.Git;
|
||||
|
||||
namespace ClaudeDo.Worker.Findings;
|
||||
|
||||
/// <summary>One durable finding, as handed in by a caller. Git and DB lookups happen above this layer.</summary>
|
||||
public sealed record FindingInput(
|
||||
string Slug,
|
||||
string Title,
|
||||
string Body,
|
||||
string Scope,
|
||||
string SourceTaskId,
|
||||
string VerifiedAgainst);
|
||||
|
||||
public sealed record SaveFindingOutcome(
|
||||
string Slug,
|
||||
string Path,
|
||||
bool Created,
|
||||
int TotalFindings,
|
||||
bool NearCapacity);
|
||||
|
||||
/// <summary>
|
||||
/// Owns <working-dir>/.claudedo/ — one markdown file per finding plus a rebuilt INDEX.md.
|
||||
/// Pure filesystem: the caller supplies the head commit and source task id.
|
||||
/// INDEX.md is always regenerated from the files on disk, never patched, so findings the user
|
||||
/// deleted or renamed by hand disappear from the index on the next write.
|
||||
/// </summary>
|
||||
public sealed class FindingsStore : IFindingsStore
|
||||
public sealed class FindingsStore
|
||||
{
|
||||
/// <summary>INDEX.md is read by every run; past this many entries it stops paying for itself.</summary>
|
||||
public const int WarnThreshold = 80;
|
||||
|
||||
@@ -3,12 +3,15 @@ using ClaudeDo.Data.Repositories;
|
||||
|
||||
namespace ClaudeDo.Worker.Findings;
|
||||
|
||||
/// <summary>The main checkout a finding belongs to, plus whether its store is committed with the repo.</summary>
|
||||
public sealed record FindingsTarget(string ListId, string ListName, string WorkingDir, bool Tracked);
|
||||
|
||||
/// <summary>
|
||||
/// Maps an MCP call to the project whose findings store it targets. Always resolves to the list's
|
||||
/// WorkingDir — the main checkout — even when the caller runs inside a worktree, because concurrent
|
||||
/// writes into worktree copies would produce INDEX.md merge conflicts.
|
||||
/// </summary>
|
||||
public sealed class FindingsStoreLocator : IFindingsStoreLocator
|
||||
public sealed class FindingsStoreLocator
|
||||
{
|
||||
private readonly TaskRepository _tasks;
|
||||
private readonly ListRepository _lists;
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
namespace ClaudeDo.Worker.Findings;
|
||||
|
||||
/// <summary>One durable finding, as handed in by a caller. Git and DB lookups happen above this layer.</summary>
|
||||
public sealed record FindingInput(
|
||||
string Slug,
|
||||
string Title,
|
||||
string Body,
|
||||
string Scope,
|
||||
string SourceTaskId,
|
||||
string VerifiedAgainst);
|
||||
|
||||
public sealed record SaveFindingOutcome(
|
||||
string Slug,
|
||||
string Path,
|
||||
bool Created,
|
||||
int TotalFindings,
|
||||
bool NearCapacity);
|
||||
|
||||
public interface IFindingsStore
|
||||
{
|
||||
Task<SaveFindingOutcome> SaveAsync(string workingDir, FindingInput input, CancellationToken ct, bool tracked = false);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace ClaudeDo.Worker.Findings;
|
||||
|
||||
/// <summary>The main checkout a finding belongs to, plus whether its store is committed with the repo.</summary>
|
||||
public sealed record FindingsTarget(string ListId, string ListName, string WorkingDir, bool Tracked);
|
||||
|
||||
public interface IFindingsStoreLocator
|
||||
{
|
||||
Task<FindingsTarget> ResolveForTaskAsync(string taskId, CancellationToken ct);
|
||||
Task<FindingsTarget> ResolveForListAsync(string listIdOrName, CancellationToken ct);
|
||||
}
|
||||
@@ -229,11 +229,11 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
|
||||
private readonly PlanningAggregator _planningAggregator;
|
||||
private readonly PlanningMergeOrchestrator _planningMergeOrchestrator;
|
||||
private readonly PlanningChainCoordinator _planningChain;
|
||||
private readonly IPrimeScheduleSignal _primeSignal;
|
||||
private readonly PrimeScheduleSignal _primeSignal;
|
||||
private readonly IPrimeRunner _primeRunner;
|
||||
private readonly ITaskStateService _state;
|
||||
private readonly IWeekReportService _report;
|
||||
private readonly IRefineRunner _refineRunner;
|
||||
private readonly WeekReportService _report;
|
||||
private readonly RefineRunner _refineRunner;
|
||||
private readonly WorkerConfig _cfg;
|
||||
private readonly OnlineInboxConfig _onlineInboxConfig;
|
||||
private readonly OnlineTokenStore _onlineTokenStore;
|
||||
@@ -264,11 +264,11 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
|
||||
PlanningAggregator planningAggregator,
|
||||
PlanningMergeOrchestrator planningMergeOrchestrator,
|
||||
PlanningChainCoordinator planningChain,
|
||||
IPrimeScheduleSignal primeSignal,
|
||||
PrimeScheduleSignal primeSignal,
|
||||
IPrimeRunner primeRunner,
|
||||
ITaskStateService state,
|
||||
IWeekReportService report,
|
||||
IRefineRunner refineRunner,
|
||||
WeekReportService report,
|
||||
RefineRunner refineRunner,
|
||||
WorkerConfig cfg,
|
||||
OnlineInboxConfig onlineInboxConfig,
|
||||
OnlineTokenStore onlineTokenStore,
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
using ClaudeDo.Worker.Online.Interfaces;
|
||||
|
||||
namespace ClaudeDo.Worker.Online;
|
||||
|
||||
/// <summary>
|
||||
/// Simple <see cref="IOnlineAuthProvider"/> that returns a fixed token supplied at construction.
|
||||
/// Used as the default DI registration until <c>ZitadelAuthProvider</c> is wired (Phase 2).
|
||||
/// Also serves as the test double.
|
||||
/// </summary>
|
||||
public sealed class StaticTokenAuthProvider : IOnlineAuthProvider
|
||||
{
|
||||
private readonly string? _token;
|
||||
|
||||
public StaticTokenAuthProvider(string? token = null)
|
||||
{
|
||||
_token = token;
|
||||
}
|
||||
|
||||
public Task<string?> GetAccessTokenAsync(CancellationToken ct = default)
|
||||
=> Task.FromResult(_token);
|
||||
|
||||
public Task<string?> GetAccessTokenAsync(bool forceRefresh, CancellationToken ct = default)
|
||||
=> Task.FromResult(_token);
|
||||
}
|
||||
@@ -86,7 +86,7 @@ public sealed class WindowsTerminalLauncher : ITerminalLauncher
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// The raw claude CLI args for a --resume launch, shared with InteractiveLaunchSpecService
|
||||
// The raw claude CLI args for a --resume launch, shared with IInteractiveLaunchSpecService
|
||||
// (which needs the bare Exe/Args pair for a ConPTY host, not a wrapped pwsh command line).
|
||||
internal static IReadOnlyList<string> BuildResumeArgs(string claudeSessionId) =>
|
||||
new[] { "--resume", claudeSessionId };
|
||||
@@ -99,7 +99,7 @@ public sealed class WindowsTerminalLauncher : ITerminalLauncher
|
||||
BuildPwshCommand(claudePath, BuildPlanningStartArgs(ctx));
|
||||
|
||||
// The raw claude CLI args for an interactive planning START, shared with the embedded-ConPTY
|
||||
// planning path (InteractiveLaunchSpecService), which needs the bare Exe/Args pair rather than
|
||||
// planning path (IInteractiveLaunchSpecService), which needs the bare Exe/Args pair rather than
|
||||
// a pwsh-wrapped command line. Arg order matters: variadic flags (--allowedTools, --add-dir)
|
||||
// come first; the single-line kickoff prompt is positional, so it must follow a single-value
|
||||
// flag (--append-system-prompt-file) or a variadic flag would swallow it.
|
||||
@@ -176,7 +176,7 @@ public sealed class WindowsTerminalLauncher : ITerminalLauncher
|
||||
?? throw new TerminalLaunchException("Failed to start Windows Terminal process.");
|
||||
}
|
||||
|
||||
// Not private: reused by InteractiveLaunchSpecService to resolve the claude executable
|
||||
// Not private: reused by IInteractiveLaunchSpecService to resolve the claude executable
|
||||
// for a ConPTY-hosted launch spec the same way a wt-launched terminal resolves it.
|
||||
internal static string? Resolve(string pathOrName)
|
||||
{
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace ClaudeDo.Worker.Prime;
|
||||
public interface IPrimeScheduleSignal
|
||||
{
|
||||
void Signal();
|
||||
CancellationToken CurrentToken { get; }
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace ClaudeDo.Worker.Prime;
|
||||
|
||||
public sealed class PrimeScheduleSignal : IPrimeScheduleSignal, IDisposable
|
||||
public sealed class PrimeScheduleSignal : IDisposable
|
||||
{
|
||||
private CancellationTokenSource _cts = new();
|
||||
private readonly object _lock = new();
|
||||
|
||||
@@ -21,7 +21,7 @@ public sealed class PrimeScheduler : BackgroundService
|
||||
private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
|
||||
private readonly IPrimeRunner _runner;
|
||||
private readonly IPrimeClock _clock;
|
||||
private readonly IPrimeScheduleSignal _signal;
|
||||
private readonly PrimeScheduleSignal _signal;
|
||||
private readonly IPrimeBroadcaster _broadcaster;
|
||||
private readonly PrimeSchedulerOptions _options;
|
||||
private readonly ILogger<PrimeScheduler> _logger;
|
||||
@@ -30,7 +30,7 @@ public sealed class PrimeScheduler : BackgroundService
|
||||
IDbContextFactory<ClaudeDoDbContext> dbFactory,
|
||||
IPrimeRunner runner,
|
||||
IPrimeClock clock,
|
||||
IPrimeScheduleSignal signal,
|
||||
PrimeScheduleSignal signal,
|
||||
IPrimeBroadcaster broadcaster,
|
||||
PrimeSchedulerOptions options,
|
||||
ILogger<PrimeScheduler> logger)
|
||||
|
||||
@@ -67,7 +67,7 @@ builder.Services.AddDbContextFactory<ClaudeDoDbContext>(opt =>
|
||||
builder.Services.AddSingleton(cfg);
|
||||
builder.Services.AddSingleton<AttachmentStore>();
|
||||
// Singleton so the always-on and in-task MCP servers share the same index write lock.
|
||||
builder.Services.AddSingleton<IFindingsStore, FindingsStore>();
|
||||
builder.Services.AddSingleton<FindingsStore>();
|
||||
builder.Services.AddHostedService<StaleTaskRecovery>();
|
||||
builder.Services.AddHostedService<OrphanRecovery>();
|
||||
builder.Services.AddHostedService<AttachmentOrphanRecovery>();
|
||||
@@ -140,7 +140,7 @@ builder.Services.AddSingleton<OverrideSlotService>();
|
||||
builder.Services.AddSingleton<IClaudeHistoryReader>(_ =>
|
||||
new ClaudeHistoryReader(Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".claude", "projects")));
|
||||
builder.Services.AddSingleton<IWeekReportService, WeekReportService>();
|
||||
builder.Services.AddSingleton<WeekReportService>();
|
||||
|
||||
// Usage
|
||||
builder.Services.AddSingleton<ITranscriptUsageReader, TranscriptUsageReader>();
|
||||
@@ -154,14 +154,13 @@ builder.Services.AddSingleton<TokenTrackerService>();
|
||||
// Prime Claude
|
||||
builder.Services.AddSingleton<IPrimeClock, PrimeClock>();
|
||||
builder.Services.AddSingleton<PrimeScheduleSignal>();
|
||||
builder.Services.AddSingleton<IPrimeScheduleSignal>(sp => sp.GetRequiredService<PrimeScheduleSignal>());
|
||||
builder.Services.AddSingleton<IPrimeRunner, PrimeRunner>();
|
||||
builder.Services.AddSingleton(PrimeSchedulerOptions.Default);
|
||||
builder.Services.AddSingleton<IPrimeBroadcaster>(sp => sp.GetRequiredService<HubBroadcaster>());
|
||||
builder.Services.AddHostedService<PrimeScheduler>();
|
||||
|
||||
// Refine
|
||||
builder.Services.AddSingleton<IRefineRunner, RefineRunner>();
|
||||
builder.Services.AddSingleton<RefineRunner>();
|
||||
builder.Services.AddSingleton<IRefineBroadcaster>(sp => sp.GetRequiredService<HubBroadcaster>());
|
||||
|
||||
// "Continue on session limit reset" toggle — depends on UsageState, registered below.
|
||||
@@ -199,7 +198,7 @@ builder.Services.AddScoped<ClaudeDoDbContext>(sp =>
|
||||
builder.Services.AddScoped<TaskRepository>();
|
||||
builder.Services.AddScoped<ListRepository>();
|
||||
builder.Services.AddScoped<PlanningMcpService>();
|
||||
builder.Services.AddScoped<IFindingsStoreLocator, FindingsStoreLocator>();
|
||||
builder.Services.AddScoped<FindingsStoreLocator>();
|
||||
builder.Services.AddScoped<TaskRunFindingsMcpTools>();
|
||||
builder.Services.AddMcpServer()
|
||||
.WithHttpTransport()
|
||||
@@ -339,8 +338,8 @@ if (cfg.ExternalMcpPort > 0)
|
||||
externalBuilder.Services.AddSingleton(app.Services.GetRequiredService<AttachmentStore>());
|
||||
externalBuilder.Services.AddScoped<TaskAttachmentRepository>();
|
||||
externalBuilder.Services.AddScoped<AttachmentMcpTools>();
|
||||
externalBuilder.Services.AddSingleton<IFindingsStore>(app.Services.GetRequiredService<IFindingsStore>());
|
||||
externalBuilder.Services.AddScoped<IFindingsStoreLocator, FindingsStoreLocator>();
|
||||
externalBuilder.Services.AddSingleton(app.Services.GetRequiredService<FindingsStore>());
|
||||
externalBuilder.Services.AddScoped<FindingsStoreLocator>();
|
||||
externalBuilder.Services.AddScoped<FindingsMcpTools>();
|
||||
externalBuilder.Services.AddMcpServer()
|
||||
.WithHttpTransport()
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace ClaudeDo.Worker.Refine;
|
||||
|
||||
public interface IRefineRunner
|
||||
{
|
||||
Task<RefineRunOutcome> RefineAsync(string taskId, CancellationToken ct);
|
||||
}
|
||||
|
||||
public sealed record RefineRunOutcome(bool Success, string Message);
|
||||
@@ -7,7 +7,9 @@ using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
|
||||
|
||||
namespace ClaudeDo.Worker.Refine;
|
||||
|
||||
public sealed class RefineRunner : IRefineRunner
|
||||
public sealed record RefineRunOutcome(bool Success, string Message);
|
||||
|
||||
public sealed class RefineRunner
|
||||
{
|
||||
private static readonly TimeSpan RunTimeout = TimeSpan.FromMinutes(5);
|
||||
private const int MaxTurns = 5;
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace ClaudeDo.Worker.Report.Interfaces;
|
||||
|
||||
public interface IWeekReportService
|
||||
{
|
||||
Task<string?> GetStoredAsync(DateOnly start, DateOnly end, CancellationToken ct = default);
|
||||
Task<string> GenerateAsync(DateOnly start, DateOnly end, CancellationToken ct = default);
|
||||
}
|
||||
@@ -8,7 +8,7 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ClaudeDo.Worker.Report;
|
||||
|
||||
public sealed class WeekReportService : IWeekReportService
|
||||
public sealed class WeekReportService
|
||||
{
|
||||
private static readonly string[] DefaultExcludes = { @"C:\Private" };
|
||||
private const string NoActivity = "_No activity in this period._";
|
||||
|
||||
@@ -11,12 +11,12 @@ public sealed record SaveTaskFindingResult(
|
||||
[McpServerToolType]
|
||||
public sealed class TaskRunFindingsMcpTools
|
||||
{
|
||||
private readonly IFindingsStore _store;
|
||||
private readonly IFindingsStoreLocator _locator;
|
||||
private readonly FindingsStore _store;
|
||||
private readonly FindingsStoreLocator _locator;
|
||||
private readonly TaskRunMcpContextAccessor _ctx;
|
||||
|
||||
public TaskRunFindingsMcpTools(
|
||||
IFindingsStore store, IFindingsStoreLocator locator, TaskRunMcpContextAccessor ctx)
|
||||
FindingsStore store, FindingsStoreLocator locator, TaskRunMcpContextAccessor ctx)
|
||||
{
|
||||
_store = store;
|
||||
_locator = locator;
|
||||
|
||||
Reference in New Issue
Block a user