feat(daily-prep): add get_daily_prep_candidates MCP tool

This commit is contained in:
mika kuns
2026-06-03 16:15:27 +02:00
parent 3c66d65160
commit 0bb809445e
2 changed files with 188 additions and 0 deletions

View File

@@ -253,4 +253,117 @@ public sealed class ExternalMcpServiceTests : IDisposable
await Assert.ThrowsAsync<InvalidOperationException>(() =>
sut.DeleteTask("does-not-exist", CancellationToken.None));
}
private async Task SeedAppSettingsAsync(string? reportExcludedPaths, int dailyPrepMaxTasks = 5)
{
var settings = new AppSettingsEntity
{
Id = AppSettingsEntity.SingletonId,
ReportExcludedPaths = reportExcludedPaths,
DailyPrepMaxTasks = dailyPrepMaxTasks,
};
// Upsert via AppSettingsRepository
await using var ctx = _db.CreateContext();
var repo = new AppSettingsRepository(ctx);
await repo.UpdateAsync(settings);
}
[Fact]
public async Task GetDailyPrepCandidates_filters_by_status_block_and_excluded_repo()
{
// List 1: included repo D:\work\repo — 4 tasks seeded
var listId1 = Guid.NewGuid().ToString();
await _lists.AddAsync(new ListEntity { Id = listId1, Name = "Work", WorkingDir = @"D:\work\repo", CreatedAt = DateTime.UtcNow });
// idle, not blocked, not MyDay → should be candidate
var idleUnblocked = new TaskEntity
{
Id = "idle-unblocked",
ListId = listId1,
Title = "Idle unblocked",
Status = TaskStatus.Idle,
CreatedAt = DateTime.UtcNow,
CommitType = "chore",
};
await _tasks.AddAsync(idleUnblocked);
// idle but blocked → excluded (BlockedByTaskId references idle-unblocked as its predecessor)
var idleBlocked = new TaskEntity
{
Id = "idle-blocked",
ListId = listId1,
Title = "Idle blocked",
Status = TaskStatus.Idle,
BlockedByTaskId = "idle-unblocked",
CreatedAt = DateTime.UtcNow,
CommitType = "chore",
};
await _tasks.AddAsync(idleBlocked);
// Done → excluded
var doneTask = new TaskEntity
{
Id = "done-task",
ListId = listId1,
Title = "Done task",
Status = TaskStatus.Done,
CreatedAt = DateTime.UtcNow,
CommitType = "chore",
};
await _tasks.AddAsync(doneTask);
// idle, IsMyDay → goes into currentMyDay, not candidates
var myDayTask = new TaskEntity
{
Id = "myday-task",
ListId = listId1,
Title = "MyDay task",
Status = TaskStatus.Idle,
IsMyDay = true,
CreatedAt = DateTime.UtcNow,
CommitType = "chore",
};
await _tasks.AddAsync(myDayTask);
// List 2: excluded repo C:\Private\secret
var listId2 = Guid.NewGuid().ToString();
await _lists.AddAsync(new ListEntity { Id = listId2, Name = "Secret", WorkingDir = @"C:\Private\secret", CreatedAt = DateTime.UtcNow });
var excludedRepoTask = new TaskEntity
{
Id = "excluded-repo-task",
ListId = listId2,
Title = "Excluded repo",
Status = TaskStatus.Idle,
CreatedAt = DateTime.UtcNow,
CommitType = "chore",
};
await _tasks.AddAsync(excludedRepoTask);
// List 3: no WorkingDir → excluded
var listId3 = Guid.NewGuid().ToString();
await _lists.AddAsync(new ListEntity { Id = listId3, Name = "NoRepo", WorkingDir = null, CreatedAt = DateTime.UtcNow });
var noRepoTask = new TaskEntity
{
Id = "no-repo-task",
ListId = listId3,
Title = "No repo",
Status = TaskStatus.Idle,
CreatedAt = DateTime.UtcNow,
CommitType = "chore",
};
await _tasks.AddAsync(noRepoTask);
await SeedAppSettingsAsync(@"[""C:\\Private""]", dailyPrepMaxTasks: 5);
var sut = BuildSut(CreateQueue());
var result = await sut.GetDailyPrepCandidates(CancellationToken.None);
Assert.Single(result.Candidates);
Assert.Equal("idle-unblocked", result.Candidates[0].Id);
Assert.Single(result.CurrentMyDay);
Assert.Equal("myday-task", result.CurrentMyDay[0].Id);
Assert.Equal(5, result.MaxTasks);
}
}