Forge
csharp3407750f
1namespace AgentForge.Plugin.Ci.Ir;
2
3public sealed record ForgeCiPlanStep(
4 string NodeId,
5 string TaskId,
6 string DisplayName,
7 IReadOnlyDictionary<string, string> Inputs);
8
9public static class ForgeCiPlanBuilder
10{
11 public static IReadOnlyList<ForgeCiPlanStep> Build(ForgeCiDocument document)
12 {
13 var tasks = document.Nodes.Where(n => string.Equals(n.Kind, "task", StringComparison.Ordinal)).ToList();
14 if (tasks.Count == 0)
15 throw new ForgeCiValidationException("Pipeline has no executable task nodes.");
16
17 var taskIds = tasks.Select(t => t.Id).ToHashSet(StringComparer.Ordinal);
18 var incoming = taskIds.ToDictionary(id => id, _ => 0, StringComparer.Ordinal);
19 var adjacency = taskIds.ToDictionary(id => id, _ => new List<string>(), StringComparer.Ordinal);
20
21 foreach (var edge in document.Edges)
22 {
23 if (!string.Equals(edge.Kind, "dependsOn", StringComparison.Ordinal))
24 continue;
25
26 if (!taskIds.Contains(edge.From) || !taskIds.Contains(edge.To))
27 continue;
28
29 adjacency[edge.From].Add(edge.To);
30 incoming[edge.To]++;
31 }
32
33 var queue = new Queue<string>(incoming.Where(kv => kv.Value == 0).Select(kv => kv.Key));
34 var orderedIds = new List<string>(tasks.Count);
35
36 while (queue.Count > 0)
37 {
38 var id = queue.Dequeue();
39 orderedIds.Add(id);
40
41 foreach (var next in adjacency[id])
42 {
43 incoming[next]--;
44 if (incoming[next] == 0)
45 queue.Enqueue(next);
46 }
47 }
48
49 if (orderedIds.Count != tasks.Count)
50 {
51 foreach (var task in tasks)
52 {
53 if (!orderedIds.Contains(task.Id))
54 orderedIds.Add(task.Id);
55 }
56 }
57
58 return orderedIds
59 .Select(id => tasks.First(t => t.Id == id))
60 .Select(node => new ForgeCiPlanStep(
61 node.Id,
62 node.Task ?? throw new ForgeCiValidationException($"Task node '{node.Id}' has no task id."),
63 string.IsNullOrWhiteSpace(node.Name) ? node.Task! : node.Name!,
64 node.Inputs ?? new Dictionary<string, string>(StringComparer.Ordinal)))
65 .ToList();
66 }
67}
68
View only · write via MCP/CIDE