Skip to content

Euler - #49

Merged
e4c5 merged 50 commits into
mainfrom
euler
Feb 17, 2026
Merged

e4c5 merged 50 commits into
mainfrom
euler

Conversation

@e4c5

@e4c5 e4c5 commented Feb 16, 2026 •

Copy link
Copy Markdown
Owner

User description

Summary by CodeRabbit

  • New Features

    • Multi-backend knowledge-graph support (Neo4j + Apache AGE) with a pluggable graph store and CLI/config integration.
    • Pluggable AI service framework with provider selection and factory.
    • AnnotationFinder utility and benchmarking tool for parser method lookup.
  • Documentation

    • Expanded README and new docs on knowledge-graph design, remediation, tools, and quick-starts; added backend config examples.
  • Improvements

    • Enhanced signature/edge handling (enums, lambdas, references), batch writes, and more robust traversal/resolution.
  • Tests

    • Many new and updated tests and benchmarks for graph stores, AI services, and utilities.
  • Removed

    • Legacy Neo4j config utility and some outdated planning docs.

CodeAnt-AI Description

Emit richer structural and behavioral relationships in the knowledge graph and unify AI service behavior

What Changed

  • Knowledge graph builder now traverses full compilation units and emits explicit nodes/edges for types, nested/inner classes, enum constants, methods, constructors, fields, lambdas, method references and initializers; relationships created include EXTENDS, IMPLEMENTS, CONTAINS, ENCLOSES, CALLS, USES, ACCESSES and REFERENCES (unresolved targets are marked as partial).
  • Graph persistence is pluggable: a GraphStore abstraction with a factory lets the tool select Neo4j or Apache AGE backends at runtime; node upserts and batched edge flushes are used and store closing is guarded against errors.
  • Lambda and anonymous/local class occurrences are recorded as distinct nodes (unique signatures) so calls/reads inside lambda bodies attribute to lambda nodes; enum constants are emitted as distinct CONTAINS targets.
  • AI integrations were consolidated under a new AbstractAIService base: provider implementations (Gemini/OpenAI) use shared request building, retry/timeout handling, HTTP execution with retry, token-usage extraction, and resilient JSON extraction/parsing for recommendations.
  • Tests updated and expanded to cover structural edge emission, AI response parsing, retry-on-timeout behavior and token usage extraction.

Impact

✅ Clearer type and call relationships in generated graphs
✅ Support for Apache AGE and Neo4j backends at runtime
✅ Fewer AI API timeouts due to unified retry/timeout handling

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Copilot AI review requested due to automatic review settings February 16, 2026 03:39
@codeant-ai

codeant-ai Bot commented Feb 16, 2026

Copy link
Copy Markdown
Contributor

CodeAnt AI is reviewing your PR.


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@codeant-ai

codeant-ai Bot commented Feb 16, 2026

Copy link
Copy Markdown
Contributor

