Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/superpowers/baselines/2026-04-17/BASELINE.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,9 @@ Ordered by severity. Each item cites the raw artifact it was derived from.
- **`GraphHealthIndicator` reports `OUT_OF_SERVICE` (503) even when the graph is loaded.** Discovered during the pipeline smoke-test fix. `/actuator/health` body: `{"groups":["liveness","readiness"],"status":"OUT_OF_SERVICE"}`. The server is fully functional (`/api/stats` returns real data) but the health indicator makes `/actuator/health` unusable as a readiness probe for orchestrators (K8s, Compose, CI). Fix in `src/main/java/io/github/randomcodespace/iq/health/GraphHealthIndicator.java`. Low for baseline use; High when we start Dockerizing or targeting K8s.
- **RESOLVED (2026-04-17, branch `phase-a/fix-graph-health`)**: Root cause was *not* in `GraphHealthIndicator` (which correctly returns UP when nodes>0). It was in `ServeCommand`: the CLI blocks on `Thread.currentThread().join()` inside Spring Boot's `CommandLineRunner.run()`, which prevents `ApplicationReadyEvent` from ever firing. Without that event, Spring's default readiness publisher never flips `ReadinessState` from `REFUSING_TRAFFIC` (503 `OUT_OF_SERVICE`) to `ACCEPTING_TRAFFIC` (200 `UP`). Fix: `ServeCommand` now explicitly publishes `AvailabilityChangeEvent` for `LivenessState.CORRECT` + `ReadinessState.ACCEPTING_TRAFFIC` before blocking, via a new `markReady()` method (unit-tested). Verified end-to-end: `health_http` is now 200 on both seeds (petclinic ready 13s, express ready 14s; status "UP"). Follow-up filed: `GraphBootstrapper`'s `@EventListener(ApplicationReadyEvent.class)` is effectively dead code for the same reason — only noticed because enrich always runs before serve in our pipeline, so the bootstrap fallback never actually needs to fire.

- **`GraphBootstrapper` dead listener** (Low severity, follow-up to the health fix). The H2→Neo4j bootstrap path was triggered via `@EventListener(ApplicationReadyEvent.class)`, which never fires while `ServeCommand` blocks as a `CommandLineRunner`.
- **RESOLVED (2026-04-17, branch `phase-a/fix-bootstrap-listener`)**: dropped the `@EventListener` annotation and instead invoke `GraphBootstrapper.bootstrapNeo4jFromCache()` explicitly from `ServeCommand.call()` before the status report. The method is idempotent (guards on Neo4j count>0 and on missing H2 cache file). Verified end-to-end: after running a full pipeline then wiping only `.code-iq/graph/` (Neo4j) while keeping `.code-iq/cache/` (H2), `serve` logs `Bootstrapped Neo4j with 634 nodes and 604 edges from H2 cache` and `/api/stats` returns real data (nodes=677, edges=604, files=65). Existing `GraphBootstrapperTest` cases still pass unchanged (they always called the method directly).

