11package io .github .randomcodespace .iq .cli ;
22
3+ import io .github .randomcodespace .iq .analyzer .AnalysisResult ;
4+ import io .github .randomcodespace .iq .analyzer .Analyzer ;
35import io .github .randomcodespace .iq .cache .AnalysisCache ;
46import io .github .randomcodespace .iq .config .CodeIqConfig ;
7+ import io .github .randomcodespace .iq .graph .GraphStore ;
58import org .slf4j .Logger ;
69import org .slf4j .LoggerFactory ;
710import org .springframework .beans .factory .annotation .Autowired ;
11+ import org .springframework .cache .CacheManager ;
812import org .springframework .stereotype .Component ;
913import picocli .CommandLine .Command ;
1014import picocli .CommandLine .Option ;
1115import picocli .CommandLine .Parameters ;
1216
1317import java .nio .file .Path ;
18+ import java .text .NumberFormat ;
19+ import java .util .Locale ;
20+ import java .util .Optional ;
1421import 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