Forge
csharp3407750f
1namespace AgentForge.Plugin.Ci.Ir;
2
3public static class ForgeCiDocumentValidator
4{
5 private static readonly HashSet<string> NodeKinds =
6 new(StringComparer.Ordinal) { "task", "phase", "comment", "group" };
7
8 private static readonly HashSet<string> EdgeKinds =
9 new(StringComparer.Ordinal) { "dependsOn", "contains", "annotates" };
10
11 public static void Validate(ForgeCiDocument document)
12 {
13 ArgumentNullException.ThrowIfNull(document);
14
15 if (!string.Equals(document.Schema, ForgeCiDocument.SchemaVersion, StringComparison.Ordinal))
16 throw new ForgeCiValidationException($"schema must be '{ForgeCiDocument.SchemaVersion}'.");
17
18 if (string.IsNullOrWhiteSpace(document.Name))
19 throw new ForgeCiValidationException("name is required.");
20
21 var nodeIds = new HashSet<string>(StringComparer.Ordinal);
22 foreach (var node in document.Nodes)
23 {
24 if (string.IsNullOrWhiteSpace(node.Id))
25 throw new ForgeCiValidationException("node id is required.");
26
27 if (!nodeIds.Add(node.Id))
28 throw new ForgeCiValidationException($"duplicate node id '{node.Id}'.");
29
30 if (!NodeKinds.Contains(node.Kind))
31 throw new ForgeCiValidationException($"unknown node kind '{node.Kind}' on '{node.Id}'.");
32
33 if (node.Kind == "task" && string.IsNullOrWhiteSpace(node.Task))
34 throw new ForgeCiValidationException($"task node '{node.Id}' requires task id.");
35
36 if (node.Kind == "task" && !ForgeCiTaskCatalog.IsKnown(node.Task!))
37 throw new ForgeCiValidationException($"unknown task '{node.Task}' on node '{node.Id}'.");
38 }
39
40 foreach (var edge in document.Edges)
41 {
42 if (!EdgeKinds.Contains(edge.Kind))
43 throw new ForgeCiValidationException($"unknown edge kind '{edge.Kind}'.");
44
45 if (string.IsNullOrWhiteSpace(edge.From) || string.IsNullOrWhiteSpace(edge.To))
46 throw new ForgeCiValidationException("edge from/to are required.");
47
48 if (!nodeIds.Contains(edge.From))
49 throw new ForgeCiValidationException($"edge.from '{edge.From}' does not reference a node.");
50
51 if (!nodeIds.Contains(edge.To))
52 throw new ForgeCiValidationException($"edge.to '{edge.To}' does not reference a node.");
53 }
54 }
55
56 public static ForgeCiDocument Canonicalize(ForgeCiDocument document)
57 {
58 Validate(document);
59 document.Nodes = document.Nodes.OrderBy(n => n.Id, StringComparer.Ordinal).ToList();
60 document.Edges = document.Edges
61 .OrderBy(e => e.Kind, StringComparer.Ordinal)
62 .ThenBy(e => e.From, StringComparer.Ordinal)
63 .ThenBy(e => e.To, StringComparer.Ordinal)
64 .ToList();
65 return document;
66 }
67}
68
View only · write via MCP/CIDE