- **SpotBugs: 8 HIGH-priority findings (priority=1) + 1,484 at priority=2.** Total 1,492. HIGH findings must be triaged individually (read `raw/spotbugs.xml`). Noise-dominant rules (`NM_METHOD_NAMING_CONVENTION`=730, `SF_SWITCH_NO_DEFAULT`=448) should be filtered via a SpotBugs exclude file so real signal surfaces; real-concern patterns that deserve review now: `NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE` (26), `BC_UNCONFIRMED_CAST` (55), `UL_UNRELEASED_LOCK_EXCEPTION_PATH` (1), `WMI_WRONG_MAP_ITERATOR` (2), `ES_COMPARING_STRINGS_WITH_EQ` (2), `MT_CORRECTNESS` category (1).
- Raw: `raw/spotbugs.xml`, `raw/spotbugs-summary.json`.
- **RESOLVED (2026-04-17, branch `phase-a/fixups-spotbugs`)**: Added `spotbugs-exclude.xml` covering ANTLR-generated parsers and global noise rules (`NM_METHOD_NAMING_CONVENTION`, `SF_SWITCH_NO_DEFAULT`, `EI_EXPOSE_REP`/`EI_EXPOSE_REP2`, `MS_PKGPROTECT`/`MS_FINAL_PKGPROTECT`), wired via `pom.xml`. Fixed all 8 priority-1 findings in codeiq code (UTF-8 in `Analyzer.getGitHead`, narrowed catch in `IndexCommand`, dead-store removed in `PluginsCommand`, `.equals()` in `AntlrParserFactory` + `CSharpPreprocessorParserBase`, try-finally unlock in `AnalysisCache.removeFile`, merged duplicate branches in `CodeIqApplication`, removed dead `BundleCommand.writeEntry` overload, `entrySet()` iteration in `PluginsCommand` + `GitLabCiDetector`, narrowed `VersionCommand` catch). **Final: 1,492 → 38 (-97.5%); priority-1: 8 → 0.** Remaining 38 are priority-2 STYLE/BAD_PRACTICE; no CORRECTNESS/MT_CORRECTNESS/SECURITY left. Next-pass candidates: 26 `NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE`. Post-triage summary: `raw/spotbugs-summary-after-triage.json`.
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/io/github/randomcodespace/iq/cli/ServeCommand.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.github.randomcodespace.iq.cli;

import io.github.randomcodespace.iq.config.CodeIqConfig;
import io.github.randomcodespace.iq.config.GraphBootstrapper;
import io.github.randomcodespace.iq.graph.GraphStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -63,6 +64,10 @@
@Autowired
private ApplicationEventPublisher events;

// Optional: only present in the "serving" profile (same conditions as the bean).
@Autowired(required = false)

Check warning on line 68 in src/main/java/io/github/randomcodespace/iq/cli/ServeCommand.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this field injection and use constructor injection instead.

See more on https://sonarcloud.io/project/issues?id=RandomCodeSpace_code-iq&issues=AZ2bif1W4qOadzuIGmYa&open=AZ2bif1W4qOadzuIGmYa&pullRequest=47
private GraphBootstrapper graphBootstrapper;

@Override
public Integer call() {
Path root = path.toAbsolutePath().normalize();
Expand All @@ -72,6 +77,15 @@
}
NumberFormat nf = NumberFormat.getIntegerInstance(Locale.US);

// Bootstrap Neo4j from the H2 analysis cache if Neo4j is empty. This is
// a no-op when enrich has already run (guarded internally by a count>0
// check) and when the H2 cache file is missing. Must happen before the
// status report below so the advertised node/edge counts are truthful.
// See GraphBootstrapper javadoc for why this is not an @EventListener.
if (graphBootstrapper != null) {
graphBootstrapper.bootstrapNeo4jFromCache();
}

// Report Neo4j graph status
if (graphStore != null) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.annotation.Profile;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import java.nio.file.Files;
Expand All @@ -25,7 +23,13 @@
* {@code serve} (which needs Neo4j for graph traversal queries like shortest path,
* cycles, impact trace, ego graph, and neighbors).
* <p>
* Runs after the Spring context is fully ready, so Neo4j and GraphStore are available.
* Invoked explicitly by {@link io.github.randomcodespace.iq.cli.ServeCommand}
* before the server reports "Server started", so the advertised node/edge counts
* reflect the bootstrapped state. Previously triggered via
* {@code @EventListener(ApplicationReadyEvent.class)}, but that event never fires
* because {@code ServeCommand.call()} blocks (as a {@code CommandLineRunner}) and
* Spring's ready-event publication waits for runners to return.
* <p>
* Only active in the "serving" profile when GraphStore is present.
*/
@Component
Expand All @@ -43,7 +47,6 @@ public GraphBootstrapper(GraphStore graphStore, CodeIqConfig config) {
this.config = config;
}

@EventListener(ApplicationReadyEvent.class)
public void bootstrapNeo4jFromCache() {
// Skip bootstrap in read-only mode (no writes allowed)
if (config.isReadOnly()) {
Expand Down
Loading