@@ -34,14 +34,18 @@ public ScanResult GenerateScanResultFromProcessingResult(
3434
3535 this . LogComponentScopeTelemetry ( mergedComponents ) ;
3636
37+ var dependencyGraphs = GraphTranslationUtility . AccumulateAndConvertToContract ( recorderDetectorPairs
38+ . Select ( tuple => tuple . Recorder )
39+ . Where ( x => x != null )
40+ . Select ( x => x . GetDependencyGraphsByLocation ( ) ) ) ;
41+
42+ ReconcileDependencyGraphIds ( dependencyGraphs , mergedComponents ) ;
43+
3744 return new DefaultGraphScanResult
3845 {
3946 ComponentsFound = mergedComponents . Select ( x => this . ConvertToContract ( x ) ) . ToList ( ) ,
4047 ContainerDetailsMap = detectorProcessingResult . ContainersDetailsMap ,
41- DependencyGraphs = GraphTranslationUtility . AccumulateAndConvertToContract ( recorderDetectorPairs
42- . Select ( tuple => tuple . Recorder )
43- . Where ( x => x != null )
44- . Select ( x => x . GetDependencyGraphsByLocation ( ) ) ) ,
48+ DependencyGraphs = dependencyGraphs ,
4549 SourceDirectory = settings . SourceDirectory . ToString ( ) ,
4650 } ;
4751 }
@@ -81,6 +85,169 @@ private static bool GraphContainsComponent(IDependencyGraph graph, TypedComponen
8185 ( component . Id != component . BaseId && graph . Contains ( component . BaseId ) ) ;
8286 }
8387
88+ /// <summary>
89+ /// Reconciles bare component Ids in <see cref="DependencyGraphCollection"/> to match the merged
90+ /// identities in ComponentsFound. When a bare node (Id == BaseId) has rich counterparts
91+ /// (Id != BaseId, same BaseId) in the same location graph, the bare node is merged into
92+ /// all rich counterparts and removed. This ensures every Id referenced in the graph output
93+ /// also exists in ComponentsFound.
94+ /// </summary>
95+ internal static void ReconcileDependencyGraphIds (
96+ DependencyGraphCollection graphs ,
97+ IReadOnlyList < DetectedComponent > mergedComponents )
98+ {
99+ if ( graphs == null || graphs . Count == 0 )
100+ {
101+ return ;
102+ }
103+
104+ // Build BaseId → set of rich Ids from merged components.
105+ var baseIdToRichIds = new Dictionary < string , HashSet < string > > ( ) ;
106+ foreach ( var component in mergedComponents )
107+ {
108+ var id = component . Component . Id ;
109+ var baseId = component . Component . BaseId ;
110+ if ( id != baseId )
111+ {
112+ if ( ! baseIdToRichIds . TryGetValue ( baseId , out var richIds ) )
113+ {
114+ baseIdToRichIds [ baseId ] = richIds = [ ] ;
115+ }
116+
117+ richIds . Add ( id ) ;
118+ }
119+ }
120+
121+ if ( baseIdToRichIds . Count == 0 )
122+ {
123+ return ;
124+ }
125+
126+ foreach ( var graphWithMetadata in graphs . Values )
127+ {
128+ ReconcileGraph ( graphWithMetadata , baseIdToRichIds ) ;
129+ }
130+ }
131+
132+ private static void ReconcileGraph (
133+ DependencyGraphWithMetadata graphWithMetadata ,
134+ Dictionary < string , HashSet < string > > baseIdToRichIds )
135+ {
136+ var graph = graphWithMetadata . Graph ;
137+
138+ // Identify bare nodes that have at least one rich counterpart in THIS graph.
139+ var bareToRich = new Dictionary < string , HashSet < string > > ( ) ;
140+ foreach ( var nodeId in graph . Keys )
141+ {
142+ if ( baseIdToRichIds . TryGetValue ( nodeId , out var allRichIds ) )
143+ {
144+ var richInGraph = new HashSet < string > ( allRichIds . Where ( graph . ContainsKey ) ) ;
145+ if ( richInGraph . Count > 0 )
146+ {
147+ bareToRich [ nodeId ] = richInGraph ;
148+ }
149+ }
150+ }
151+
152+ if ( bareToRich . Count == 0 )
153+ {
154+ return ;
155+ }
156+
157+ // Rewrite a single Id: if it's a bare Id being merged, expand to its rich counterparts.
158+ HashSet < string > RewriteId ( string id ) =>
159+ bareToRich . TryGetValue ( id , out var richIds ) ? richIds : [ id ] ;
160+
161+ // Rebuild graph: skip bare nodes being merged, rewrite edge targets.
162+ var newGraph = new Contracts . BcdeModels . DependencyGraph ( ) ;
163+ foreach ( var ( nodeId , edges ) in graph )
164+ {
165+ if ( bareToRich . ContainsKey ( nodeId ) )
166+ {
167+ continue ; // bare node will be merged into its rich counterparts below
168+ }
169+
170+ if ( edges == null )
171+ {
172+ newGraph [ nodeId ] = null ;
173+ }
174+ else
175+ {
176+ var newEdges = new HashSet < string > ( ) ;
177+ foreach ( var edge in edges )
178+ {
179+ foreach ( var rewritten in RewriteId ( edge ) )
180+ {
181+ // Avoid self-edges that rewriting could introduce.
182+ if ( rewritten != nodeId )
183+ {
184+ newEdges . Add ( rewritten ) ;
185+ }
186+ }
187+ }
188+
189+ newGraph [ nodeId ] = newEdges ;
190+ }
191+ }
192+
193+ // Merge bare nodes' outbound edges into their rich counterparts.
194+ foreach ( var ( bareId , richIds ) in bareToRich )
195+ {
196+ var bareEdges = graph [ bareId ] ;
197+ foreach ( var richId in richIds )
198+ {
199+ if ( bareEdges != null )
200+ {
201+ newGraph [ richId ] ??= [ ] ;
202+ foreach ( var edge in bareEdges )
203+ {
204+ foreach ( var rewritten in RewriteId ( edge ) )
205+ {
206+ if ( rewritten != richId )
207+ {
208+ newGraph [ richId ] . Add ( rewritten ) ;
209+ }
210+ }
211+ }
212+ }
213+ }
214+ }
215+
216+ // Rebuild metadata sets, rewriting bare Ids to their rich counterparts.
217+ graphWithMetadata . Graph = newGraph ;
218+ graphWithMetadata . ExplicitlyReferencedComponentIds = RewriteIdSet ( graphWithMetadata . ExplicitlyReferencedComponentIds , bareToRich ) ;
219+ graphWithMetadata . DevelopmentDependencies = RewriteIdSet ( graphWithMetadata . DevelopmentDependencies , bareToRich ) ;
220+ graphWithMetadata . Dependencies = RewriteIdSet ( graphWithMetadata . Dependencies , bareToRich ) ;
221+ }
222+
223+ private static HashSet < string > RewriteIdSet (
224+ HashSet < string > original ,
225+ Dictionary < string , HashSet < string > > bareToRich )
226+ {
227+ if ( original == null || original . Count == 0 )
228+ {
229+ return original ;
230+ }
231+
232+ var result = new HashSet < string > ( ) ;
233+ foreach ( var id in original )
234+ {
235+ if ( bareToRich . TryGetValue ( id , out var richIds ) )
236+ {
237+ foreach ( var richId in richIds )
238+ {
239+ result . Add ( richId ) ;
240+ }
241+ }
242+ else
243+ {
244+ result . Add ( id ) ;
245+ }
246+ }
247+
248+ return result ;
249+ }
250+
84251 private void LogComponentScopeTelemetry ( List < DetectedComponent > components )
85252 {
86253 using var record = new DetectedComponentScopeRecord ( ) ;
0 commit comments