From e901829f0d772989b06d8239fddc7917376f8d8d Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Wed, 1 Apr 2026 13:27:33 +0000 Subject: [PATCH] fix: pass project root to ServiceDetector so filesystem walk runs during enrich EnrichCommand called the 3-arg overload of ServiceDetector.detect(), which set projectRoot=null and skipped the filesystem walk entirely. Multi-module projects (e.g. .NET eShop with 20+ .csproj files) only received the single root-fallback SERVICE node because their build files were never parsed into CodeNodes by any detector. Passing `root` as the 4th argument enables the filesystem scan, so all modules are discovered regardless of whether a detector created a node for their build file. Added ServiceDetectorTest#filesystemWalkFindsModulesNotPresentAsNodes to verify this path with a simulated .NET monorepo (3 .csproj files, no matching CodeNodes). Co-Authored-By: Paperclip --- .../randomcodespace/iq/cli/EnrichCommand.java | 2 +- .../iq/analyzer/ServiceDetectorTest.java | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/github/randomcodespace/iq/cli/EnrichCommand.java b/src/main/java/io/github/randomcodespace/iq/cli/EnrichCommand.java index 81fcf624..44b4a83a 100644 --- a/src/main/java/io/github/randomcodespace/iq/cli/EnrichCommand.java +++ b/src/main/java/io/github/randomcodespace/iq/cli/EnrichCommand.java @@ -145,7 +145,7 @@ private int enrichFromCache(AnalysisCache cache, Path root, NumberFormat nf, Ins CliOutput.step("\uD83C\uDFD7\uFE0F", "Detecting service boundaries..."); var serviceDetector = new io.github.randomcodespace.iq.analyzer.ServiceDetector(); String projectName = root.getFileName().toString(); - var serviceResult = serviceDetector.detect(enrichedNodes, enrichedEdges, projectName); + var serviceResult = serviceDetector.detect(enrichedNodes, enrichedEdges, projectName, root); if (!serviceResult.serviceNodes().isEmpty()) { // Add service nodes and edges to the builder builder.addNodes(serviceResult.serviceNodes()); diff --git a/src/test/java/io/github/randomcodespace/iq/analyzer/ServiceDetectorTest.java b/src/test/java/io/github/randomcodespace/iq/analyzer/ServiceDetectorTest.java index b4e25917..db939ffd 100644 --- a/src/test/java/io/github/randomcodespace/iq/analyzer/ServiceDetectorTest.java +++ b/src/test/java/io/github/randomcodespace/iq/analyzer/ServiceDetectorTest.java @@ -523,6 +523,38 @@ void deterministicWithContentExtraction(@TempDir Path tempDir) throws IOExceptio } } + @Test + void filesystemWalkFindsModulesNotPresentAsNodes(@TempDir Path tempDir) throws IOException { + // Simulate a .NET monorepo: .csproj files exist on disk but NO detector created CodeNodes for them + Files.createDirectories(tempDir.resolve("src/Basket.API")); + Files.writeString(tempDir.resolve("src/Basket.API/Basket.API.csproj"), + "", StandardCharsets.UTF_8); + + Files.createDirectories(tempDir.resolve("src/Catalog.API")); + Files.writeString(tempDir.resolve("src/Catalog.API/Catalog.API.csproj"), + "", StandardCharsets.UTF_8); + + Files.createDirectories(tempDir.resolve("src/Ordering.API")); + Files.writeString(tempDir.resolve("src/Ordering.API/Ordering.API.csproj"), + "", StandardCharsets.UTF_8); + + // No nodes have build file paths — they are only on the filesystem + List nodes = new ArrayList<>(); + nodes.add(makeNode("cls:BasketCtrl", NodeKind.CLASS, "BasketController", + "src/Basket.API/Controllers/BasketController.cs")); + nodes.add(makeNode("cls:CatalogCtrl", NodeKind.CLASS, "CatalogController", + "src/Catalog.API/Controllers/CatalogController.cs")); + + var result = detector.detect(nodes, List.of(), "eShop", tempDir); + + // Should detect 3 services via filesystem walk (not from node paths) + assertEquals(3, result.serviceNodes().size()); + var names = result.serviceNodes().stream().map(CodeNode::getLabel).sorted().toList(); + assertEquals(List.of("Basket.API", "Catalog.API", "Ordering.API"), names); + result.serviceNodes().forEach(svc -> + assertEquals("dotnet", svc.getProperties().get("build_tool"))); + } + private static CodeNode makeNode(String id, NodeKind kind, String label, String filePath) { CodeNode node = new CodeNode(id, kind, label); node.setFilePath(filePath);