Merge claudedo/e39b952576f5469a9a3d821bdc110853

This commit is contained in:
mika kuns
2026-08-11 17:19:23 +02:00
5 changed files with 135 additions and 17 deletions
@@ -608,6 +608,53 @@ public class MissionControlViewModelTests : IDisposable
Assert.NotNull(error);
}
[Fact]
public async Task OpenMergeHelperHandoffConPtySessionAsync_ExistingPaneForTaskId_ClosesOldPane_OpensNewFocusedPane()
{
var worker = new FakeWorker();
using var vm = BuildVm(worker);
await vm.OpenMergeHelperConPtySessionAsync("L1", new[] { "t1" });
var outgoingPane = Assert.Single(vm.ConPtySessions);
var taskId = outgoingPane.TaskId!;
await vm.OpenMergeHelperHandoffConPtySessionAsync(taskId, new[] { "t1" }, "wait");
var survivingPane = Assert.Single(vm.ConPtySessions);
Assert.Equal(taskId, survivingPane.TaskId);
Assert.NotSame(outgoingPane, survivingPane);
Assert.Same(survivingPane, vm.FocusedPane);
Assert.Single(vm.Panes);
Assert.Same(survivingPane, vm.Panes[0]);
// The outgoing pane went through CloseConPtySession (not just spliced out of the
// collection) -- it unsubscribed its event handlers, same signal the
// CloseConPtySession_UnsubscribesSubmitForReviewRequested test relies on.
Assert.Equal(0, SubscriberCount(outgoingPane, "SubmitForReviewRequested"));
}
[Fact]
public async Task OpenMergeHelperHandoffConPtySessionAsync_SubmitForReview_ResolvesToSurvivingPane()
{
var worker = new BlockingSubmitWorker();
using var vm = BuildVm(worker);
await vm.OpenMergeHelperConPtySessionAsync("L1", new[] { "t1" });
var outgoingPane = Assert.Single(vm.ConPtySessions);
var taskId = outgoingPane.TaskId!;
await vm.OpenMergeHelperHandoffConPtySessionAsync(taskId, new[] { "t1" }, "wait");
var survivingPane = Assert.Single(vm.ConPtySessions);
survivingPane.Terminal.IsRunning = true; // simulate a live hand-driven session
survivingPane.SubmitForReviewCommand.Execute(null);
Assert.Equal(1, worker.CallCount);
Assert.True(survivingPane.IsSubmitPending);
worker.Gate.SetResult(null);
await Task.Delay(20);
Assert.Empty(vm.ConPtySessions);
}
private sealed class BlockingSubmitWorker : StubWorkerClient
{
public int CallCount { get; private set; }
@@ -1,12 +1,31 @@
using System.Text.Json;
using ClaudeDo.Data;
using ClaudeDo.Worker.Lifecycle;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace ClaudeDo.Worker.Tests.Lifecycle;
public sealed class PromptFileRecoveryTests
{
private sealed class CapturingLogger<T> : ILogger<T>
{
public List<string> Messages { get; } = new();
public IDisposable BeginScope<TState>(TState state) where TState : notnull => NullScope.Instance;
public bool IsEnabled(LogLevel logLevel) => true;
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception,
Func<TState, Exception?, string> formatter)
=> Messages.Add(formatter(state, exception));
private sealed class NullScope : IDisposable
{
public static readonly NullScope Instance = new();
public void Dispose() { }
}
}
[Fact]
public async Task StartAsync_ReconcilesStaleDefaultAndQuarantinesOrphan_WithoutTouchingRealEdit()
{
@@ -46,4 +65,33 @@ public sealed class PromptFileRecoveryTests
if (Directory.Exists(root)) Directory.Delete(root, recursive: true);
}
}
[Fact]
public async Task StartAsync_QuarantinesOrphanedMergeHelperExecute_WarnNamesFileAndReplacements()
{
var root = Path.Combine(Path.GetTempPath(), $"claudedo_prompts_{Guid.NewGuid():N}");
try
{
Directory.CreateDirectory(root);
var orphanPath = Path.Combine(root, "merge-helper-execute.md");
File.WriteAllText(orphanPath, "my old customized triage+wait+merge prompt");
var logger = new CapturingLogger<PromptFileRecovery>();
var sut = new PromptFileRecovery(logger, root);
await sut.StartAsync(CancellationToken.None);
var dest = Path.Combine(root, "_orphans", "merge-helper-execute.md");
Assert.False(File.Exists(orphanPath));
Assert.True(File.Exists(dest));
Assert.Contains(logger.Messages, m => m.Contains("merge-helper-execute.md") && m.Contains(dest));
Assert.Contains(logger.Messages, m =>
m.Contains("merge-helper-wait.md") && m.Contains("merge-helper-merge.md"));
}
finally
{
if (Directory.Exists(root)) Directory.Delete(root, recursive: true);
}
}
}