CodeAnt AI is running Incremental review


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@codeant-ai codeant-ai Bot added size:XXL This PR changes 1000+ lines, ignoring generated files and removed size:XXL This PR changes 1000+ lines, ignoring generated files labels Feb 16, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@src/main/java/com/raditha/graph/ApacheAgeGraphStore.java`:
- Around line 115-117: The methods persistNode, flushEdges, clearGraph, and
executeQuery currently swallow all exceptions and only log them; change their
error handling so failures are visible to callers by either rethrowing a runtime
exception or returning an explicit failure indicator—preferably rethrow a new
RuntimeException (including a clear message and the caught exception as the
cause) from each catch block in persistNode, flushEdges, clearGraph, and
executeQuery so callers can detect and handle write/query failures; ensure any
callers are updated if needed to handle the propagated RuntimeException.

In `@src/main/java/com/raditha/graph/GraphStoreFactory.java`:
- Around line 81-91: The code currently does an unchecked cast when retrieving
nested configs in createNeo4jStore (and the corresponding AGE branch), which can
throw ClassCastException for malformed scalar values; add a defensive helper
method getSubMap(Map<String,Object> cfg, String key) that checks instanceof Map
and returns the cast map or an empty Map.of() otherwise, then replace the direct
casts in createNeo4jStore (and the AGE factory) to call getSubMap(graphConfig,
"neo4j") / getSubMap(graphConfig, "age") and keep the rest of the logic
(getString(...)) unchanged so malformed configs safely fall back to defaults.

In
`@src/main/java/sa/com/cloudsolutions/antikythera/examples/AbstractAIService.java`:
- Around line 568-589: The method extractJsonFromCodeBlocks currently breaks out
when any line ends with '}' or ']', which truncates nested JSON; update this
logic to track brace/bracket depth instead: introduce an integer depth variable,
increment on '{' or '[', decrement on '}' or ']', update depth while appending
lines (when starting to capture via foundJson/inCodeBlock), and only break when
depth <= 0 indicating the outermost structure closed; ensure variables
referenced are depth, inCodeBlock, foundJson, and jsonBuilder in the existing
method.

In
`@src/test/java/sa/com/cloudsolutions/antikythera/examples/AbstractAIServiceTest.java`:
- Around line 122-140: The test testExtractJsonFromCodeBlocks_WithNestedJson
currently only uses contains(...) so it misses truncation; update it to assert
structural validity by parsing the extracted result with a JSON parser (e.g.,
ObjectMapper.readTree) or by comparing against the full expected JSON string,
calling AbstractAIService.extractJsonFromCodeBlocks(response) and then asserting
the parsed tree is non-null or equals the expected JSON structure to ensure the
outer braces aren’t dropped.
🧹 Nitpick comments (9)
src/main/java/sa/com/cloudsolutions/antikythera/examples/AnnotationFinder.java (3)

43-44: Static mutable state reduces testability and reusability.

finalSearchTerm and isSimpleMode are mutable static fields, which means this class cannot be safely reused or tested in parallel. Consider passing these as method parameters through the call chain instead of relying on class-level static state.


40-41: Consider using SLF4J for error reporting.

The @SuppressWarnings("java:S106") suppression is reasonable for CLI stdout output, but the error messages on System.err (lines 59, 107–112) would benefit from using an SLF4J logger for consistency. As per coding guidelines, "Use SLF4J logger fields with @Slf4j annotation or manual logger initialization in Java classes".


128-133: Minor: redundant endsWith check when searchTerm is fully qualified.

When searchTerm is already a FQN (e.g., org.springframework.stereotype.Service), line 131's endsWith("." + searchTerm) will almost never match anything meaningful since annotation names are unlikely to have a prefix before a full package path. Not a bug, but a no-op branch in that scenario.

src/main/java/com/raditha/graph/ApacheAgeGraphStore.java (1)

108-108: Avoid creating a new ObjectMapper on every call — reuse a shared instance.

ObjectMapper is thread-safe and expensive to construct. It's instantiated inline in persistNode, flushEdges (per edge!), and executeQuery. Extract it to a private static final field.

♻️ Proposed fix
+    private static final com.fasterxml.jackson.databind.ObjectMapper OBJECT_MAPPER =
+            new com.fasterxml.jackson.databind.ObjectMapper();
+
     private static final String BASE_LABEL = "CodeElement";

Then replace all three new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(params) calls with OBJECT_MAPPER.writeValueAsString(params).

Also applies to: 152-152, 219-219

src/test/java/com/raditha/graph/GraphStoreFactoryTest.java (1)

74-86: Consider using JUnit 5 @TempDir to simplify temp file lifecycle.

The manual createTempFile + try/finally/deleteIfExists pattern works but is verbose. @TempDir handles cleanup automatically and is the idiomatic JUnit 5 approach.

♻️ Example with `@TempDir`
+import org.junit.jupiter.api.io.TempDir;
+import java.nio.file.Path;
+
 class GraphStoreFactoryTest {
+    `@TempDir`
+    Path tempDir;
+
     `@ParameterizedTest`(name = "{0}")
     `@MethodSource`("neo4jStoreTestCases")
     void testNeo4jStoreCreation(String testName, String yamlConfig, Class<? extends GraphStore> expectedClass) throws Exception {
-        Path config = Files.createTempFile("graph-config", ".yml");
-        try {
-            Files.writeString(config, yamlConfig);
-            try (GraphStore store = GraphStoreFactory.createGraphStore(config.toFile())) {
-                assertInstanceOf(expectedClass, store);
-            }
-        } finally {
-            Files.deleteIfExists(config);
-        }
+        Path config = tempDir.resolve("graph-config.yml");
+        Files.writeString(config, yamlConfig);
+        try (GraphStore store = GraphStoreFactory.createGraphStore(config.toFile())) {
+            assertInstanceOf(expectedClass, store);
+        }
     }
src/main/java/sa/com/cloudsolutions/antikythera/examples/AbstractAIService.java (4)

414-425: getConfigInt and getConfigDouble propagate uncaught NumberFormatException on malformed string values.

Integer.parseInt (line 422) and Double.parseDouble (line 438) throw NumberFormatException if the config string is not a valid number. Since config values may come from YAML files or environment variables, a malformed value would surface as an unhandled runtime exception instead of falling back to the default.

♻️ Proposed fix — catch and fall back to default
         } else if (value instanceof String str) {
-            return Integer.parseInt(str);
+            try {
+                return Integer.parseInt(str);
+            } catch (NumberFormatException e) {
+                logger.warn("Invalid integer config value for key '{}': '{}'", key, str);
+                return defaultValue;
+            }
         }

Apply the same pattern to getConfigDouble.

Also applies to: 430-442


606-618: Blind fallback to the first field value may silently misparse non-array responses.

Lines 615–618 grab the first field of an unknown object and treat it as the recommendations array. If the LLM returns an unexpected schema (e.g., {"error": "rate limited"}), this silently proceeds with a non-array node, which is then caught by the !responseArray.isArray() guard — but only after potentially confusing log output. Consider logging the unexpected structure here for debuggability.


536-563: extractJsonFromResponse uses indexOf/lastIndexOf which can mis-pair brackets across disjoint structures.

If the response contains multiple separate JSON objects or arrays (e.g., prose with [see above] and then {"key": "val"}), the first [ and last ] may not belong to the same structure. This is an inherent limitation of the simple bracket-matching approach. It works for typical single-structure LLM responses, but worth a comment or a TODO for robustness if multi-structure responses are expected.


338-372: LGTM — cloneClassSignature and cloneNodeList are clean utility methods.

Deliberately omitting members from the clone is a sound approach for creating a stub compilation unit. The @SuppressWarnings("unchecked") annotation is missing on the cast at line 390, but this is a minor nit.

Also applies to: 383-393

Comment on lines +115 to +117
} catch (Exception e) {
logger.error("Failed to persist node: {}", signature, e);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Silent exception swallowing in persistNode can hide data-loss bugs.

persistNode catches all exceptions and only logs them. The caller has no indication the node was never written, which can lead to dangling edge references later. The same pattern repeats in flushEdges (Line 161), clearGraph (Line 181), and executeQuery (Line 241). Consider either propagating a runtime exception or returning a success/failure indicator so callers can react.

🤖 Prompt for AI Agents
In `@src/main/java/com/raditha/graph/ApacheAgeGraphStore.java` around lines 115 -
117, The methods persistNode, flushEdges, clearGraph, and executeQuery currently
swallow all exceptions and only log them; change their error handling so
failures are visible to callers by either rethrowing a runtime exception or
returning an explicit failure indicator—preferably rethrow a new
RuntimeException (including a clear message and the caught exception as the
cause) from each catch block in persistNode, flushEdges, clearGraph, and
executeQuery so callers can detect and handle write/query failures; ensure any
callers are updated if needed to handle the propagated RuntimeException.

Comment on lines +81 to +91
@SuppressWarnings("unchecked")
private static GraphStore createNeo4jStore(Map<String, Object> graphConfig, int batchSize) {
Map<String, Object> neo4jConfig = (Map<String, Object>) graphConfig.getOrDefault(DEFAULT_TYPE, Map.of());

String uri = getString(neo4jConfig, "uri", DEFAULT_NEO4J_URI);
String username = getString(neo4jConfig, "username", DEFAULT_NEO4J_USERNAME);
String password = getString(neo4jConfig, "password", "");
String database = getString(neo4jConfig, "database", DEFAULT_NEO4J_DATABASE);

return new Neo4jGraphStore(uri, username, password, database, batchSize);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Unchecked cast to Map<String, Object> can throw ClassCastException on malformed config.

Lines 83 and 95 cast the value of "neo4j" / "age" keys to Map<String, Object>. If a user writes a scalar (e.g., neo4j: true) instead of a nested map, this will throw an unhandled ClassCastException at runtime. An instanceof check or a defensive helper would make this more robust.

🛡️ Proposed defensive helper
+    `@SuppressWarnings`("unchecked")
+    private static Map<String, Object> getSubMap(Map<String, Object> config, String key) {
+        Object value = config.get(key);
+        if (value instanceof Map) {
+            return (Map<String, Object>) value;
+        }
+        return Map.of();
+    }

Then use getSubMap(graphConfig, "neo4j") and getSubMap(graphConfig, "age") instead of the raw casts.

🤖 Prompt for AI Agents
In `@src/main/java/com/raditha/graph/GraphStoreFactory.java` around lines 81 - 91,
The code currently does an unchecked cast when retrieving nested configs in
createNeo4jStore (and the corresponding AGE branch), which can throw
ClassCastException for malformed scalar values; add a defensive helper method
getSubMap(Map<String,Object> cfg, String key) that checks instanceof Map and
returns the cast map or an empty Map.of() otherwise, then replace the direct
casts in createNeo4jStore (and the AGE factory) to call getSubMap(graphConfig,
"neo4j") / getSubMap(graphConfig, "age") and keep the rest of the logic
(getString(...)) unchanged so malformed configs safely fall back to defaults.

Comment on lines +112 to +114
MavenHelper mavenHelper = new MavenHelper();
mavenHelper.readPomFile();
mavenHelper.buildJarPaths();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Exceptions thrown by the Maven helper (for example when pom.xml is missing or invalid) are no longer caught, causing the CLI to abort instead of proceeding with the previously supported "limited resolution" mode; this breaks analysis for non-Maven projects or projects with a bad pom file. [logic error]

Severity Level: Major ⚠️
- ❌ KnowledgeGraphCLI fails entirely when pom.xml missing or invalid.
- ⚠️ Limited-resolution analysis mode no longer reachable for bad pom.xml.
- ⚠️ Non-Maven or misconfigured projects cannot generate knowledge graphs.
Suggested change
MavenHelper mavenHelper = new MavenHelper();
mavenHelper.readPomFile();
mavenHelper.buildJarPaths();
try {
MavenHelper mavenHelper = new MavenHelper();
mavenHelper.readPomFile();
mavenHelper.buildJarPaths();
} catch (Exception e) {
logger.warn("Could not load Maven dependencies (pom.xml not found or invalid). Proceeding with limited resolution.", e);
}
Steps of Reproduction ✅
1. Build and run the CLI entry point `com.raditha.graph.KnowledgeGraphCLI.main` from
`src/main/java/com/raditha/graph/KnowledgeGraphCLI.java:39-47`, providing a valid
`graph.yml` but pointing `--base-path` (or positional project path) to a Java project
directory that does not contain a valid `pom.xml` (e.g., non-Maven project or project with
malformed pom).

2. `main` calls `parseArgs()` at `KnowledgeGraphCLI.java:49-85` to resolve `configPath`
and `basePath`, then invokes `new KnowledgeGraphCLI().run(options.basePath(),
options.configPath())` at line 42.

3. Inside `run(...)` at `KnowledgeGraphCLI.java:96-133`, configuration is loaded and
`AbstractCompiler.reset()` is executed (line 110), after which a `MavenHelper` is
instantiated and `readPomFile()` / `buildJarPaths()` are called at lines 112-114 to
resolve Maven dependencies based on the target project.

4. When `pom.xml` is missing or invalid for the target project,
`MavenHelper.readPomFile()` or `buildJarPaths()` (external class
`sa.com.cloudsolutions.antikythera.parser.MavenHelper`, used similarly in
`src/test/java/com/raditha/graph/KnowledgeGraphIntegrationTest.java:96-99` and declared to
throw `XmlPullParserException`) will throw an exception, which now propagates out of
`run(...)`, is caught only in `main`'s broad `catch (Exception e)` at lines 40-45, causes
the log message "Analysis failed" and `System.exit(1)`, and prevents the CLI from
continuing with the previously supported "limited resolution" mode that was implemented by
the removed try/catch around the MavenHelper block in the old hunk.
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/main/java/com/raditha/graph/KnowledgeGraphCLI.java
**Line:** 112:114
**Comment:**
	*Logic Error: Exceptions thrown by the Maven helper (for example when pom.xml is missing or invalid) are no longer caught, causing the CLI to abort instead of proceeding with the previously supported "limited resolution" mode; this breaks analysis for non-Maven projects or projects with a bad pom file.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
👍 | 👎

@codeant-ai

codeant-ai Bot commented Feb 16, 2026

Copy link
Copy Markdown
Contributor

CodeAnt AI Incremental review completed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In
`@src/main/java/sa/com/cloudsolutions/antikythera/examples/AbstractAIService.java`:
- Around line 417-428: getConfigInt currently calls Integer.parseInt(str)
without handling NumberFormatException; update getConfigInt to catch
NumberFormatException around Integer.parseInt and return defaultValue on parse
failure (preserve existing behavior for Integer instances), and apply the same
pattern in getConfigDouble by wrapping Double.parseDouble(str) in a try-catch
that returns the provided defaultValue on parse errors. Ensure both methods
still handle null config and non-string/non-number types by returning
defaultValue, and reference the existing methods getConfigInt and
getConfigDouble when making the changes.

In
`@src/test/java/sa/com/cloudsolutions/antikythera/examples/AnnotationFinderTest.java`:
- Around line 186-189: The current assertions in AnnotationFinderTest use weak
disjunctions that are trivially true; replace the two assertions that check
signature.contains("items") || signature.contains("List") and
signature.contains("counts") || signature.contains("Map") with stronger checks
that verify the parameter name is paired with its type (e.g., ensure signature
contains both "List" and "items" together and both "Map" and "counts" together)
or, even better, assert equality against the exact expected signature string
produced by the method under test (refer to the local variable signature in the
test to construct the expected value).

In
`@src/test/java/sa/com/cloudsolutions/antikythera/examples/OpenAIServiceTest.java`:
- Around line 471-490: The test method
testExtractJsonFromResponse_MultipleFormats uses string literals with escaped
"\\n" and escaped quotes, so it doesn't exercise the code-block parsing path in
openAIService.extractJsonFromResponse; change the codeBlockResponse and
plainResponse inputs to use actual newline characters and normal JSON quotes
(e.g., "```json\n[{\"test\": \"value\"}]\n```" and "Here is the result:
[{\"test\": \"value\"}] end") and update the corresponding expected assertions
to match unescaped JSON ("[{\"test\": \"value\"}]"); leave the no-JSON and null
checks as-is.
- Around line 197-206: The parameterized test testExtractJsonFromResponse in
OpenAIServiceTest is using CSV-escaped strings (\\n, \\") which produce literal
backslashes instead of real newlines/quotes; replace the `@CsvSource` with a
`@MethodSource`: add a static provider method (e.g.,
provideExtractJsonFromResponseCases or provideExtractJsonCases) that returns
Stream<Arguments> containing the input strings with actual newlines and real
quotes (e.g., "Here is the JSON response:\n[{\"test\": \"value\"}]\nEnd of
response." etc.) and the expected JSON outputs, then change the test signature
to `@ParameterizedTest` `@MethodSource`("provideExtractJsonFromResponseCases") void
testExtractJsonFromResponse(String input, String expected) to ensure realistic
IA response formats are used.
🧹 Nitpick comments (10)
src/test/java/com/raditha/perf/JavaParserMethodFindingBenchmark.java (2)

33-51: Visitor never stops after finding the target — full traversal every time.

The visitor calls super.visit(md, targetMethodName) unconditionally, so it walks every MethodDeclaration in the AST even after a match. This negates the main advantage of the visitor pattern (early exit) and makes the benchmark comparison less meaningful. For overloaded methods, foundMethod is also silently overwritten with the last match.

Additionally, reset() (line 48) is dead code — a new MethodFinderVisitor is allocated on every iteration in findMethodUsingVisitor().

♻️ Proposed fix: stop after first match and reuse the visitor
 private static class MethodFinderVisitor extends VoidVisitorAdapter<String> {
     private MethodDeclaration foundMethod;

     `@Override`
     public void visit(MethodDeclaration md, String targetMethodName) {
+        if (foundMethod != null) {
+            return; // stop traversal after first match
+        }
         if (md.getNameAsString().equals(targetMethodName)) {
             foundMethod = md;
+            return;
         }
         super.visit(md, targetMethodName);
     }

And reuse the visitor instance in the benchmark loop:

 private MethodDeclaration findMethodUsingVisitor() {
-    MethodFinderVisitor visitor = new MethodFinderVisitor();
+    visitor.reset();
     classDeclaration.accept(visitor, TARGET_METHOD_NAME);
     return visitor.getFoundMethod();
 }

90-143: Hand-rolled microbenchmark is susceptible to JIT/GC noise.

System.nanoTime() loops without fork isolation, GC pauses control, or statistical analysis can produce misleading results. Consider using JMH for reliable microbenchmarks — it handles warmup, dead-code elimination, and result statistics out of the box.

src/test/java/sa/com/cloudsolutions/antikythera/examples/AnnotationFinderTest.java (1)

384-386: Assertion tests local string concatenation, not the SUT.

Lines 385-386 construct a string locally and then assert it equals a hardcoded value — this validates nothing about AnnotationFinder. Only the assertion on Lines 389-390 (calling buildMethodSignature) actually exercises the SUT. Consider removing the trivially-true assertion or replacing it with a call to an actual AnnotationFinder method that produces simple-mode output.

src/main/java/sa/com/cloudsolutions/antikythera/examples/AbstractAIService.java (2)

598-640: Index-based correlation between AI response array and query list is fragile.

Line 631 assumes a 1:1 positional mapping between responseArray elements and batch.getQueries(). If the AI returns fewer or more elements, or reorders them, recommendations silently attach to the wrong query. The && i < queries.size() guard prevents an AIOOBE but drops extra recommendations without logging.

Consider logging a warning when sizes don't match, so mismatches are visible during debugging.

🔍 Suggested improvement
         List<RepositoryQuery> queries = batch.getQueries();
+        if (responseArray.size() != queries.size()) {
+            logger.warn("AI returned {} recommendations but batch has {} queries — correlating by index",
+                    responseArray.size(), queries.size());
+        }
         for (int i = 0; i < responseArray.size() && i < queries.size(); i++) {

86-90: analyzeQueryBatch doesn't guard against null or empty batch.

If batch is null or batch.getQueries() is empty, the call chain proceeds into payload building and an actual API call, wasting tokens. A quick guard at the entry point would avoid unnecessary API calls.

src/test/java/sa/com/cloudsolutions/antikythera/examples/GeminiAIServiceTest.java (1)

70-72: MockitoAnnotations.openMocks(this) return value is not closed.

openMocks returns an AutoCloseable that should be closed after each test to release mock resources. Consider using @ExtendWith(MockitoExtension.class) instead, or storing and closing the handle in @AfterEach.

♻️ Preferred approach
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;

+@ExtendWith(MockitoExtension.class)
 class GeminiAIServiceTest {
     ...

     `@BeforeEach`
     void setUp() throws IOException {
-        MockitoAnnotations.openMocks(this);
         geminiAIService = new GeminiAIService();
         ...
     }
src/test/java/sa/com/cloudsolutions/antikythera/examples/OpenAIServiceTest.java (1)

42-86: Significant duplication with GeminiAIServiceTest.

setUpAll(), setUp(), createTestQueryBatch(), and several test methods (testBuildTableSchemaString, testBuildTableSchemaString_NullTable, testExtractJsonFromResponse_MultipleFormats, testExtractRecommendedColumnOrder_NestedClass) are nearly identical to their GeminiAIServiceTest counterparts. Consider extracting shared setup and helpers into a common base test class or utility to reduce maintenance burden.

Also applies to: 267-286

src/main/java/sa/com/cloudsolutions/antikythera/examples/OpenAIService.java (1)

34-39: Double-brace initialization creates an anonymous LinkedHashMap subclass.

This is a well-known Java anti-pattern: the {{ }} initializer creates an anonymous inner class that holds a reference to the enclosing class, slightly inflates the class count, and can confuse serialization. Use Map.of entries fed into a LinkedHashMap constructor or a static factory method instead.

♻️ Suggested alternative
-    private static final Map<String, ModelPricing> MODEL_PRICING = new LinkedHashMap<>() {{
-        put("gpt-4o-mini", new ModelPricing(0.150, 0.600, 0.25));
-        put("gpt-4-turbo", new ModelPricing(10.00, 30.00, 0.25));
-        put(GPT_4_O, new ModelPricing(2.50, 10.00, 0.25));
-        put("gpt-4", new ModelPricing(30.00, 60.00, 0.25));
-    }};
+    private static final Map<String, ModelPricing> MODEL_PRICING;
+    static {
+        Map<String, ModelPricing> m = new LinkedHashMap<>();
+        m.put("gpt-4o-mini", new ModelPricing(0.150, 0.600, 0.25));
+        m.put("gpt-4-turbo", new ModelPricing(10.00, 30.00, 0.25));
+        m.put(GPT_4_O, new ModelPricing(2.50, 10.00, 0.25));
+        m.put("gpt-4", new ModelPricing(30.00, 60.00, 0.25));
+        MODEL_PRICING = Map.copyOf(m); // or Collections.unmodifiableMap(m) to preserve order
+    }

Note: Map.copyOf does not preserve insertion order. Use Collections.unmodifiableMap(m) if iteration order matters (it does for the contains() lookup).

src/main/java/sa/com/cloudsolutions/antikythera/examples/GeminiAIService.java (2)

127-151: sendApiRequest retry/timeout logic is nearly identical to OpenAIService.sendApiRequest.

Both implementations share the same pattern: read initialRetryCount, compare with retryCount, add 30s on retry, build an HttpRequest, and delegate to executeHttpRequest. The only differences are the URL construction and auth header. Consider extracting the common timeout-adjustment and retry-count logic into the base class, with subclasses providing only the request-building specifics (URL, headers).

♻️ Sketch

In AbstractAIService:

protected abstract HttpRequest buildHttpRequest(String payload, int timeoutSeconds);

protected String sendApiRequest(String payload, int retryCount) throws IOException, InterruptedException {
    int timeoutSeconds = getConfigInt("timeout_seconds", 90);
    int initialRetryCount = getConfigInt("initial_retry_count", 0);
    if (retryCount < initialRetryCount) {
        timeoutSeconds += 30;
        logger.info("Retrying with extra 30s (total: {}s)", timeoutSeconds);
    }
    HttpRequest request = buildHttpRequest(payload, timeoutSeconds);
    return executeHttpRequest(request, payload, retryCount);
}

142-142: API key appended as URL query parameter — standard for Gemini but could leak in access logs or exception stack traces.

?key= in the URL means the API key may appear in HTTP access logs, exception messages, or any toString() of the URI. This is Google's documented approach for Gemini, but be mindful of logging the request URL at debug level.

Comment thread src/test/java/sa/com/cloudsolutions/antikythera/examples/OpenAIServiceTest.java Outdated
Comment thread src/test/java/sa/com/cloudsolutions/antikythera/examples/OpenAIServiceTest.java Outdated
@codeant-ai

codeant-ai Bot commented Feb 17, 2026

Copy link
Copy Markdown
Contributor

CodeAnt AI is running Incremental review


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@codeant-ai codeant-ai Bot added size:XXL This PR changes 1000+ lines, ignoring generated files and removed size:XXL This PR changes 1000+ lines, ignoring generated files labels Feb 17, 2026
@codeant-ai

codeant-ai Bot commented Feb 17, 2026

Copy link
Copy Markdown
Contributor

CodeAnt AI Incremental review completed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (6)
src/test/java/sa/com/cloudsolutions/antikythera/examples/AnnotationFinderTest.java (3)

318-319: Nit: prefer importing java.util.List and java.util.HashSet at the top of the file.

These types are used inline with fully-qualified names while other java.util types (Set, Stream) are already imported.

Suggested fix

Add to the import block:

import java.util.HashSet;
import java.util.List;

Then replace inline usages:

-        java.util.List<MethodDeclaration> methods = classDecl.getMethods();
-        Set<String> seen = new java.util.HashSet<>();
+        List<MethodDeclaration> methods = classDecl.getMethods();
+        Set<String> seen = new HashSet<>();

(Also applies to Line 354–355.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/sa/com/cloudsolutions/antikythera/examples/AnnotationFinderTest.java`
around lines 318 - 319, Add imports for java.util.List and java.util.HashSet at
the top of the file and replace the fully-qualified inline usages with the
simple types: change java.util.List<MethodDeclaration> methods =
classDecl.getMethods(); to List<MethodDeclaration> methods =
classDecl.getMethods(); and change Set<String> seen = new java.util.HashSet<>();
to Set<String> seen = new HashSet<>(); (also update the other occurrences
mentioned around the same area, e.g., the usages at lines flagged 354–355) so
the file consistently uses imports for these java.util types.

386-392: Lines 387–388 assert local string concatenation, not AnnotationFinder behavior.

The "simple mode" assertion constructs a string locally and then checks it equals a hardcoded value — this tests Java string concatenation, not any AnnotationFinder method. Only the "detailed mode" assertion on Line 391–392 actually exercises buildMethodSignature. Consider either removing the trivial assertion or replacing it with a call to an actual AnnotationFinder API that produces simple-mode output.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/sa/com/cloudsolutions/antikythera/examples/AnnotationFinderTest.java`
around lines 386 - 392, The test currently asserts a locally constructed string
instead of exercising AnnotationFinder; replace the trivial "simpleOutput"
concatenation and assertEquals call with a call into AnnotationFinder that
produces simple-mode output (e.g., call
AnnotationFinder.buildSimpleMethodSignature(method) or the equivalent
simple-mode API on AnnotationFinder that returns
"com.example.TestClass#testMethod"), keeping the detailed-mode assertion that
uses AnnotationFinder.buildMethodSignature(method) intact.

296-368: "Simple mode" tests replicate production logic inline rather than testing an AnnotationFinder API.

These tests manually iterate methods, check annotations, and build output strings — essentially reimplementing the expected tool behavior in the test itself. If AnnotationFinder exposes a method for simple-mode output (or will in the future), these tests should call that method directly. Otherwise, the tests are validating their own inline logic rather than the tool's behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/sa/com/cloudsolutions/antikythera/examples/AnnotationFinderTest.java`
around lines 296 - 368, The tests reimplement simple-mode behavior inline
instead of exercising AnnotationFinder; update both tests to call a single
AnnotationFinder API that returns the simple-mode outputs (e.g., add or use a
method like AnnotationFinder.getSimpleModeOutputs(ClassOrInterfaceDeclaration)
or
AnnotationFinder.generateSimpleModeOutputs(CompilationUnit/ClassOrInterfaceDeclaration))
and assert against that result; if such a method does not exist, add a small API
on AnnotationFinder (name it getSimpleModeOutputs or generateSimpleModeOutputs)
that takes the class declaration (or compilation unit) and returns a Set<String>
of "ClassName#methodName" for methods with the Test annotation, then replace the
per-test forEach/hasAnnotation/string-building logic with calls to that method
and assert its size/contents.
src/test/java/sa/com/cloudsolutions/antikythera/examples/GeminiAIServiceConfigTest.java (1)

218-228: Add a test for explicitly null config values.

The method has tests for missing keys and non-string values, but not for keys that exist with null values. When config.put("api_key", null) and getConfigString("api_key", default) is called, the null value fails the instanceof String check, causing a fallthrough to environment variable checks before returning the default. While the outcome is the same as a missing key, the code path differs. A focused test would clarify expected behavior:

📝 Suggested test
`@Test`
void testGetConfigString_NullValueInConfig() {
    Map<String, Object> config = new HashMap<>();
    config.put("api_key", null);
    service.setConfig(config);
    
    String result = service.getConfigString("api_key", "default-key");
    
    assertEquals("default-key", result, "Should return default when config value is null");
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/sa/com/cloudsolutions/antikythera/examples/GeminiAIServiceConfigTest.java`
around lines 218 - 228, Add a focused unit test in GeminiAIServiceConfigTest to
cover keys present with null values: create a test method (e.g.,
testGetConfigString_NullValueInConfig) that does Map<String,Object> config = new
HashMap<>(); config.put("api_key", null); service.setConfig(config); then call
service.getConfigString("api_key", "default-key") and
assertEquals("default-key", result). This ensures getConfigString's behavior for
a null config entry is verified (similar style and assertions as the existing
testGetConfigString_NonStringConfigValue).
src/main/java/sa/com/cloudsolutions/antikythera/examples/OpenAIService.java (1)

94-116: Re-reading initial_retry_count from config on every retry call is slightly wasteful but not incorrect.

Line 98 calls getConfigInt("initial_retry_count", 0) on every invocation of sendApiRequest(payload, retryCount), including recursive retry calls. Since config doesn't change between retries, this is redundant but harmless. Mentioning for awareness only — no change needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/sa/com/cloudsolutions/antikythera/examples/OpenAIService.java`
around lines 94 - 116, The code repeatedly calls
getConfigInt("initial_retry_count", 0) inside sendApiRequest(String payload, int
retryCount), which is redundant across recursive retries; either leave as-is (no
change required) or cache the value once and reuse it — e.g., read
initial_retry_count earlier (call getConfigInt in the caller or a constructor)
and pass it into sendApiRequest or store it in a private field so sendApiRequest
uses the cached initialRetryCount rather than calling getConfigInt on every
invocation.
pom.xml (1)

136-146: Misleading XML comment — PostgreSQL dependency placed under the Neo4j comment.

The comment on line 136 reads <!-- Neo4j Java Driver for Knowledge Graph storage --> but lines 137–141 declare the PostgreSQL JDBC driver. This is confusing for future maintainers. Add a separate comment for the PostgreSQL dependency.

Proposed fix
-        <!-- Neo4j Java Driver for Knowledge Graph storage -->
-        <dependency>
+        <!-- PostgreSQL JDBC Driver for Apache AGE graph storage -->
+        <dependency>
             <groupId>org.postgresql</groupId>
             <artifactId>postgresql</artifactId>
             <version>42.7.2</version>
         </dependency>
+        <!-- Neo4j Java Driver for Knowledge Graph storage -->
         <dependency>
             <groupId>org.neo4j.driver</groupId>
             <artifactId>neo4j-java-driver</artifactId>
             <version>5.18.0</version>
         </dependency>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pom.xml` around lines 136 - 146, Update the misleading XML comment so each
dependency has the correct descriptive comment: move or add a comment describing
the PostgreSQL JDBC driver above the org.postgresql:postgresql dependency and
keep the Neo4j comment above the org.neo4j.driver:neo4j-java-driver dependency;
ensure the comment text clearly states "PostgreSQL JDBC driver" for the postgres
dependency and "Neo4j Java Driver for Knowledge Graph storage" for the neo4j
dependency so maintainers can unambiguously identify org.postgresql:postgresql
and org.neo4j.driver:neo4j-java-driver.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pom.xml`:
- Around line 137-141: Update the three Maven dependencies shown by replacing
the artifact versions: bump artifactId "postgresql" from 42.7.2 to 42.7.10;
upgrade "testcontainers-neo4j" to either 2.0.3 (preferred) or at minimum 1.21.4;
and update "system-stubs-jupiter" to 2.1.8 (or if you prefer to keep 2.1.x, add
explicit dependencyManagement or dependency overrides for AssertJ >= 3.27.7 and
Jetty >= 9.4.57 to mitigate the transitive CVEs). Ensure the pom's dependency
entries for artifactId postgresql, testcontainers-neo4j, and
system-stubs-jupiter are updated accordingly and run mvn dependency:tree to
verify the transitive versions were resolved as intended.

In
`@src/main/java/sa/com/cloudsolutions/antikythera/examples/AbstractAIService.java`:
- Around line 539-566: The current extractJsonFromResponse method can return
substrings with mismatched brackets because it uses indexOf/lastIndexOf; update
extractJsonFromResponse to (1) prefer extracting JSON via
extractJsonFromCodeBlocks(response) first when present, and otherwise when using
the bracket-based substring logic (arrayStart/arrayEnd or objectStart/objectEnd)
validate the candidate by attempting to parse it with the existing objectMapper
(e.g., call objectMapper.readTree(candidate) inside a try/catch) and only return
the candidate if parsing succeeds; if parsing fails, continue to the next
extraction strategy or return null. Ensure you reference extractJsonFromResponse
and extractJsonFromCodeBlocks and use the objectMapper.readTree validation to
avoid returning malformed JSON.
- Around line 571-604: The extractJsonFromCodeBlocks method can prematurely
break if foundJson is set before any opening brace/bracket is seen; add a
boolean flag (e.g., hasOpenedStructure = false) initialized alongside
braceDepth/bracketDepth, set to true whenever you increment braceDepth or
bracketDepth (when encountering '{' or '['), and change the break condition to
require hasOpenedStructure && braceDepth == 0 && bracketDepth == 0; update
references to foundJson, braceDepth, bracketDepth and the loop in
extractJsonFromCodeBlocks accordingly so the parser only exits after at least
one opening structure was seen and subsequently closed.

In
`@src/test/java/sa/com/cloudsolutions/antikythera/examples/OpenAIServiceTest.java`:
- Around line 72-88: The test uses MockitoAnnotations.openMocks(this) in setUp()
which returns an AutoCloseable that is never closed; replace manual mock init by
annotating the test class (OpenAIServiceTest) with
`@ExtendWith`(MockitoExtension.class) and remove the
MockitoAnnotations.openMocks(this) call from the setUp() method so the
MockitoExtension manages mock lifecycle automatically (no need to call/close
AutoCloseable).

---

Duplicate comments:
In
`@src/main/java/sa/com/cloudsolutions/antikythera/examples/AbstractAIService.java`:
- Around line 58-67: The constructor AbstractAIService currently mutates the
global StaticJavaParser configuration (via new ParserConfiguration() and
StaticJavaParser.setConfiguration(parserConfig)) on every instantiation, causing
a thread-unsafe race; change this to a one-time initialization by moving the
ParserConfiguration/StaticJavaParser.setConfiguration(...) into a static
initializer or dedicated bootstrap method that runs once (e.g., a static block
in AbstractAIService or a static init method called on class load), and leave
the AbstractAIService() constructor to only initialize instance fields
(objectMapper, lastTokenUsage, systemPrompt) so the global parser config is not
overwritten per instance.
- Around line 417-428: getConfigInt and getConfigDouble currently call
Integer.parseInt(str) and Double.parseDouble(str) directly which will throw
NumberFormatException for non-numeric strings; wrap the string-parse logic in a
try-catch that catches NumberFormatException (and optionally
NullPointerException) and returns the provided default on failure, and trim the
string before parsing; update the methods getConfigInt(...) and
getConfigDouble(...) to perform the safe parse-with-catch and return default
when parsing fails.

In
`@src/test/java/sa/com/cloudsolutions/antikythera/examples/AnnotationFinderTest.java`:
- Around line 186-194: Replace the loose contains-based assertions in
AnnotationFinderTest (the assertions using signature.contains(...) for
"process", "items", "List", "counts", "Map" and the startsWith/endsWith checks)
with a single exact equality assertion against the expected signature string;
build the expected string (e.g. "process(List<String> items, Map<String,Integer>
counts)" or whatever the canonical formatting used by other tests is) and call
assertEquals(expected, signature) to ensure exact match, updating the assertion
message accordingly so the test follows the same exact-equality pattern as the
other parameterized cases.

---

Nitpick comments:
In `@pom.xml`:
- Around line 136-146: Update the misleading XML comment so each dependency has
the correct descriptive comment: move or add a comment describing the PostgreSQL
JDBC driver above the org.postgresql:postgresql dependency and keep the Neo4j
comment above the org.neo4j.driver:neo4j-java-driver dependency; ensure the
comment text clearly states "PostgreSQL JDBC driver" for the postgres dependency
and "Neo4j Java Driver for Knowledge Graph storage" for the neo4j dependency so
maintainers can unambiguously identify org.postgresql:postgresql and
org.neo4j.driver:neo4j-java-driver.

In `@src/main/java/sa/com/cloudsolutions/antikythera/examples/OpenAIService.java`:
- Around line 94-116: The code repeatedly calls
getConfigInt("initial_retry_count", 0) inside sendApiRequest(String payload, int
retryCount), which is redundant across recursive retries; either leave as-is (no
change required) or cache the value once and reuse it — e.g., read
initial_retry_count earlier (call getConfigInt in the caller or a constructor)
and pass it into sendApiRequest or store it in a private field so sendApiRequest
uses the cached initialRetryCount rather than calling getConfigInt on every
invocation.

In
`@src/test/java/sa/com/cloudsolutions/antikythera/examples/AnnotationFinderTest.java`:
- Around line 318-319: Add imports for java.util.List and java.util.HashSet at
the top of the file and replace the fully-qualified inline usages with the
simple types: change java.util.List<MethodDeclaration> methods =
classDecl.getMethods(); to List<MethodDeclaration> methods =
classDecl.getMethods(); and change Set<String> seen = new java.util.HashSet<>();
to Set<String> seen = new HashSet<>(); (also update the other occurrences
mentioned around the same area, e.g., the usages at lines flagged 354–355) so
the file consistently uses imports for these java.util types.
- Around line 386-392: The test currently asserts a locally constructed string
instead of exercising AnnotationFinder; replace the trivial "simpleOutput"
concatenation and assertEquals call with a call into AnnotationFinder that
produces simple-mode output (e.g., call
AnnotationFinder.buildSimpleMethodSignature(method) or the equivalent
simple-mode API on AnnotationFinder that returns
"com.example.TestClass#testMethod"), keeping the detailed-mode assertion that
uses AnnotationFinder.buildMethodSignature(method) intact.
- Around line 296-368: The tests reimplement simple-mode behavior inline instead
of exercising AnnotationFinder; update both tests to call a single
AnnotationFinder API that returns the simple-mode outputs (e.g., add or use a
method like AnnotationFinder.getSimpleModeOutputs(ClassOrInterfaceDeclaration)
or
AnnotationFinder.generateSimpleModeOutputs(CompilationUnit/ClassOrInterfaceDeclaration))
and assert against that result; if such a method does not exist, add a small API
on AnnotationFinder (name it getSimpleModeOutputs or generateSimpleModeOutputs)
that takes the class declaration (or compilation unit) and returns a Set<String>
of "ClassName#methodName" for methods with the Test annotation, then replace the
per-test forEach/hasAnnotation/string-building logic with calls to that method
and assert its size/contents.

In
`@src/test/java/sa/com/cloudsolutions/antikythera/examples/GeminiAIServiceConfigTest.java`:
- Around line 218-228: Add a focused unit test in GeminiAIServiceConfigTest to
cover keys present with null values: create a test method (e.g.,
testGetConfigString_NullValueInConfig) that does Map<String,Object> config = new
HashMap<>(); config.put("api_key", null); service.setConfig(config); then call
service.getConfigString("api_key", "default-key") and
assertEquals("default-key", result). This ensures getConfigString's behavior for
a null config entry is verified (similar style and assertions as the existing
testGetConfigString_NonStringConfigValue).

Comment thread pom.xml
Comment on lines +137 to +141
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.2</version>
</dependency>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

PostgreSQL JDBC driver latest version 2025 2026 security

💡 Result:

  • Latest PostgreSQL JDBC (pgjdbc) release (Feb 2026): 42.7.10 (listed as the current release; also published to Maven Central on Feb 11, 2026). [1], [2]

    • Maven coordinate: org.postgresql:postgresql:42.7.10 [2]
  • Key 2025 security item: CVE-2025-49146 (MITM risk when channelBinding=require could incorrectly allow non-channel-binding auth methods). Fixed in 42.7.7; upgrading to ≥ 42.7.7 mitigates this, so 42.7.10 is safe with respect to this issue. [3], [4]

Recommendation: if you’re on any 42.7.4–42.7.6, upgrade to 42.7.10 (or at minimum 42.7.7+). [3], [4]

Sources: [1] pgJDBC site “Latest Releases”, [2] Maven Repository versions list, [3] PostgreSQL.org security news post, [4] pgJDBC 42.7.7 changelog.


🌐 Web query:

testcontainers neo4j latest stable version

💡 Result:

For Testcontainers for Java (Neo4j module), the latest stable Maven Central release is:

  • org.testcontainers:testcontainers-neo4j:2.0.3 (published Dec 15, 2025) [1]

Note: there’s also the older/legacy artifact org.testcontainers:neo4j, whose latest release is 1.21.4 (published Dec 16, 2025) [2]

Sources: [1], [2]


🌐 Web query:

system-stubs-jupiter latest stable version uk.org.webcompere

💡 Result:

Latest stable version of uk.org.webcompere:system-stubs-jupiter is 2.1.8 (released 2025-04-05). [1], [2], [3]

Maven coordinate

<dependency>
  <groupId>uk.org.webcompere</groupId>
  <artifactId>system-stubs-jupiter</artifactId>
  <version>2.1.8</version>
</dependency>

🌐 Web query:

CVE PostgreSQL JDBC driver 42.7.2

💡 Result:

For PostgreSQL JDBC (pgjdbc) v42.7.2:

  • CVE fixed by 42.7.2: CVE-2024-1597 (SQL injection possible only when using the non-default connection property preferQueryMode=simple and a specific vulnerable SQL pattern). 42.7.2 is the patched release. (github.com)
  • If you are already on 42.7.2: you are not vulnerable to CVE-2024-1597 (assuming you’re actually running 42.7.2+). (nvd.nist.gov)
  • Related later pgjdbc CVE (not 42.7.2-specific): CVE-2025-49146 affects 42.7.4–42.7.6 and is fixed in 42.7.7 (channel binding “required” not enforced correctly). This is not reported as affecting 42.7.2. (postgresql.org)

Citations:


🌐 Web query:

CVE testcontainers-neo4j 1.20.4

💡 Result:

No CVE (or GitHub Security Advisory) is currently published specifically for the Java artifact org.testcontainers:neo4j (often referred to as “testcontainers-neo4j”) version 1.20.4—it’s mainly a thin wrapper for running the official Neo4j Docker image in tests. [1][2] Snyk also reports no direct vulnerabilities for the core org.testcontainers:testcontainers package (note: this does not cover vulnerabilities in transitive deps or the container image you run). [3]

What you do need to check for CVEs is the Neo4j Docker image tag you use with Neo4jContainer(...), because Neo4j publishes CVEs affecting certain Neo4j versions (e.g., CVE-2026-1622, CVE-2026-1337, etc.). [4]

Sources: [1] org.testcontainers:neo4j 1.20.4 on Maven Central index [2] Testcontainers Neo4j module docs (uses official Neo4j image) [3] Snyk page for org.testcontainers:testcontainers [4] Neo4j security advisories page.


🌐 Web query:

CVE system-stubs-jupiter 2.1.6

💡 Result:

uk.org.webcompere:system-stubs-jupiter:2.1.6 itself does not appear to have a CVE assigned; the CVEs typically reported against it are from its (test) dependency tree (notably AssertJ and Jetty). In Maven Repository, 2.1.6 is flagged due to dependency CVEs including CVE-2026-24400, CVE-2024-9823, CVE-2024-8184, CVE-2024-13009. [1]

Why 2.1.6 gets flagged (key vulnerable deps it brings in):

  • AssertJ Core 3.24.2 → CVE-2026-24400 (XXE in XML pretty formatter / isXmlEqualTo); fixed in AssertJ 3.27.7. [2][3]
  • Jetty 9.4.52.v20230823 (via Jetty server/servlets artifacts)
    • CVE-2024-9823 (Jetty DosFilter DoS / OOM); patched in Jetty 9.4.54+. [4][3]
    • CVE-2024-8184 (Jetty ThreadLimitHandler.getRemote() DoS / OOM); affected up to < 9.4.56 (so 9.4.56+ is outside the affected range). [5]
    • CVE-2024-13009 (gzip inflate error can lead to incorrect buffer release/data sharing); affected 9.4.0 to 9.4.56, patched in 9.4.57+. [6]

What to do

  • Prefer upgrading system-stubs-jupiter to a newer release (latest shown is 2.1.8, dated Apr 05, 2025). [7]
  • Additionally (or if you must stay on 2.1.6), override dependency versions to patched releases (e.g., assertj-core 3.27.7+ and Jetty 9.4.57+ / or newer supported Jetty line). [2][6][3]

Sources: [1] [2] [3] [4] [5] [6] [7]


Upgrade all three dependencies to latest stable versions; system-stubs-jupiter 2.1.6 introduces indirect CVEs.

  • PostgreSQL driver 42.7.2 is outdated; upgrade to 42.7.10 (latest, published Feb 2026). While 42.7.2 itself is not vulnerable to CVE-2024-1597 or CVE-2025-49146, the current version provides 8 patch releases of improvements.
  • testcontainers-neo4j 1.20.4 is outdated; upgrade to 2.0.3 (latest modern artifact, Dec 2025) or at minimum 1.21.4 (legacy artifact).
  • system-stubs-jupiter 2.1.6 introduces multiple indirect CVEs through transitive dependencies: CVE-2026-24400 in AssertJ 3.24.2 (XXE in XML formatter), CVE-2024-9823 in Jetty 9.4.52 (DoS/OOM), CVE-2024-8184 and CVE-2024-13009 in Jetty (DoS/buffer issues). Upgrade to 2.1.8 (released Apr 2025), or override AssertJ to ≥3.27.7 and Jetty to ≥9.4.57.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pom.xml` around lines 137 - 141, Update the three Maven dependencies shown by
replacing the artifact versions: bump artifactId "postgresql" from 42.7.2 to
42.7.10; upgrade "testcontainers-neo4j" to either 2.0.3 (preferred) or at
minimum 1.21.4; and update "system-stubs-jupiter" to 2.1.8 (or if you prefer to
keep 2.1.x, add explicit dependencyManagement or dependency overrides for
AssertJ >= 3.27.7 and Jetty >= 9.4.57 to mitigate the transitive CVEs). Ensure
the pom's dependency entries for artifactId postgresql, testcontainers-neo4j,
and system-stubs-jupiter are updated accordingly and run mvn dependency:tree to
verify the transitive versions were resolved as intended.

Comment thread src/main/java/sa/com/cloudsolutions/antikythera/examples/AbstractAIService.java Outdated
Comment on lines +571 to +604
protected static String extractJsonFromCodeBlocks(String response) {
// If no JSON found, try to find JSON in code blocks
String[] lines = response.split("\\n");
StringBuilder jsonBuilder = new StringBuilder();
boolean inCodeBlock = false;
boolean foundJson = false;
int braceDepth = 0;
int bracketDepth = 0;

for (String line : lines) {
if (line.trim().startsWith("```")) {
inCodeBlock = !inCodeBlock;
}
else if (inCodeBlock || line.trim().startsWith("{") || line.trim().startsWith("[") || foundJson) {
jsonBuilder.append(line).append("\n");
foundJson = true;

// Count braces and brackets to handle nested structures
for (char c : line.toCharArray()) {
if (c == '{') braceDepth++;
else if (c == '}') braceDepth--;
else if (c == '[') bracketDepth++;
else if (c == ']') bracketDepth--;
}

// Break when we've closed all opened braces/brackets
if (foundJson && braceDepth == 0 && bracketDepth == 0) {
break;
}
}
}

return jsonBuilder.toString().trim();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Depth-tracking approach for extractJsonFromCodeBlocks is a solid improvement — but the early-exit can miss JSON that doesn't start on the first captured line.

The brace/bracket depth tracking fixes the original truncation bug. However, once foundJson is set to true (line 586), the break condition on line 597 fires when both depths are zero. If the first captured line inside a code block contains no braces (e.g., a blank line or comment preceding the JSON), foundJson becomes true while depths remain at 0, causing an immediate break before the actual JSON is reached.

This is a narrow edge case since AI responses typically place JSON immediately after the code fence, but worth being aware of.

Proposed fix — only break after at least one brace/bracket has been opened
+        boolean hasOpenedStructure = false;
+
         for (String line : lines) {
             if (line.trim().startsWith("```")) {
                 inCodeBlock = !inCodeBlock;
             }
             else if (inCodeBlock || line.trim().startsWith("{") || line.trim().startsWith("[") || foundJson) {
                 jsonBuilder.append(line).append("\n");
                 foundJson = true;
                 
                 // Count braces and brackets to handle nested structures
                 for (char c : line.toCharArray()) {
-                    if (c == '{') braceDepth++;
+                    if (c == '{') { braceDepth++; hasOpenedStructure = true; }
                     else if (c == '}') braceDepth--;
-                    else if (c == '[') bracketDepth++;
+                    else if (c == '[') { bracketDepth++; hasOpenedStructure = true; }
                     else if (c == ']') bracketDepth--;
                 }
                 
                 // Break when we've closed all opened braces/brackets
-                if (foundJson && braceDepth == 0 && bracketDepth == 0) {
+                if (hasOpenedStructure && braceDepth == 0 && bracketDepth == 0) {
                     break;
                 }
             }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/sa/com/cloudsolutions/antikythera/examples/AbstractAIService.java`
around lines 571 - 604, The extractJsonFromCodeBlocks method can prematurely
break if foundJson is set before any opening brace/bracket is seen; add a
boolean flag (e.g., hasOpenedStructure = false) initialized alongside
braceDepth/bracketDepth, set to true whenever you increment braceDepth or
bracketDepth (when encountering '{' or '['), and change the break condition to
require hasOpenedStructure && braceDepth == 0 && bracketDepth == 0; update
references to foundJson, braceDepth, bracketDepth and the loop in
extractJsonFromCodeBlocks accordingly so the parser only exits after at least
one opening structure was seen and subsequently closed.

Comment on lines +72 to +88
@BeforeEach
void setUp() throws IOException {
MockitoAnnotations.openMocks(this);

openAIService = new OpenAIService();

// Create a test configuration
config = new HashMap<>();
config.put("api_key", "test-api-key");
config.put("timeout_seconds", 30);
config.put("track_usage", true);
config.put("cost_per_1k_tokens", 0.001);
config.put("initial_retry_count", 1);

// Configure the service
openAIService.configure(config);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

MockitoAnnotations.openMocks(this) return value is not closed — resource leak.

openMocks returns an AutoCloseable that must be closed after tests to release mock resources. The simplest fix is to switch to @ExtendWith(MockitoExtension.class) on the class, which handles the lifecycle automatically.

Proposed fix — use MockitoExtension
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
 class OpenAIServiceTest {

Then remove MockitoAnnotations.openMocks(this) from setUp():

     `@BeforeEach`
     void setUp() throws IOException {
-        MockitoAnnotations.openMocks(this);
-
         openAIService = new OpenAIService();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/sa/com/cloudsolutions/antikythera/examples/OpenAIServiceTest.java`
around lines 72 - 88, The test uses MockitoAnnotations.openMocks(this) in
setUp() which returns an AutoCloseable that is never closed; replace manual mock
init by annotating the test class (OpenAIServiceTest) with
`@ExtendWith`(MockitoExtension.class) and remove the
MockitoAnnotations.openMocks(this) call from the setUp() method so the
MockitoExtension manages mock lifecycle automatically (no need to call/close
AutoCloseable).

@sonarqubecloud

Copy link
Copy Markdown

@e4c5
e4c5 merged commit 0850498 into main Feb 17, 2026
2 checks passed
@e4c5
e4c5 deleted the euler branch February 17, 2026 00:58
This was referenced Mar 8, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Apr 18, 2026
Merged
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL This PR changes 1000+ lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants