diff --git a/src/ClaudeDo.Data/Repositories/TaskRepository.cs b/src/ClaudeDo.Data/Repositories/TaskRepository.cs index 3fd2ad2..8a52336 100644 --- a/src/ClaudeDo.Data/Repositories/TaskRepository.cs +++ b/src/ClaudeDo.Data/Repositories/TaskRepository.cs @@ -293,6 +293,16 @@ public sealed class TaskRepository .SetProperty(t => t.PlanningSessionId, sessionId), ct); } + public async Task FindByPlanningTokenAsync( + string token, + CancellationToken ct = default) + { + if (string.IsNullOrEmpty(token)) return null; + return await _context.Tasks + .AsNoTracking() + .FirstOrDefaultAsync(t => t.PlanningSessionToken == token, ct); + } + #endregion #region Queue selection diff --git a/tests/ClaudeDo.Worker.Tests/Repositories/TaskRepositoryPlanningTests.cs b/tests/ClaudeDo.Worker.Tests/Repositories/TaskRepositoryPlanningTests.cs index 3ba9c2a..7ead35c 100644 --- a/tests/ClaudeDo.Worker.Tests/Repositories/TaskRepositoryPlanningTests.cs +++ b/tests/ClaudeDo.Worker.Tests/Repositories/TaskRepositoryPlanningTests.cs @@ -166,4 +166,25 @@ public sealed class TaskRepositoryPlanningTests : IDisposable var loaded = await _tasks.GetByIdAsync(task.Id); Assert.Equal("claude-session-42", loaded!.PlanningSessionId); } + + [Fact] + public async Task FindByPlanningTokenAsync_ReturnsTask_WhenTokenMatches() + { + var listId = await CreateListAsync(); + var task = MakeTask(listId, TaskStatus.Manual); + await _tasks.AddAsync(task); + await _tasks.SetPlanningStartedAsync(task.Id, "unique-token-123"); + + var found = await _tasks.FindByPlanningTokenAsync("unique-token-123"); + + Assert.NotNull(found); + Assert.Equal(task.Id, found!.Id); + } + + [Fact] + public async Task FindByPlanningTokenAsync_ReturnsNull_WhenTokenUnknown() + { + var found = await _tasks.FindByPlanningTokenAsync("no-such-token"); + Assert.Null(found); + } }