using TaskStatus = ClaudeDo.Data.Models.TaskStatus; namespace ClaudeDo.Worker.External; /// /// Renders the `blockedReason` string shared by and /// . Lives here so the two can't drift on the wording an agent /// parses. /// internal static class TaskBlocking { /// /// The predecessors of that have not reached Done — an id /// missing from counts as unmet (the picker treats a dangling /// edge as blocking too). /// public static List<(string Id, TaskStatus? Status)> Unmet( IEnumerable dependencyIds, IReadOnlyDictionary statuses) { var unmet = new List<(string Id, TaskStatus? Status)>(); foreach (var id in dependencyIds) { if (statuses.TryGetValue(id, out var status)) { if (status != TaskStatus.Done) unmet.Add((id, status)); } else { unmet.Add((id, null)); } } return unmet; } /// Names every open predecessor, not just the first — a task can wait on n of them. public static string Describe(IReadOnlyList<(string Id, TaskStatus? Status)> unmet) { var noun = unmet.Count == 1 ? "task" : "tasks"; var parts = unmet.Select(u => $"{u.Id} (status: {(u.Status?.ToString() ?? "not found")})"); return $"Blocked: depends on {noun} {string.Join(", ", parts)}."; } }