Skip to content

Commit d75688c

Browse files
aksOpsclaude
andcommitted
feat: auto-enrich Neo4j on serve startup
When serve starts and Neo4j is empty, automatically runs the full analysis pipeline (analyze → persist to Neo4j → evict caches) so API/MCP endpoints return data immediately without requiring manual POST /api/analyze. Flow: 1. Check if Neo4j has data → if yes, report stats and skip 2. If empty → run full analyzer.run() with progress output 3. bulkSave() results to Neo4j with UNWIND batches 4. Evict Spring caches 5. Report final node/edge counts This fixes the disconnect where H2 cache stats were shown on startup but Neo4j was empty, causing all API queries to return 0. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ba8d04d commit d75688c

1 file changed

Lines changed: 78 additions & 20 deletions

File tree

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

Lines changed: 78 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
package io.github.randomcodespace.iq.cli;
22

3+
import io.github.randomcodespace.iq.analyzer.AnalysisResult;
4+
import io.github.randomcodespace.iq.analyzer.Analyzer;
35
import io.github.randomcodespace.iq.cache.AnalysisCache;
46
import io.github.randomcodespace.iq.config.CodeIqConfig;
7+
import io.github.randomcodespace.iq.graph.GraphStore;
58
import org.slf4j.Logger;
69
import org.slf4j.LoggerFactory;
710
import org.springframework.beans.factory.annotation.Autowired;
11+
import org.springframework.cache.CacheManager;
812
import org.springframework.stereotype.Component;
913
import picocli.CommandLine.Command;
1014
import picocli.CommandLine.Option;
1115
import picocli.CommandLine.Parameters;
1216

1317
import java.nio.file.Path;
18+
import java.text.NumberFormat;
19+
import java.util.Locale;
20+
import java.util.Optional;
1421
import java.util.concurrent.Callable;
1522

1623
/**
@@ -49,33 +56,24 @@ public class ServeCommand implements Callable<Integer> {
4956
@Autowired
5057
private CodeIqConfig config;
5158

59+
@Autowired
60+
private Analyzer analyzer;
61+
62+
@Autowired(required = false)
63+
private GraphStore graphStore;
64+
65+
@Autowired(required = false)
66+
private CacheManager cacheManager;
67+
5268
@Override
5369
public Integer call() {
5470
Path root = path.toAbsolutePath().normalize();
5571

5672
// Update config with the root path so controllers can find it
5773
config.setRootPath(root.toString());
5874

59-
// Check that H2 cache exists and report status
60-
Path cacheDir;
61-
if (graphPath != null) {
62-
cacheDir = graphPath.toAbsolutePath().normalize();
63-
} else {
64-
cacheDir = root.resolve(config.getCacheDir());
65-
}
66-
Path h2File = cacheDir.resolve("analysis-cache.mv.db");
67-
if (java.nio.file.Files.exists(h2File)) {
68-
try (AnalysisCache cache = new AnalysisCache(cacheDir.resolve("analysis-cache.db"))) {
69-
long nodeCount = cache.getNodeCount();
70-
long edgeCount = cache.getEdgeCount();
71-
CliOutput.success("Loaded analysis cache: " + nodeCount + " nodes, " + edgeCount + " edges");
72-
} catch (Exception e) {
73-
log.warn("Could not read H2 cache stats", e);
74-
}
75-
} else {
76-
CliOutput.info("No analysis cache found at " + cacheDir);
77-
CliOutput.info(" Run 'code-iq analyze " + root + "' to populate data.");
78-
}
75+
// Auto-enrich: if Neo4j is empty, run full analysis to populate it
76+
autoEnrich(root);
7977

8078
CliOutput.step("\uD83D\uDE80", "@|bold,green Server started|@");
8179
System.out.println();
@@ -101,6 +99,66 @@ public Integer call() {
10199
return 0;
102100
}
103101

102+
/**
103+
* Auto-enrich Neo4j on startup: if the graph is empty, run the full analysis
104+
* pipeline to populate Neo4j from the codebase. This ensures the API/MCP
105+
* endpoints return data immediately without requiring a manual POST /api/analyze.
106+
*/
107+
private void autoEnrich(Path root) {
108+
// Check if Neo4j already has data
109+
boolean neo4jEmpty = true;
110+
if (graphStore != null) {
111+
try {
112+
neo4jEmpty = graphStore.count() == 0;
113+
} catch (Exception e) {
114+
log.debug("Could not check Neo4j state", e);
115+
}
116+
}
117+
118+
if (!neo4jEmpty) {
119+
long nodeCount = graphStore.count();
120+
long edgeCount = graphStore.countEdges();
121+
CliOutput.success("Neo4j graph loaded: " + nodeCount + " nodes, " + edgeCount + " edges");
122+
return;
123+
}
124+
125+
// Neo4j is empty — run analysis to populate it
126+
NumberFormat nf = NumberFormat.getIntegerInstance(Locale.US);
127+
CliOutput.step("\u26A1", "Auto-enriching Neo4j graph...");
128+
129+
try {
130+
AnalysisResult result = analyzer.run(root, null, true, msg -> {
131+
if (msg.startsWith("Discovering") || msg.startsWith("Found") || msg.startsWith("Analyzing")
132+
|| msg.startsWith("Building") || msg.startsWith("Linking") || msg.startsWith("Classifying")
133+
|| msg.startsWith("Analysis complete")) {
134+
CliOutput.info(" " + msg);
135+
}
136+
});
137+
138+
// Persist to Neo4j
139+
if (graphStore != null && result.nodes() != null && !result.nodes().isEmpty()) {
140+
CliOutput.step("\uD83D\uDCBE", "Persisting " + nf.format(result.nodes().size()) + " nodes to Neo4j...");
141+
graphStore.bulkSave(result.nodes());
142+
}
143+
144+
// Evict Spring caches
145+
if (cacheManager != null) {
146+
cacheManager.getCacheNames().forEach(name -> {
147+
var cache = cacheManager.getCache(name);
148+
if (cache != null) cache.clear();
149+
});
150+
}
151+
152+
CliOutput.success("Graph ready: " + nf.format(result.nodeCount()) + " nodes, "
153+
+ nf.format(result.edgeCount()) + " edges from "
154+
+ nf.format(result.filesAnalyzed()) + " files");
155+
} catch (Exception e) {
156+
log.error("Auto-enrich failed", e);
157+
CliOutput.error("Auto-enrich failed: " + e.getMessage());
158+
CliOutput.info(" You can manually trigger analysis via POST /api/analyze");
159+
}
160+
}
161+
104162
public Path getPath() {
105163
return path;
106164
}

0 commit comments

Comments
 (0)