Skip to content

Commit 34bf771

Browse files
aksOpsclaude
andcommitted
Fix all deep review issues: Hazelcast, Swagger, detectors, MCP, dead code, H2 cache
Architecture: - Activate Hazelcast as Spring CacheManager (was overridden by spring.cache.type:simple) - Add springdoc-openapi-starter-webmvc-ui for Swagger UI (/swagger-ui/index.html) - Add CORS config for /api/** and /mcp/** (CorsConfig.java) - Cache H2 data in memory during serve (GraphController, TopologyController, McpTools) — eliminates per-request full table scans - Invalidate cache after POST /api/analyze Detectors: - Fix 25 @DetectorInfo annotations: ParserType.ANTLR → REGEX (honesty) - Fix DjangoModelDetector + SQLAlchemyModelDetector: FK/M2M edges now set target node using wildcard convention (*:TargetClass) for cross-file resolution - Fix NestJSControllerDetector: allow stacked decorators between @controller and class API: - Add pagination limit caps (max 1000) on all paginated endpoints - Fix 7 REST endpoints returning 200 with error body → proper 503 status - Wrap 16 MCP tools with try-catch → return JSON error instead of crash New Features: - Dead code detection: QueryService.findDeadCode() + REST endpoint + MCP tool Finds classes/methods/interfaces with no incoming calls/imports/extends 1,227 tests pass, 0 failures. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 624196a commit 34bf771

34 files changed

Lines changed: 531 additions & 240 deletions

pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,13 @@
126126
<artifactId>h2</artifactId>
127127
</dependency>
128128

129+
<!-- OpenAPI / Swagger UI -->
130+
<dependency>
131+
<groupId>org.springdoc</groupId>
132+
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
133+
<version>2.8.6</version>
134+
</dependency>
135+
129136
<!-- Testing -->
130137
<dependency>
131138
<groupId>org.springframework.boot</groupId>

src/main/java/io/github/randomcodespace/iq/api/GraphController.java

Lines changed: 141 additions & 87 deletions
Large diffs are not rendered by default.

src/main/java/io/github/randomcodespace/iq/api/TopologyController.java

Lines changed: 56 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -29,87 +29,108 @@ public class TopologyController {
2929

3030
private final TopologyService topologyService;
3131
private final CodeIqConfig config;
32+
private volatile List<CodeNode> cachedNodes;
33+
private volatile List<CodeEdge> cachedEdges;
3234

3335
public TopologyController(TopologyService topologyService, CodeIqConfig config) {
3436
this.topologyService = topologyService;
3537
this.config = config;
3638
}
3739

40+
// --- H2 in-memory cache: load once, reuse across requests ---
41+
42+
/**
43+
* Ensure the H2 cache is loaded into memory. Thread-safe via synchronized.
44+
*/
45+
private synchronized void ensureCacheLoaded() {
46+
if (cachedNodes != null) return;
47+
Path root = Path.of(config.getRootPath()).toAbsolutePath().normalize();
48+
Path cachePath = root.resolve(config.getCacheDir()).resolve("analysis-cache.db");
49+
Path h2File = root.resolve(config.getCacheDir()).resolve("analysis-cache.mv.db");
50+
if (!Files.exists(h2File)) return;
51+
try (AnalysisCache cache = new AnalysisCache(cachePath)) {
52+
cachedNodes = cache.loadAllNodes();
53+
cachedEdges = cache.loadAllEdges();
54+
}
55+
}
56+
57+
/**
58+
* Invalidate the in-memory cache (e.g. after re-analysis).
59+
*/
60+
public void invalidateCache() {
61+
cachedNodes = null;
62+
cachedEdges = null;
63+
}
64+
3865
@GetMapping
3966
public Map<String, Object> getTopology() {
40-
var data = loadData();
41-
return topologyService.getTopology(data.nodes(), data.edges());
67+
ensureCacheLoaded();
68+
requireCache();
69+
return topologyService.getTopology(cachedNodes, cachedEdges);
4270
}
4371

4472
@GetMapping("/services/{name}")
4573
public Map<String, Object> serviceDetail(@PathVariable String name) {
46-
var data = loadData();
47-
return topologyService.serviceDetail(name, data.nodes(), data.edges());
74+
ensureCacheLoaded();
75+
requireCache();
76+
return topologyService.serviceDetail(name, cachedNodes, cachedEdges);
4877
}
4978

