refactor: address code smells (run-dir helper, App DI injection)
- TaskRunner: extract worktree-vs-sandbox selection into PrepareRunDirectoryAsync so RunAsync reads linearly (a small helper, not a Strategy pattern — overkill for a two-way branch). - App: drop the public static ServiceProvider locator; inject the provider via constructor through AppBuilder.Configure(() => new App(services)). Parameterless ctor + BuildAvaloniaApp() retained for the XAML designer. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -208,8 +208,8 @@ Diese Punkte standen 2026-04-30 noch als offen/partial und sind verifiziert fert
|
|||||||
|
|
||||||
| Stelle | Issue | Status |
|
| Stelle | Issue | Status |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `TaskRunner.cs:70` `if (list.WorkingDir is not null)` | Inline-Verzweigung Worktree vs. Non-Worktree; Strategy-Pattern erst wenn die Methode wächst | ⬜ |
|
| `TaskRunner` Worktree-vs-Sandbox-Branch | In `PrepareRunDirectoryAsync` ausgelagert (kleiner Helper, kein Strategy-Pattern — bei 2 Pfaden Over-Engineering). `RunAsync` liest jetzt linear. | ✅ |
|
||||||
| `App.axaml.cs:13` `public static ServiceProvider Services` | Service-Locator-Antipattern, toleriert weil nur in `OnFrameworkInitializationCompleted` genutzt | ⬜ |
|
| `App.Services` statischer ServiceProvider | Entfernt: `App` bekommt den Provider per Constructor-Injection über `AppBuilder.Configure(() => new App(services))`; parameterloser Ctor + `BuildAvaloniaApp()` bleiben für den Designer. **UI-Start manuell smoke-testen** (kein headless-Lauf möglich). | ✅ |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
@@ -10,7 +11,12 @@ namespace ClaudeDo.App;
|
|||||||
|
|
||||||
public partial class App : Application
|
public partial class App : Application
|
||||||
{
|
{
|
||||||
public static ServiceProvider Services { get; set; } = null!;
|
private readonly IServiceProvider? _services;
|
||||||
|
|
||||||
|
// Parameterless ctor is required by the XAML previewer / designer.
|
||||||
|
public App() { }
|
||||||
|
|
||||||
|
public App(IServiceProvider services) => _services = services;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -21,16 +27,19 @@ public partial class App : Application
|
|||||||
{
|
{
|
||||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
|
var services = _services
|
||||||
|
?? throw new InvalidOperationException("App was constructed without a service provider.");
|
||||||
|
|
||||||
FocusClearing.Install();
|
FocusClearing.Install();
|
||||||
|
|
||||||
desktop.MainWindow = new MainWindow
|
desktop.MainWindow = new MainWindow
|
||||||
{
|
{
|
||||||
DataContext = Services.GetRequiredService<IslandsShellViewModel>(),
|
DataContext = services.GetRequiredService<IslandsShellViewModel>(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Kick off the SignalR retry loop — reconnects indefinitely if the worker
|
// Kick off the SignalR retry loop — reconnects indefinitely if the worker
|
||||||
// is not up yet, or goes down and comes back.
|
// is not up yet, or goes down and comes back.
|
||||||
_ = Services.GetRequiredService<WorkerClient>().StartAsync();
|
_ = services.GetRequiredService<WorkerClient>().StartAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
base.OnFrameworkInitializationCompleted();
|
base.OnFrameworkInitializationCompleted();
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ sealed class Program
|
|||||||
SetCurrentProcessExplicitAppUserModelID("ClaudeDo.App");
|
SetCurrentProcessExplicitAppUserModelID("ClaudeDo.App");
|
||||||
|
|
||||||
var services = BuildServices();
|
var services = BuildServices();
|
||||||
App.Services = services;
|
|
||||||
|
|
||||||
using (var scope = services.CreateScope())
|
using (var scope = services.CreateScope())
|
||||||
{
|
{
|
||||||
@@ -45,7 +44,7 @@ sealed class Program
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
BuildAvaloniaApp()
|
ConfigureAppBuilder(AppBuilder.Configure(() => new App(services)))
|
||||||
.StartWithClassicDesktopLifetime(args);
|
.StartWithClassicDesktopLifetime(args);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -58,8 +57,12 @@ sealed class Program
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parameterless entry point required by the XAML previewer / designer.
|
||||||
public static AppBuilder BuildAvaloniaApp()
|
public static AppBuilder BuildAvaloniaApp()
|
||||||
=> AppBuilder.Configure<App>()
|
=> ConfigureAppBuilder(AppBuilder.Configure<App>());
|
||||||
|
|
||||||
|
private static AppBuilder ConfigureAppBuilder(AppBuilder builder)
|
||||||
|
=> builder
|
||||||
.UsePlatformDetect()
|
.UsePlatformDetect()
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
.WithDeveloperTools()
|
.WithDeveloperTools()
|
||||||
|
|||||||
@@ -64,29 +64,14 @@ public sealed class TaskRunner
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Determine working directory: worktree or sandbox.
|
// Determine working directory: worktree or sandbox.
|
||||||
WorktreeContext? wtCtx = null;
|
var prep = await PrepareRunDirectoryAsync(task, list, ct);
|
||||||
string runDir;
|
if (prep.FailureReason is not null)
|
||||||
|
|
||||||
if (list.WorkingDir is not null)
|
|
||||||
{
|
{
|
||||||
try
|
await MarkFailed(task.Id, task.Title, slot, prep.FailureReason);
|
||||||
{
|
return;
|
||||||
wtCtx = await _wtManager.CreateAsync(task, list, ct);
|
|
||||||
await _broadcaster.WorkerLog($"Created worktree for \"{task.Title}\"", WorkerLogLevel.Info, DateTime.UtcNow);
|
|
||||||
runDir = wtCtx.WorktreePath;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Failed to create worktree for task {TaskId}", task.Id);
|
|
||||||
await MarkFailed(task.Id, task.Title, slot, $"Worktree creation failed: {ex.Message}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
runDir = Path.Combine(_cfg.SandboxRoot, task.Id);
|
|
||||||
Directory.CreateDirectory(runDir);
|
|
||||||
}
|
}
|
||||||
|
var wtCtx = prep.WtCtx;
|
||||||
|
var runDir = prep.RunDir!;
|
||||||
|
|
||||||
var resolvedConfig = await ResolveConfigAsync(task, listConfig, null, ct);
|
var resolvedConfig = await ResolveConfigAsync(task, listConfig, null, ct);
|
||||||
|
|
||||||
@@ -216,6 +201,30 @@ public sealed class TaskRunner
|
|||||||
await _broadcaster.TaskUpdated(taskId);
|
await _broadcaster.TaskUpdated(taskId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly record struct RunDirResult(string? RunDir, WorktreeContext? WtCtx, string? FailureReason);
|
||||||
|
|
||||||
|
private async Task<RunDirResult> PrepareRunDirectoryAsync(TaskEntity task, ListEntity list, CancellationToken ct)
|
||||||
|
{
|
||||||
|
if (list.WorkingDir is not null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var wtCtx = await _wtManager.CreateAsync(task, list, ct);
|
||||||
|
await _broadcaster.WorkerLog($"Created worktree for \"{task.Title}\"", WorkerLogLevel.Info, DateTime.UtcNow);
|
||||||
|
return new RunDirResult(wtCtx.WorktreePath, wtCtx, null);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to create worktree for task {TaskId}", task.Id);
|
||||||
|
return new RunDirResult(null, null, $"Worktree creation failed: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sandboxDir = Path.Combine(_cfg.SandboxRoot, task.Id);
|
||||||
|
Directory.CreateDirectory(sandboxDir);
|
||||||
|
return new RunDirResult(sandboxDir, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<RunResult> RunOnceAsync(
|
private async Task<RunResult> RunOnceAsync(
|
||||||
string taskId, string taskTitle, string slot, string runDir, ClaudeRunConfig config,
|
string taskId, string taskTitle, string slot, string runDir, ClaudeRunConfig config,
|
||||||
int runNumber, bool isRetry, string prompt, CancellationToken ct)
|
int runNumber, bool isRetry, string prompt, CancellationToken ct)
|
||||||
|
|||||||
Reference in New Issue
Block a user