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:
@@ -1,3 +1,5 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace ClaudeDo.Data.Models;
|
||||
|
||||
public enum TaskStatus
|
||||
@@ -33,12 +35,17 @@ public sealed class TaskEntity
|
||||
public PlanningPhase PlanningPhase { get; set; } = PlanningPhase.None;
|
||||
public string? BlockedByTaskId { get; set; }
|
||||
|
||||
// A user/MCP-declared predecessor, distinct from BlockedByTaskId (the planning chain's own
|
||||
// internal link): the picker also skips a Queued task while this is set and the referenced
|
||||
// task's Status isn't Done. Unlike the chain, a Failed/Cancelled dependency does NOT cascade
|
||||
// or auto-resolve -- the dependent just stays blocked and reports why (see TaskStateService.
|
||||
// SetDependsOnAsync and QueuePicker).
|
||||
public string? DependsOnTaskId { get; set; }
|
||||
// User/MCP-declared predecessors, distinct from BlockedByTaskId (the planning chain's own
|
||||
// internal link): the picker skips a Queued task until EVERY predecessor reached Done.
|
||||
// Unlike the chain, a Failed/Cancelled dependency does NOT cascade or auto-resolve -- the
|
||||
// dependent just stays blocked and reports why (see TaskStateService.SetDependsOnAsync and
|
||||
// QueuePicker). Only loaded where a read path Includes it; the picker gates in raw SQL and
|
||||
// never relies on this collection.
|
||||
public ICollection<TaskDependencyEntity> Dependencies { get; set; } = new List<TaskDependencyEntity>();
|
||||
|
||||
[NotMapped]
|
||||
public IReadOnlyList<string> DependsOnTaskIds =>
|
||||
Dependencies.Select(d => d.DependsOnTaskId).ToList();
|
||||
public DateTime? ScheduledFor { get; set; }
|
||||
public string? Result { get; set; }
|
||||
public string? ReviewFeedback { get; set; }
|
||||
|
||||
Reference in New Issue
Block a user