5079
@GetMapping("/services/{name}/deps")
5180
public Map<String, Object> serviceDependencies(@PathVariable String name) {
52-
var data = loadData();
53-
return topologyService.serviceDependencies(name, data.nodes(), data.edges());
81+
ensureCacheLoaded();
82+
requireCache();
83+
return topologyService.serviceDependencies(name, cachedNodes, cachedEdges);
5484
}
5585

5686
@GetMapping("/services/{name}/dependents")
5787
public Map<String, Object> serviceDependents(@PathVariable String name) {
58-
var data = loadData();
59-
return topologyService.serviceDependents(name, data.nodes(), data.edges());
88+
ensureCacheLoaded();
89+
requireCache();
90+
return topologyService.serviceDependents(name, cachedNodes, cachedEdges);
6091
}
6192

6293
@GetMapping("/blast-radius/{nodeId}")
6394
public Map<String, Object> blastRadius(@PathVariable String nodeId) {
64-
var data = loadData();
65-
return topologyService.blastRadius(nodeId, data.nodes(), data.edges());
95+
ensureCacheLoaded();
96+
requireCache();
97+
return topologyService.blastRadius(nodeId, cachedNodes, cachedEdges);
6698
}
6799

68100
@GetMapping("/path")
69101
public List<Map<String, Object>> findPath(
70102
@RequestParam("from") String source,
71103
@RequestParam("to") String target) {
72-
var data = loadData();
73-
return topologyService.findPath(source, target, data.nodes(), data.edges());
104+
ensureCacheLoaded();
105+
requireCache();
106+
return topologyService.findPath(source, target, cachedNodes, cachedEdges);
74107
}
75108

76109
@GetMapping("/bottlenecks")
77110
public List<Map<String, Object>> findBottlenecks() {
78-
var data = loadData();
79-
return topologyService.findBottlenecks(data.nodes(), data.edges());
111+
ensureCacheLoaded();
112+
requireCache();
113+
return topologyService.findBottlenecks(cachedNodes, cachedEdges);
80114
}
81115

82116
@GetMapping("/circular")
83117
public List<List<String>> findCircularDeps() {
84-
var data = loadData();
85-
return topologyService.findCircularDeps(data.nodes(), data.edges());
118+
ensureCacheLoaded();
119+
requireCache();
120+
return topologyService.findCircularDeps(cachedNodes, cachedEdges);
86121
}
87122

88123
@GetMapping("/dead")
89124
public List<Map<String, Object>> findDeadServices() {
90-
var data = loadData();
91-
return topologyService.findDeadServices(data.nodes(), data.edges());
125+
ensureCacheLoaded();
126+
requireCache();
127+
return topologyService.findDeadServices(cachedNodes, cachedEdges);
92128
}
93129

