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.
53 lines
2.4 KiB
C#
53 lines
2.4 KiB
C#
using System.ComponentModel;
|
|
using ClaudeDo.Worker.Findings;
|
|
using ClaudeDo.Worker.Git;
|
|
using ModelContextProtocol.Server;
|
|
|
|
namespace ClaudeDo.Worker.Runner;
|
|
|
|
public sealed record SaveTaskFindingResult(
|
|
bool Saved, string Slug, string Path, int TotalFindings, bool NearCapacity);
|
|
|
|
[McpServerToolType]
|
|
public sealed class TaskRunFindingsMcpTools
|
|
{
|
|
private readonly FindingsStore _store;
|
|
private readonly FindingsStoreLocator _locator;
|
|
private readonly TaskRunMcpContextAccessor _ctx;
|
|
|
|
public TaskRunFindingsMcpTools(
|
|
FindingsStore store, FindingsStoreLocator locator, TaskRunMcpContextAccessor ctx)
|
|
{
|
|
_store = store;
|
|
_locator = locator;
|
|
_ctx = ctx;
|
|
}
|
|
|
|
[McpServerTool, Description(
|
|
"Record a durable trap you just hit, so future runs read it instead of losing the same " +
|
|
"turns. Only for findings that are lasting, non-obvious and behaviour-changing (\"X looks " +
|
|
"like Y but is Z — do W instead\"); a bug you fixed is git history, not a finding. When " +
|
|
"unsure, don't. Re-using a slug overwrites it. Writes to this task's own project; " +
|
|
"nearCapacity=true means prune before adding more.")]
|
|
public async Task<SaveTaskFindingResult> SaveFinding(
|
|
[Description("Stable lowercase kebab-case id, max 60 chars, e.g. 'conpty-arg-quoting'. Same slug overwrites.")] string slug,
|
|
[Description("One full sentence stating the trap itself — this is the index line other agents scan.")] string title,
|
|
[Description("2-6 sentences: what happens, why it does not look like that, what to do instead.")] string body,
|
|
[Description("Repo-relative path or subsystem the finding applies to, e.g. 'src/ClaudeDo.Worker/Planning'.")] string scope = "",
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var taskId = _ctx.Current.CallerTaskId;
|
|
var target = await _locator.ResolveForTaskAsync(taskId, cancellationToken);
|
|
var head = await GitHead.ShortAsync(target.WorkingDir, cancellationToken);
|
|
|
|
var outcome = await _store.SaveAsync(
|
|
target.WorkingDir,
|
|
new FindingInput(slug, title, body, scope, SourceTaskId: taskId, VerifiedAgainst: head),
|
|
cancellationToken,
|
|
target.Tracked);
|
|
|
|
return new SaveTaskFindingResult(
|
|
true, outcome.Slug, outcome.Path, outcome.TotalFindings, outcome.NearCapacity);
|
|
}
|
|
}
|