Forge
csharpdeeb25a2
1#nullable enable
2
3namespace CascadeIDE.Cockpit.Graph;
4
5/// <summary>
6/// Изменяемый чертёж <see cref="GraphDocument"/> для разных источников (control flow, related files, GitMap).
7/// Общий механический API graph-backed layer (ADR 0115).
8/// </summary>
9public class GraphDocumentBlueprint
10{
11 private int _nextId = 1;
12 private int _legendSerial;
13
14 public GraphDocumentBlueprint(
15 string anchorPath,
16 int maxNodes,
17 int maxEdges,
18 string anchorLabel,
19 string anchorRationale,
20 GraphKind graphKind = GraphKind.Unspecified)
21 {
22 AnchorPath = anchorPath;
23 Kind = graphKind;
24 MaxNodes = maxNodes;
25 MaxEdges = maxEdges;
26 Nodes.Add(new GraphBuildNode(
27 AnchorNodeId,
28 anchorPath,
29 "anchor",
30 anchorLabel,
31 "",
32 anchorRationale,
33 null,
34 null,
35 null,
36 null,
37 null));
38 }
39
40 public string AnchorPath { get; }
41
42 public string AnchorNodeId { get; } = "n0";
43
44 public GraphKind Kind { get; }
45
46 public int MaxNodes { get; }
47
48 public int MaxEdges { get; }
49
50 public List<GraphBuildNode> Nodes { get; } = [];
51
52 public List<GraphBuildEdge> Edges { get; } = [];
53
54 public bool TruncatedNodes { get; private set; }
55
56 public bool TruncatedEdges { get; private set; }
57
58 public static string SanitizeLegendLine(string? text, int maxLen)
59 {
60 if (string.IsNullOrWhiteSpace(text))
61 return "";
62 var s = text.Replace('\r', ' ').Replace('\n', ' ');
63 while (s.Contains(" ", StringComparison.Ordinal))
64 s = s.Replace(" ", " ", StringComparison.Ordinal);
65 s = s.Trim();
66 if (s.Length <= maxLen)
67 return s;
68 return s[..(maxLen - 1)] + "…";
69 }
70
71 public string? TryAddNode(
72 string kind,
73 string nodePath,
74 string label,
75 string relativePath,
76 string rationale,
77 string? legendLine,
78 bool assignControlFlowLegendIndex,
79 int? lineStart = null,
80 int? lineEnd = null,
81 int? loopGroupId = null)
82 {
83 if (Nodes.Count >= MaxNodes)
84 {
85 TruncatedNodes = true;
86 return null;
87 }
88
89 var id = $"n{_nextId++}";
90 int? legendIndex = null;
91 string? legendText = null;
92 if (assignControlFlowLegendIndex)
93 {
94 var leg = string.IsNullOrWhiteSpace(legendLine)
95 ? SanitizeLegendLine(rationale, 200)
96 : SanitizeLegendLine(legendLine, 200);
97 _legendSerial++;
98 legendIndex = _legendSerial;
99 legendText = string.IsNullOrEmpty(leg) ? null : leg;
100 }
101
102 Nodes.Add(new GraphBuildNode(
103 id, nodePath, kind, label, relativePath, rationale, legendIndex, legendText, lineStart, lineEnd, loopGroupId));
104 return id;
105 }
106
107 public bool TryAddEdge(string fromId, string toId, string kind, string relationKind, string? edgeProvenance = null)
108 {
109 if (Edges.Count >= MaxEdges)
110 {
111 TruncatedEdges = true;
112 return false;
113 }
114 Edges.Add(new GraphBuildEdge(fromId, toId, kind, relationKind, edgeProvenance));
115 return true;
116 }
117
118 public void AddEdges(IReadOnlyList<string> fromIds, string toId, string kind, string relationKind, string? edgeProvenance = null)
119 {
120 if (fromIds.Count == 0)
121 return;
122
123 var edgeKind = fromIds.Count > 1 ? "Merge" : kind;
124 foreach (var fromId in fromIds)
125 {
126 if (Edges.Count >= MaxEdges)
127 {
128 TruncatedEdges = true;
129 break;
130 }
131 Edges.Add(new GraphBuildEdge(fromId, toId, edgeKind, relationKind, edgeProvenance));
132 }
133 }
134
135 public string? TryAddSubmoduleRepository(string absolutePath, string shortLabel, string? rationale = null) =>
136 TryAddNode(
137 "submodule",
138 absolutePath,
139 shortLabel,
140 "",
141 rationale ?? "submodule",
142 null,
143 assignControlFlowLegendIndex: false);
144
145 public bool TryLinkParentContainsSubmodule(string parentId, string childId, string edgeKind = "contains") =>
146 TryAddEdge(parentId, childId, edgeKind, edgeKind);
147
148 public GraphDocument ToDocument() =>
149 new()
150 {
151 AnchorPath = AnchorPath,
152 Kind = Kind,
153 Nodes = Nodes.Select(n => new GraphNode
154 {
155 Id = n.Id,
156 Path = n.Path,
157 Kind = n.Kind,
158 Label = n.Label,
159 RelativePath = string.IsNullOrEmpty(n.RelativePath) ? null : n.RelativePath,
160 Rationale = n.Rationale,
161 LegendIndex = n.LegendIndex,
162 LegendText = n.LegendText,
163 LineStart = n.LineStart,
164 LineEnd = n.LineEnd,
165 LoopGroupId = n.LoopGroupId
166 }).ToList(),
167 Edges = Edges.Select(e => new GraphEdge
168 {
169 FromId = e.FromId,
170 ToId = e.ToId,
171 Kind = e.Kind,
172 RelationKind = e.RelationKind,
173 EdgeProvenance = e.EdgeProvenance
174 }).ToList()
175 };
176}
177
178public readonly record struct GraphBuildNode(
179 string Id,
180 string Path,
181 string Kind,
182 string Label,
183 string RelativePath,
184 string Rationale,
185 int? LegendIndex,
186 string? LegendText,
187 int? LineStart,
188 int? LineEnd,
189 int? LoopGroupId);
190
191public readonly record struct GraphBuildEdge(
192 string FromId,
193 string ToId,
194 string Kind,
195 string RelationKind,
196 string? EdgeProvenance);
197
View only · write via MCP/CIDE