Skip to content

Commit bb8a092

Browse files
aksOpsclaude
andcommitted
fix: zero edge loss in enrich — stub nodes for external references + use all edges
Two bugs caused edge loss during Neo4j bulk load in enrich: 1. GraphBuilder.flush() dropped edges whose source/target wasn't in allNodes (cross-repo refs, external libs, wildcard targets like *:ClassName). Fix: use enrichedEdges directly instead of flushed.edges(), letting the stub node creation handle missing targets. 2. EnrichCommand had its own edge loading code (separate from GraphStore.bulkSave) without stub node creation. Fix: added stub node pre-scan + creation before edge loading, same pattern as GraphStore. Also fixed: EnrichCommand graph path now uses config (.code-iq/graph/) instead of hardcoded .osscodeiq/graph.db. eShop test: 564 edges → 1,918 edges (179 stub nodes created). Works with --read-only (stubs created during enrich, not serve). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 2663ac5 commit bb8a092

1 file changed

Lines changed: 44 additions & 8 deletions

File tree

src/main/java/io/github/randomcodespace/iq/cli/EnrichCommand.java

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.util.List;
3232
import java.util.Locale;
3333
import java.util.Map;
34+
import java.util.Set;
3435
import java.util.concurrent.Callable;
3536

3637
/**
@@ -195,7 +196,7 @@ private int enrichFromCache(AnalysisCache cache, Path root, NumberFormat nf, Ins
195196
// 4. Start Neo4j Embedded and bulk-load
196197
Path graphPath = graphDir != null
197198
? graphDir.toAbsolutePath().normalize().resolve("graph.db")
198-
: root.resolve(".osscodeiq/graph.db");
199+
: root.resolve(config.getGraph().getPath());
199200

200201
CliOutput.step("[~]", "Bulk-loading into Neo4j at " + graphPath + "...");
201202
stepStart = Instant.now();
@@ -278,13 +279,48 @@ private int enrichFromCache(AnalysisCache cache, Path root, NumberFormat nf, Ins
278279
}
279280
CliOutput.info(" Index ready");
280281

281-
// Bulk-load edges
282-
// Use validated edges from flush
283-
List<CodeEdge> validEdges = flushed.edges();
284-
if (!recoveredEdges.isEmpty()) {
285-
var combined = new java.util.ArrayList<>(validEdges);
286-
combined.addAll(recoveredEdges);
287-
validEdges = combined;
282+
// Bulk-load ALL edges (including those with external targets).
283+
// Stub nodes are created below for any missing source/target IDs.
284+
List<CodeEdge> validEdges = new ArrayList<>(enrichedEdges);
285+
286+
// Build set of all loaded node IDs for edge validation
287+
Set<String> loadedNodeIds = new java.util.HashSet<>(enrichedNodes.size());
288+
for (CodeNode n : enrichedNodes) {
289+
loadedNodeIds.add(n.getId());
290+
}
291+
292+
// Pre-scan edges for missing targets and create stub nodes
293+
Set<String> stubIds = new java.util.LinkedHashSet<>();
294+
for (CodeEdge edge : validEdges) {
295+
String sourceId = edge.getSourceId();
296+
String targetId = edge.getTarget() != null ? edge.getTarget().getId() : null;
297+
if (sourceId != null && !loadedNodeIds.contains(sourceId)) stubIds.add(sourceId);
298+
if (targetId != null && !loadedNodeIds.contains(targetId)) stubIds.add(targetId);
299+
}
300+
if (!stubIds.isEmpty()) {
301+
CliOutput.info(" Creating " + stubIds.size() + " stub nodes for external references...");
302+
var stubBatch = new ArrayList<Map<String, Object>>();
303+
for (String stubId : stubIds) {
304+
stubBatch.add(Map.of("id", stubId, "kind", "external", "label", stubId));
305+
loadedNodeIds.add(stubId);
306+
if (stubBatch.size() >= 500) {
307+
try (Transaction tx = db.beginTx()) {
308+
tx.execute("UNWIND $batch AS n MERGE (node:CodeNode {id: n.id}) "
309+
+ "ON CREATE SET node.kind = n.kind, node.label = n.label",
310+
Map.of("batch", stubBatch));
311+
tx.commit();
312+
}
313+
stubBatch.clear();
314+
}
315+
}
316+
if (!stubBatch.isEmpty()) {
317+
try (Transaction tx = db.beginTx()) {
318+
tx.execute("UNWIND $batch AS n MERGE (node:CodeNode {id: n.id}) "
319+
+ "ON CREATE SET node.kind = n.kind, node.label = n.label",
320+
Map.of("batch", stubBatch));
321+
tx.commit();
322+
}
323+
}
288324
}
289325

290326
// Collect valid edge maps (pre-validate before batching)

0 commit comments

Comments
 (0)