|
|
|
@@ -628,7 +628,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
|
|
|
|
var listId = await SeedListAsync(workingDir: _tempDir);
|
|
|
|
|
var svc = BuildService();
|
|
|
|
|
await Assert.ThrowsAsync<InvalidOperationException>(
|
|
|
|
|
() => svc.BuildForMergeHelperAsync(Array.Empty<string>(), listId, CancellationToken.None));
|
|
|
|
|
() => svc.BuildForMergeHelperAsync(Array.Empty<string>(), listId, Guid.NewGuid().ToString(), CancellationToken.None));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
@@ -640,7 +640,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
|
|
|
|
|
|
|
|
|
var svc = BuildService();
|
|
|
|
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
|
|
|
|
() => svc.BuildForMergeHelperAsync(new[] { taskId }, listId, CancellationToken.None));
|
|
|
|
|
() => svc.BuildForMergeHelperAsync(new[] { taskId }, listId, Guid.NewGuid().ToString(), CancellationToken.None));
|
|
|
|
|
Assert.Contains("working directory", ex.Message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -653,7 +653,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
|
|
|
|
|
|
|
|
|
var svc = BuildService();
|
|
|
|
|
await Assert.ThrowsAsync<KeyNotFoundException>(
|
|
|
|
|
() => svc.BuildForMergeHelperAsync(new[] { taskId }, "no-such-list", CancellationToken.None));
|
|
|
|
|
() => svc.BuildForMergeHelperAsync(new[] { taskId }, "no-such-list", Guid.NewGuid().ToString(), CancellationToken.None));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
@@ -669,7 +669,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
|
|
|
|
await SeedTaskAsync(t2, listId, TaskStatus.Idle, title: "Second task");
|
|
|
|
|
|
|
|
|
|
var svc = BuildService();
|
|
|
|
|
var spec = await svc.BuildForMergeHelperAsync(new[] { t1, t2 }, listId, CancellationToken.None);
|
|
|
|
|
var spec = await svc.BuildForMergeHelperAsync(new[] { t1, t2 }, listId, Guid.NewGuid().ToString(), CancellationToken.None);
|
|
|
|
|
var sessionDir = TrackSessionDir(spec);
|
|
|
|
|
|
|
|
|
|
Assert.Equal(repo, spec.Cwd);
|
|
|
|
@@ -726,7 +726,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
|
|
|
|
await SeedTaskAsync(t1, listId, TaskStatus.WaitingForReview, title: "First task");
|
|
|
|
|
|
|
|
|
|
var svc = BuildService();
|
|
|
|
|
var spec = await svc.BuildForMergeHelperAsync(new[] { t1 }, listId, CancellationToken.None);
|
|
|
|
|
var spec = await svc.BuildForMergeHelperAsync(new[] { t1 }, listId, Guid.NewGuid().ToString(), CancellationToken.None);
|
|
|
|
|
var sessionDir = TrackSessionDir(spec);
|
|
|
|
|
|
|
|
|
|
var args = spec.Args.ToList();
|
|
|
|
@@ -749,7 +749,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
|
|
|
|
await SeedTaskAsync(t2, listId, TaskStatus.Idle, title: "Second task");
|
|
|
|
|
|
|
|
|
|
var svc = BuildService();
|
|
|
|
|
var spec = await svc.BuildForMergeHelperAsync(new[] { t1, t2 }, listId, CancellationToken.None);
|
|
|
|
|
var spec = await svc.BuildForMergeHelperAsync(new[] { t1, t2 }, listId, Guid.NewGuid().ToString(), CancellationToken.None);
|
|
|
|
|
var sessionDir = TrackSessionDir(spec);
|
|
|
|
|
|
|
|
|
|
var brief = File.ReadAllText(Path.Combine(sessionDir, "brief.md"));
|
|
|
|
@@ -761,6 +761,28 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
|
|
|
|
Assert.Contains(t2, brief);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Regression guard: no session ever learned its own handler task id, so it could never call
|
|
|
|
|
// handoff_list_handler(taskId, ...) on itself -- the handoff chain broke exactly where it was
|
|
|
|
|
// needed. The brief must carry it.
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task BuildForMergeHelperAsync_BriefIncludesHandlerTaskId()
|
|
|
|
|
{
|
|
|
|
|
var repo = Path.Combine(_tempDir, "repoHandlerId");
|
|
|
|
|
Directory.CreateDirectory(repo);
|
|
|
|
|
|
|
|
|
|
var listId = await SeedListAsync(workingDir: repo, name: "Alpha");
|
|
|
|
|
var t1 = Guid.NewGuid().ToString();
|
|
|
|
|
await SeedTaskAsync(t1, listId, TaskStatus.Idle, title: "First task");
|
|
|
|
|
var handlerTaskId = Guid.NewGuid().ToString();
|
|
|
|
|
|
|
|
|
|
var svc = BuildService();
|
|
|
|
|
var spec = await svc.BuildForMergeHelperAsync(new[] { t1 }, listId, handlerTaskId, CancellationToken.None);
|
|
|
|
|
var sessionDir = TrackSessionDir(spec);
|
|
|
|
|
|
|
|
|
|
var brief = File.ReadAllText(Path.Combine(sessionDir, "brief.md"));
|
|
|
|
|
Assert.Contains(handlerTaskId, brief);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task BuildForMergeHelperAsync_BriefIncludesTaskDescription()
|
|
|
|
|
{
|
|
|
|
@@ -773,7 +795,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
|
|
|
|
description: "Do the thing carefully and report back.");
|
|
|
|
|
|
|
|
|
|
var svc = BuildService();
|
|
|
|
|
var spec = await svc.BuildForMergeHelperAsync(new[] { t1 }, listId, CancellationToken.None);
|
|
|
|
|
var spec = await svc.BuildForMergeHelperAsync(new[] { t1 }, listId, Guid.NewGuid().ToString(), CancellationToken.None);
|
|
|
|
|
var sessionDir = TrackSessionDir(spec);
|
|
|
|
|
|
|
|
|
|
var brief = File.ReadAllText(Path.Combine(sessionDir, "brief.md"));
|
|
|
|
@@ -791,7 +813,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
|
|
|
|
await SeedTaskAsync(t1, listId, TaskStatus.Idle, title: "No description task", description: null);
|
|
|
|
|
|
|
|
|
|
var svc = BuildService();
|
|
|
|
|
var spec = await svc.BuildForMergeHelperAsync(new[] { t1 }, listId, CancellationToken.None);
|
|
|
|
|
var spec = await svc.BuildForMergeHelperAsync(new[] { t1 }, listId, Guid.NewGuid().ToString(), CancellationToken.None);
|
|
|
|
|
var sessionDir = TrackSessionDir(spec);
|
|
|
|
|
|
|
|
|
|
var brief = File.ReadAllText(Path.Combine(sessionDir, "brief.md"));
|
|
|
|
@@ -822,7 +844,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
|
|
|
|
await SeedTaskAsync(t2, listId, TaskStatus.WaitingForReview, title: "Second task", description: "plain description");
|
|
|
|
|
|
|
|
|
|
var svc = BuildService();
|
|
|
|
|
var spec = await svc.BuildForMergeHelperAsync(new[] { t1, t2 }, listId, CancellationToken.None);
|
|
|
|
|
var spec = await svc.BuildForMergeHelperAsync(new[] { t1, t2 }, listId, Guid.NewGuid().ToString(), CancellationToken.None);
|
|
|
|
|
var sessionDir = TrackSessionDir(spec);
|
|
|
|
|
|
|
|
|
|
var brief = File.ReadAllText(Path.Combine(sessionDir, "brief.md"));
|
|
|
|
@@ -967,6 +989,26 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
|
|
|
|
Assert.DoesNotContain(args, a => a.EndsWith('\\') || a.EndsWith('/'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Regression guard: the handoff session must see the SAME handler task id as the run that
|
|
|
|
|
// called handoff_list_handler -- it's the id already passed in as taskId, just never rendered
|
|
|
|
|
// into the handoff kickoff.
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task BuildForMergeHelperHandoffAsync_HandoffIncludesSameHandlerTaskId()
|
|
|
|
|
{
|
|
|
|
|
var listId = await SeedListAsync(workingDir: _tempDir);
|
|
|
|
|
var handlerTaskId = Guid.NewGuid().ToString();
|
|
|
|
|
await SeedTaskAsync(handlerTaskId, listId, TaskStatus.Idle, title: "Handler");
|
|
|
|
|
var survivor = Guid.NewGuid().ToString();
|
|
|
|
|
await SeedTaskAsync(survivor, listId, TaskStatus.WaitingForReview);
|
|
|
|
|
|
|
|
|
|
var svc = BuildService();
|
|
|
|
|
var spec = await svc.BuildForMergeHelperHandoffAsync(handlerTaskId, new[] { survivor }, "wait", CancellationToken.None);
|
|
|
|
|
var sessionDir = TrackSessionDir(spec);
|
|
|
|
|
|
|
|
|
|
var handoff = File.ReadAllText(Path.Combine(sessionDir, "handoff.md"));
|
|
|
|
|
Assert.Contains(handlerTaskId, handoff);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task BuildForMergeHelperHandoffAsync_UnknownPhase_Throws()
|
|
|
|
|
{
|
|
|
|
@@ -1126,7 +1168,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
|
|
|
|
|
|
|
|
|
var svc = BuildService();
|
|
|
|
|
|
|
|
|
|
var triageSpec = await svc.BuildForMergeHelperAsync(new[] { survivor }, listId, CancellationToken.None);
|
|
|
|
|
var triageSpec = await svc.BuildForMergeHelperAsync(new[] { survivor }, listId, handlerTaskId, CancellationToken.None);
|
|
|
|
|
var triageDir = TrackSessionDir(triageSpec);
|
|
|
|
|
var executeSpec = await svc.BuildForMergeHelperHandoffAsync(handlerTaskId, new[] { survivor }, "merge", CancellationToken.None);
|
|
|
|
|
var executeDir = TrackSessionDir(executeSpec);
|
|
|
|
@@ -1177,7 +1219,7 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
|
|
|
|
ClaudeSessionId: "sess-42", Token: "tok-2", WorktreePath: _worktreeDir);
|
|
|
|
|
|
|
|
|
|
var svc = BuildService();
|
|
|
|
|
var mergeHelperSpec = await svc.BuildForMergeHelperAsync(new[] { survivor }, listId, CancellationToken.None);
|
|
|
|
|
var mergeHelperSpec = await svc.BuildForMergeHelperAsync(new[] { survivor }, listId, handlerTaskId, CancellationToken.None);
|
|
|
|
|
TrackSessionDir(mergeHelperSpec);
|
|
|
|
|
var handoffSpec = await svc.BuildForMergeHelperHandoffAsync(handlerTaskId, new[] { survivor }, "wait", CancellationToken.None);
|
|
|
|
|
TrackSessionDir(handoffSpec);
|
|
|
|
|