feat(claude-do): depends_on als Menge statt Einzelfeld
Ein Task kann jetzt auf beliebig viele Vorgaenger warten. Die Einzelspalte tasks.depends_on_task_id wird durch die Beziehungstabelle task_dependencies ersetzt (Composite-PK, beide FKs ON DELETE CASCADE -- das reproduziert das alte ON DELETE SET NULL: geloeschter Vorgaenger = Kante weg = Task wieder pickbar). BlockedByTaskId bleibt unberuehrt. - Migration AddTaskDependencies kopiert jede bestehende depends_on_task_id verlustfrei in die Tabelle, bevor die Spalte fallengelassen wird - QueuePicker gatet per doppelt negiertem NOT EXISTS: kein Task mit 0 Kanten wird blockiert, alle Kanten muessen done sein, eine Kante auf einen verschwundenen Task blockiert weiter - SetDependsOnAsync ersetzt die ganze Menge; Zykluspruefung ist jetzt eine BFS ueber den Graphen, faengt also auch A->B->C->A - blockedReason nennt alle offenen Vorgaenger (External/TaskBlocking, von ExternalMcpService und TaskWaitMcpTools gemeinsam genutzt) - add_task/update_task nehmen dependsOnTaskIds; die alte Skalarform bleibt als Ein-Element-Menge inkl. ""-Clear-Sentinel gueltig - UI: Fan-in-Zeile bleibt flach und listet alle Vorgaenger im After-Label, die lineare Kette rendert unveraendert
This commit is contained in:
+16
-13
@@ -132,17 +132,24 @@ public sealed class TaskWaitMcpTools
|
||||
var rows = await ctx.Tasks
|
||||
.AsNoTracking()
|
||||
.Where(t => taskIds.Contains(t.Id))
|
||||
.Select(t => new { t.Id, t.Number, t.Status, t.BlockedByTaskId, t.DependsOnTaskId })
|
||||
.Select(t => new
|
||||
{
|
||||
t.Id,
|
||||
t.Number,
|
||||
t.Status,
|
||||
t.BlockedByTaskId,
|
||||
DependsOnTaskIds = t.Dependencies.Select(d => d.DependsOnTaskId).ToList(),
|
||||
})
|
||||
.ToListAsync(ct);
|
||||
|
||||
var byId = rows.ToDictionary(r => r.Id, r => r);
|
||||
|
||||
// A Queued task with an unmet depends_on link never becomes "changed" by itself -- the
|
||||
// A Queued task with an unmet depends_on edge never becomes "changed" by itself -- the
|
||||
// picker will not touch it. Batch-resolve those dependencies' statuses once instead of a
|
||||
// query per candidate.
|
||||
var dependencyIds = rows
|
||||
.Where(r => r.Status == TaskStatus.Queued && r.BlockedByTaskId is null && r.DependsOnTaskId is not null)
|
||||
.Select(r => r.DependsOnTaskId!)
|
||||
.Where(r => r.Status == TaskStatus.Queued && r.BlockedByTaskId is null)
|
||||
.SelectMany(r => r.DependsOnTaskIds)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
var dependencyStatuses = dependencyIds.Count == 0
|
||||
@@ -168,16 +175,12 @@ public sealed class TaskWaitMcpTools
|
||||
// every chained child and turn the wait into a one-turn-per-poll busy loop.
|
||||
if (row.Status == TaskStatus.Queued && row.BlockedByTaskId is null)
|
||||
{
|
||||
if (row.DependsOnTaskId is not null)
|
||||
var unmet = TaskBlocking.Unmet(row.DependsOnTaskIds, dependencyStatuses);
|
||||
if (unmet.Count > 0)
|
||||
{
|
||||
var known = dependencyStatuses.TryGetValue(row.DependsOnTaskId, out var depStatus);
|
||||
if (!known || depStatus != TaskStatus.Done)
|
||||
{
|
||||
result.Add(new TaskStatusChangeDto(id, "Blocked",
|
||||
$"Blocked: depends on task {row.DependsOnTaskId} (status: " +
|
||||
(known ? depStatus.ToString() : "not found") + ").", row.Number));
|
||||
continue;
|
||||
}
|
||||
result.Add(new TaskStatusChangeDto(
|
||||
id, "Blocked", TaskBlocking.Describe(unmet), row.Number));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user