94-
/**
95-
* Load nodes and edges from the analysis cache.
96-
*/
97-
private GraphData loadData() {
98-
Path root = Path.of(config.getRootPath()).toAbsolutePath().normalize();
99-
Path cachePath = root.resolve(config.getCacheDir()).resolve("analysis-cache.db");
100-
Path h2File = root.resolve(config.getCacheDir()).resolve("analysis-cache.mv.db");
101-
102-
if (!Files.exists(h2File)) {
130+
private void requireCache() {
131+
if (cachedNodes == null) {
103132
throw new ResponseStatusException(HttpStatus.NOT_FOUND,
104133
"No analysis cache found. Run analyze first.");
105134
}
106-
107-
try (AnalysisCache cache = new AnalysisCache(cachePath)) {
108-
List<CodeNode> nodes = cache.loadAllNodes();
109-
List<CodeEdge> edges = cache.loadAllEdges();
110-
return new GraphData(nodes, edges);
111-
}
112135
}
113-
114-
private record GraphData(List<CodeNode> nodes, List<CodeEdge> edges) {}
115136
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package io.github.randomcodespace.iq.config;
2+
3+
import org.springframework.context.annotation.Bean;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.context.annotation.Profile;
6+
import org.springframework.web.servlet.config.annotation.CorsRegistry;
7+
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
8+
9+
@Configuration
10+
@Profile("serving")
11+
public class CorsConfig {
12+
@Bean
13+
public WebMvcConfigurer corsConfigurer() {
14+
return new WebMvcConfigurer() {
15+
@Override
16+
public void addCorsMappings(CorsRegistry registry) {
17+
registry.addMapping("/api/**")
18+
.allowedOrigins("*")
19+
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
20+
.allowedHeaders("*");
21+
registry.addMapping("/mcp/**")
22+
.allowedOrigins("*")
23+
.allowedMethods("GET", "POST", "OPTIONS")
24+
.allowedHeaders("*");
25+
}
26+
};
27+
}
28+
}

src/main/java/io/github/randomcodespace/iq/detector/cpp/CppStructuresDetector.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
name = "cpp_structures",
2323
category = "structures",
2424
description = "Detects C/C++ classes, structs, functions, namespaces, and includes",
25-
parser = ParserType.ANTLR,
25+
parser = ParserType.REGEX,
2626
languages = {"cpp", "c"},
2727
nodeKinds = {NodeKind.CLASS, NodeKind.ENUM, NodeKind.METHOD, NodeKind.MODULE},
2828
edgeKinds = {EdgeKind.EXTENDS, EdgeKind.IMPORTS}

src/main/java/io/github/randomcodespace/iq/detector/csharp/CSharpEfcoreDetector.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
name = "csharp_efcore",
2323
category = "entities",
2424
description = "Detects Entity Framework Core DbContexts, entities, and migrations",
25-
parser = ParserType.ANTLR,
25+
parser = ParserType.REGEX,
2626
languages = {"csharp"},
2727
nodeKinds = {NodeKind.ENTITY, NodeKind.MIGRATION, NodeKind.REPOSITORY},
2828
edgeKinds = {EdgeKind.QUERIES},

src/main/java/io/github/randomcodespace/iq/detector/csharp/CSharpMinimalApisDetector.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
name = "csharp_minimal_apis",
2323
category = "endpoints",
2424
description = "Detects C# minimal API endpoints (MapGet, MapPost, etc.)",
25-
parser = ParserType.ANTLR,
25+
parser = ParserType.REGEX,
2626
languages = {"csharp"},
2727
nodeKinds = {NodeKind.ENDPOINT, NodeKind.GUARD, NodeKind.MODULE},
2828
edgeKinds = {EdgeKind.EXPOSES},

src/main/java/io/github/randomcodespace/iq/detector/csharp/CSharpStructuresDetector.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
name = "csharp_structures",
2121
category = "structures",
2222
description = "Detects C# classes, interfaces, enums, records, and controller endpoints",
23-
parser = ParserType.ANTLR,
23+
parser = ParserType.REGEX,
2424
languages = {"csharp"},
2525
nodeKinds = {NodeKind.ABSTRACT_CLASS, NodeKind.CLASS, NodeKind.ENDPOINT, NodeKind.ENUM, NodeKind.INTERFACE, NodeKind.MODULE},
2626
edgeKinds = {EdgeKind.EXTENDS, EdgeKind.IMPLEMENTS, EdgeKind.IMPORTS},

src/main/java/io/github/randomcodespace/iq/detector/go/GoOrmDetector.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
name = "go_orm",
2323
category = "database",
2424
description = "Detects Go ORM usage (GORM models, queries, migrations, connections)",
25-
parser = ParserType.ANTLR,
25+
parser = ParserType.REGEX,
2626
languages = {"go"},
2727
nodeKinds = {NodeKind.DATABASE_CONNECTION, NodeKind.ENTITY, NodeKind.MIGRATION, NodeKind.QUERY},
2828
edgeKinds = {EdgeKind.QUERIES},

src/main/java/io/github/randomcodespace/iq/detector/go/GoStructuresDetector.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
name = "go_structures",
2121
category = "structures",
2222
description = "Detects Go structs, interfaces, functions, and imports",
23-
parser = ParserType.ANTLR,
23+
parser = ParserType.REGEX,
2424
languages = {"go"},
2525
nodeKinds = {NodeKind.CLASS, NodeKind.INTERFACE, NodeKind.METHOD, NodeKind.MODULE},
2626
edgeKinds = {EdgeKind.DEFINES, EdgeKind.IMPORTS}

0 commit comments

Comments
 (0)