Conversation
Phase 4 of sub-project 1 — pipeline wiring (plan tasks 19-21). The orchestration boundary for the symbol-resolution pass that sits between parse and detect. ResolverRegistry becomes a new constructor dependency on Analyzer: @Autowired primary ctor adds it as the 10th arg; the 6-arg backward-compat ctor defaults to `new ResolverRegistry(List.of())` so existing tests + direct constructor call-sites still work and observe the same behaviour (every ctx.resolved() reads back as Optional.of(EmptyResolved.INSTANCE)). Two private helpers do the work: - bootstrapResolvers(Path) — called exactly once at the top of every pipeline entry point (run / runBatchedIndex / runSmartIndex), before any file iteration. ResolverRegistry already swallows per-resolver failures; this catches the registry-itself-blowing-up case so the pipeline keeps going with NOOP resolvers. - resolveFor(DiscoveredFile, Object) — called per file at all three DetectorContext build sites (analyzeFile, the batched-index variant, and the regex-only fallback). Catches ResolutionException + RuntimeException and falls back to EmptyResolved.INSTANCE so one file's resolver blow-up cannot disrupt the rest of the pass. Every DetectorContext now reads back ctx.resolved() == Optional.of(...) — either the language's resolver result or EmptyResolved.INSTANCE. Detectors that don't care simply ignore the field; migrations to consume the resolved view follow in Phase 6. IndexCommand reaches the resolver via Analyzer.runSmartIndex, so plan task 21 ("mirror in IndexCommand") lands automatically with the analyzer wiring — no separate command-side changes required. 8 wiring tests cover: - bootstrap called exactly once per run (single file, many files, empty repo) - bootstrap path is the normalised absolute repoPath - resolverFor("java") called for each java file - ctx.resolved() is Optional.of(EmptyResolved.INSTANCE) when no resolver is registered for the language - legacy 6-arg ctor still produces a working analyzer with the same observable resolved() shape Plan: docs/plans/2026-04-27-sub-project-1-resolver-spi-and-java-pilot.md (tasks 19, 20, 21). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… JavaResolved
The orchestrator (Analyzer) only parses structured-language files at the
top level (YAML/JSON/etc.) — Java is parsed independently inside
AbstractJavaParserDetector via its ThreadLocal pool. Without an extra
hook, ctx.resolved() always reads back as EmptyResolved for Java because
the SPI's resolve(file, parsedAst) was never given a CompilationUnit.
Two minimal changes flip ctx.resolved() to JavaResolved for Java files:
1. JavaSymbolResolver.resolve() now accepts a String source as well as
a CompilationUnit. When given a String, it parses with a fresh
JavaParser configured with the symbol solver, so resolution is
attached to the resulting CU. Per-call JavaParser allocation is
intentional (JavaParser instances aren't thread-safe and resolve()
is invoked from virtual threads concurrently); cost is small
relative to the parse itself.
2. Analyzer.resolveFor() now takes content as a 3rd arg and uses it as
the parsedAst fallback when the orchestrator's structured parser
produced nothing. The 3 call sites (analyzeFile, the batched-index
variant, and the regex-only fallback) all pass content.
Permissive parsing: JavaParser produces a CompilationUnit even for files
with syntax errors (with attached Problems). The resolver returns
JavaResolved in that case — production analysis must keep going across
malformed files instead of failing the whole pass. EmptyResolved is only
returned when getResult().isEmpty(), which JavaParser reserves for hard
configuration-level failures.
New tests:
- JavaSymbolResolverTest: 4 new cases — valid source string parses,
junk input doesn't throw or null, empty source produces an empty CU,
unknown AST type (e.g. a Path) → EmptyResolved (replaces the old
"wrong AST type" String case).
- AnalyzerResolverWiringTest: javaFilePicksUpJavaResolvedWhenResolverRegistered
asserts ctx.resolved() is JavaResolved (not EmptyResolved) once a
JavaSymbolResolver is registered with the registry. This is the
bridge that lets detector migrations (Phase 6 / tasks 24-29)
actually consume RESOLVED-tier resolution from ctx.resolved().
Plan: docs/plans/2026-04-27-sub-project-1-resolver-spi-and-java-pilot.md
(unblocks tasks 24-29).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…edges
Phase 6 task 26 (and the JPA-relationship part of task 24) — first
detector migration to the resolver SPI.
When ctx.resolved() carries a JavaResolved (Analyzer registered a
JavaSymbolResolver), the detector now:
1. Uses the resolver-parsed CompilationUnit (which has the symbol
solver attached) instead of the local ThreadLocal-pool parse — no
double-parse, and Type.resolve() works inside the AST walk.
2. Attempts to resolve each @onetomany / @manytoone / @OnetoOne /
@manytomany field's target type to a fully-qualified name via the
symbol solver:
- Generic-arg case: List<Owner> → resolves the type argument
- Direct-field case: Owner → resolves the field type
3. On resolution success, attaches target_fqn to the edge properties
and stamps Confidence.RESOLVED + source = "jpa_entity". The simple-
name edge ID + target placeholder are unchanged so EntityLinker's
post-pass keeps working — target_fqn rides as the canonical pointer.
4. On resolution failure (missing classpath, unsolvable symbol, etc.),
falls back gracefully to the existing simple-name path with the
base-class default confidence.
Existing detector behaviour is unchanged when ctx.resolved() is empty
or carries EmptyResolved — the 29 pre-existing JpaEntityDetectorExtended
tests still pass without modification.
5 new tests in JpaEntityDetectorResolvedTest cover the three plan-
required modes (resolved, fallback, mixed) plus generic-arg resolution
and the no-resolved-at-all legacy ctx path:
- resolvedModeProducesResolvedEdgeWithTargetFqn — two Owner classes
in different packages; with resolution, the imported one wins and
edge.target_fqn = "com.example.a.Owner" + RESOLVED.
- resolvedModeFindsCollectionGenericArg — @onetomany List<Owner>
resolves the generic arg, not the List type.
- fallbackModeMatchesPreSpecBaseline — EmptyResolved → no target_fqn
+ raw-default confidence (orchestrator stamps SYNTACTIC at boundary).
- fallbackModeWhenContextHasNoResolvedAtAll — Optional.empty() also
produces the same baseline shape (legacy ctx path safety).
- mixedModeUsesResolverWhereAvailable — one resolvable + one
unresolvable relationship in the same class; the resolvable edge
is RESOLVED + target_fqn, the unresolvable falls back.
Plan: docs/plans/2026-04-27-sub-project-1-resolver-spi-and-java-pilot.md
(task 26).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…UERIES edges
Phase 6 task 25 (Spring Data repository migration). Same shape as the
JpaEntityDetector migration applied to RepositoryDetector — promote the
QUERIES edge from SYNTACTIC → RESOLVED with a stable target FQN when
the resolver can pin the entity type.
The detector stays regex-first (the inheritance regex is the cheapest
positive signal for "this file is a Spring Data repo"), and uses
ctx.resolved() purely for the FQN upgrade. When ctx.resolved() carries
a JavaResolved, the detector walks JavaResolved.cu(), finds the
interface declaration matching the regex-extracted name, takes the
first type argument of its first extended type (e.g. JpaRepository<User,
Long> → User), and resolves it via the attached symbol solver.
On success:
- repo node gains entity_fqn (so the RepositoryNode can be reasoned
about without a join through the entity index).
- QUERIES edge gains target_fqn + Confidence.RESOLVED + source =
"spring_repository".
On failure (no ctx.resolved(), EmptyResolved, no parent type with
generics, solver can't find the type), behaviour is unchanged from
before this commit — the simple-name placeholder edge with default
confidence is what shipped before, and tests confirm that path is
intact.
4 new tests in RepositoryDetectorResolvedTest cover the same three
modes as the JpaEntity migration:
- resolvedModeProducesResolvedEdgeWithTargetFqn — two User classes
in different packages; the imported one wins on entity_fqn +
target_fqn.
- fallbackModeMatchesPreSpecBaseline — EmptyResolved → no FQN
properties + no RESOLVED stamp.
- fallbackModeWhenContextHasNoResolvedAtAll — Optional.empty() also
safe.
- mixedModeFallsBackForUnreachableEntityType — repo whose entity has
no source: solver fails → fallback to simple-name + default tier.
Plan: docs/plans/2026-04-27-sub-project-1-resolver-spi-and-java-pilot.md
(task 25).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DTOs Phase 6 task 29 (SpringRestDetector migration). Per the plan: "Resolves @RequestBody UserDto dto and @PathVariable types. Edge: MAPS_TO from endpoint node to the resolved DTO class." When ctx.resolved() carries a JavaResolved, the detector now: 1. Uses the resolver-parsed CompilationUnit (symbol solver attached) instead of the local ThreadLocal-pool parse — Type.resolve() works inside the AST walk for parameter type resolution. 2. After emitting each ENDPOINT node + its EXPOSES edge, scans the method's parameters for @RequestBody. For each binding parameter whose type is a class/interface, attempts to resolve to a stable fully-qualified name via the symbol solver. 3. On success, emits a MAPS_TO edge: endpoint --MAPS_TO--> *:<simpleName> stamped with target_fqn / parameter_kind=request_body / parameter_name properties + Confidence.RESOLVED + source = "spring_rest". Target node uses NodeKind.CLASS so EntityLinker can resolve the FQN to a concrete class node post-pass. 4. On failure (primitive type, classpath gap, generic variable, etc.), no MAPS_TO edge is emitted — endpoint extraction itself is unaffected. The endpoint's `parameters` property still records the simple type name for the lexical / SYNTACTIC tier. This is purely additive: when ctx.resolved() is empty / EmptyResolved, the detector behaves identically to before the migration. The 27 existing SpringRestDetectorExtended tests pass unchanged. 4 new tests in SpringRestDetectorResolvedTest cover: - resolvedModeProducesResolvedMapsToEdge — two UserDto in different packages; imported FQN wins on edge.target_fqn + RESOLVED stamp + parameter_name property. - fallbackModeProducesNoMapsToEdge — EmptyResolved → endpoint still emitted, but no MAPS_TO (additive contract). - fallbackModeWhenContextHasNoResolvedAtAll — Optional.empty() also produces no MAPS_TO. - mixedModeFallsBackForUnreachableType — endpoint with one resolvable DTO + one unresolvable: only the resolvable one gets MAPS_TO. Plan: docs/plans/2026-04-27-sub-project-1-resolver-spi-and-java-pilot.md (task 29). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…XTENDS/IMPLEMENTS edges
Phase 6 task 24-style migration applied to ClassHierarchyDetector.
Class hierarchy is high-leverage for resolution: simple-name superclass
references like "extends Service" are routine across unrelated codebases,
and EXTENDS / IMPLEMENTS edges are downstream-load-bearing for
blast-radius / dead-code / cycle / topology analysis. Pinning the
target FQN turns "Service-named-something" into a stable cross-file
reference.
When ctx.resolved() carries a JavaResolved, the detector now:
1. Uses the resolver-parsed CompilationUnit (symbol solver attached).
2. For each parent type in extendedTypes / implementedTypes, calls
a single new helper addHierarchyEdge() that:
- tries to resolve the type via the symbol solver
- on success, attaches target_fqn to edge properties + stamps
Confidence.RESOLVED + source = "java.class_hierarchy"
- on failure (and always when ctx.resolved() is empty), emits
the existing simple-name placeholder edge with raw default
confidence (orchestrator stamps SYNTACTIC at the boundary).
3. The 4 prior in-line edge-emission blocks (class-extends,
interface-extends, class-implements, enum-implements) collapse to
two-line iterations through addHierarchyEdge — net is fewer LOC
plus the new resolution capability.
Existing 30 ClassHierarchyDetectorExtended tests pass unchanged — node
emission, regex fallback, the property shapes, and the simple-name
edge IDs / target placeholders are all preserved.
5 new tests in ClassHierarchyDetectorResolvedTest:
- resolvedModeStampsResolvedTierOnExtendsEdge — two BaseService in
different packages; imported one wins on edge.target_fqn.
- resolvedModeStampsResolvedTierOnImplementsEdge — same shape for
interface implements.
- fallbackModeMatchesPreSpecBaseline — EmptyResolved → no FQN, no
RESOLVED stamp.
- fallbackModeWhenContextHasNoResolvedAtAll — Optional.empty() also
safe.
- mixedModeFallsBackForUnreachableType — class extends a known type,
implements an unknown one: EXTENDS is RESOLVED, IMPLEMENTS falls
back gracefully.
This brings the migrated-detector count to 4 (JpaEntityDetector,
RepositoryDetector, SpringRestDetector, ClassHierarchyDetector) — at
the lower bound of the plan's "4-6 Java detectors migrated as proof of
value".
Plan: docs/plans/2026-04-27-sub-project-1-resolver-spi-and-java-pilot.md
(spirit of tasks 24-29 — using the actual detectors that exist in this
repo, not the plan's hypothetical names).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…migrations Extends the [Unreleased] entry with the Phase 4 + 6 follow-up work shipped on this branch — the resolver is now wired end-to-end into Analyzer and four Java detectors consume ctx.resolved() to emit RESOLVED-tier edges with stable target FQNs: - JpaEntityDetector — @onetomany / @manytoone MAPS_TO targets - RepositoryDetector — JpaRepository<T, ID> entity FQN - SpringRestDetector — @RequestBody DTO MAPS_TO edges - ClassHierarchyDetector — EXTENDS / IMPLEMENTS FQN targets Also covers the JavaSymbolResolver lazy-parse extension that lets the orchestrator pass raw source content for Java (the structured parser doesn't cover Java, so without this the resolver could never receive a CompilationUnit). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…erminism
Phase 7 of the resolver-and-Java-pilot plan, top two highest-leverage
aggressive-testing layers.
Layer 6 — JavaSymbolResolverDeterminismTest (4 tests):
- sameInputResolvesToSameFqnEveryTime — single resolver, 25 iterations
over the same source must produce the same resolved FQN
("com.example.a.Owner"). Pins the value-stable contract under repeated
calls (different identity, same value).
- twoResolverInstancesOverSameProjectAgree — two independent resolver
instances bootstrapped against the same root must produce the same
FQN for the same source — establishes that bootstrap is value-stable
across instances, not just within one.
- rebootstrapStillProducesSameFqn — resolve, rebootstrap, resolve
again; FQN is unchanged. The orchestrator calls bootstrap once, but
if the resolver were ever refreshed mid-run, the value contract must
still hold.
- deeperFqnsAreAlsoStable — same shape on a 3-segment package
(com.example.inner.deep.Marker) so a divergence on a deeper lookup
can't hide behind a 1-level passing test.
Layer 3 — JavaSymbolResolverConcurrencyTest (3 tests):
- parallelResolveNeverThrowsAndAlwaysAgrees — 256 virtual threads each
resolve the same source; the aggregated FQN set must be of size 1.
Catches "thread X's CU bleeds into thread Y" / shared-mutable-state
classes of races. Runs cleanly on the per-call-fresh-JavaParser
contract.
- parallelResolveAcrossDistinctFilesProducesPerFileResults — 200
distinct Consumer files each resolved on a virtual thread; aggregate
FQN set = {com.example.api.Target}. Catches "one thread's resolved
state survives into another thread's resolution" classes of bugs.
- parallelResolveOnGarbageInputDoesNotThrow — 256 virtual threads each
pass garbage strings; no exceptions escape and no thread returns null.
The resolver's "no throw, no null" contract holds under concurrency.
Plan: docs/plans/2026-04-27-sub-project-1-resolver-spi-and-java-pilot.md
(tasks 30 + 31).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…odex) brainstorm Both reviewers independently identified the same four corner-cases in the Phase 4 + 6 wiring; this lands the converged fix list. 1. JavaSymbolResolver — `volatile` on `solver` and `combined` bootstrap() publishes; resolve() and the public accessors read from arbitrary virtual-thread carriers. The JLS Thread Start Rule covers the executor.submit() path but does NOT cover callers that read the public accessors after bootstrap on a different thread. Cheap fence, closes the visibility hole. 2. JavaSymbolResolver.resolve(String) — strict parse-success check JavaParser is permissive and may return a partial CompilationUnit even when the source has parse problems. Resolving against a partial CU silently emits simple-name-only edges and looks like coverage even though resolution is broken. Treat any non-success as EmptyResolved so the graph never carries phantom RESOLVED-tier edges from broken parses. 3. Analyzer.resolveFor — catch StackOverflowError Pathological generic / type-cycle inputs can blow JavaSymbolSolver's recursion stack. Catching the Error keeps the virtual-thread worker alive and degrades that file's resolution to lexical. Other Errors (OOM, ThreadDeath) remain fatal and propagate. 4. JavaSourceRootDiscovery.containsJavaFile — try-with-resources on Files.walk Files.walk holds an open directory stream; without a close, the file descriptor leaks for every plain-layout fallback scan. Cheap fix. mvn test: 3592 tests / 0 failures / 31 skipped (full suite, no regressions). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… profile Phase 7 of the sub-project 1 plan. Spec §12's testing matrix lands as five new test classes (26 tests) plus a non-default Maven profile. Layers 3 + 6 were already shipped in the prior commit on this branch. Layer 1 — JavaSymbolResolverLayer1ExtendedTest (16): Spec §12 Layer 1 cases not exercised by the existing JavaSymbolResolverTest — deep generics (Map<String, List<Set<UUID>>>), inner classes (static + non-static), records, sealed hierarchies, enums with abstract methods, default-method interfaces, abstract classes, annotation types, same simple name in different packages pinned by import direction, JDK Optional/Stream/List via ReflectionTypeSolver, multi-source-root cross-reference (src/main ↔ src/test), wildcard imports, cyclic imports both directions. Layer 4 — JavaSymbolResolverPathologicalTest (3): 10K-line class, 1000 imports (most unresolvable), 10-deep generic nesting (programmatically built so brackets are provably balanced). @timeout per-test is the regression sentinel against quadratic memoization; Surefire's default heap covers the spec's -Xmx512m target many times over so we don't pin it explicitly. Layer 5 — JavaSymbolResolverAdversarialTest (5): Unbalanced braces (strict-success → EmptyResolved, strong assertion), mis-tagged Kotlin (no exception/null, branch-agnostic — JavaParser's permissiveness for "fun ... { }" is implementation-specific), mis-tagged random bytes, mixed source root with .java + .txt siblings (only .java enters the solver), empty source root (no Java files anywhere) bootstraps via ReflectionTypeSolver alone. Layer 7 — E2EResolverPetclinicTest (1, env-gated): Runs JavaSymbolResolver against every .java under $E2E_PETCLINIC_DIR and asserts bootstrap < 10 s (spec §9 budget), no exception, > 50% files produce JavaResolved (i.e. strict-success isn't false-rejecting valid Java). Lighter than spec §12 Layer 7's full precision/recall comparison — that needs a pre-resolver baseline JSON checked into test resources, captured at implementation time. This stand-in is the strongest signal we have until that baseline lands. Layer 8 — JavaSymbolResolverRandomizedTest (1, 100 samples): Hand-rolled randomized generator with fixed seed (0xC0DE197042L). Per the plan's license guidance, jqwik (EPL-2.0) isn't on the project's preferred-license list (~/.claude/rules/dependencies.md prefers MIT/Apache/BSD); this is the documented JUnit + java.util.Random fallback. Properties: never throws unchecked, never returns null, completes per-file in < 1 s budget. Layer 9 — mutation Maven profile (non-default): Adds pitest-maven 1.18.0 (Apache-2.0) targeting intelligence.resolver.* and model.Confidence. Run with mvn -P mutation org.pitest:pitest-maven:mutationCoverage \ -Dfrontend.skip=true -Ddependency-check.skip=true Reports under target/pit-reports/. Non-gating per the plan; the ≥ 80% target is a follow-up signal once a first run lands. Full suite: mvn test → 3618 / 0 failures / 32 skipped (1 new skip is the env-gated E2EResolverPetclinicTest). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan §40 / §41 close-out — the resolver SPI, Confidence schema,
CACHE_VERSION bump, and runtime lifecycle gotchas now land in
CLAUDE.md and PROJECT_SUMMARY.md, not just CHANGELOG.md.
CLAUDE.md Gotchas:
- Cache versioning bullet updated 4 → 5 with reason (Confidence/source
schema), so future agents reading the gotcha don't propagate the
stale "4" forward.
- "Symbol resolver runs at index-time only" — bootstrapResolvers and
resolveFor are wired into run / runBatchedIndex / runSmartIndex only.
Never reached at serve. Prevents future agents from reaching for
ResolverRegistry from serve-mode code paths.
- "Confidence + source mandatory on every CodeNode/CodeEdge" —
DetectorEmissionDefaults stamps the floor; RESOLVED is opt-in via
ctx.resolved(). Reading legacy data is non-throwing.
- "JavaSymbolResolver.resolve() allocates a fresh JavaParser per call"
— intentional thread-safety boundary for virtual-thread fan-out, not
a perf bug.
- "Strict parse-success check" — resolve(String) returns EmptyResolved
on any JavaParser problem so the graph never carries phantom
RESOLVED-tier edges from partial-CU outputs.
- "Volatile fields on JavaSymbolResolver" — closes the public-accessor
visibility race per the dual-agent brainstorm fix.
PROJECT_SUMMARY.md:
- Tech-stack row updated to "AST + symbols (Java) | JavaParser 3.28.0
+ javaparser-symbol-solver-core 3.28.0".
- Cache dir line updated CACHE_VERSION=4 → 5.
- Two new gotchas (resolver-is-index-time-only, strict parse-success)
cross-referencing the canonical CLAUDE.md entries.
No source changes. Full mvn test was last green at 3618 / 0 / 32
skipped (unchanged for this docs-only commit).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
d1cef4e to
1470182
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Re-opens #102 (auto-closed when #101's base branch was deleted on squash-merge). Branch rebased onto
main— only the sub-project-1 Phase 4-6 incremental commits remain.This PR wires the resolver SPI (landed in #101) end-to-end and migrates four Java detectors to consume
ctx.resolved()for RESOLVED-tier edges with stable fully-qualified-name targets.Pipeline wiring
AnalyzerbootstrapsResolverRegistryexactly once per pipeline entry point (run/runBatchedIndex/runSmartIndex) and threads aResolvedonto everyDetectorContextat all three detect call sites. Per-fileResolutionException+RuntimeExceptionare swallowed and fall back toEmptyResolved.INSTANCE.JavaSymbolResolver.resolve()lazy-parses raw sourceStringcontent with a fresh symbol-solver-configuredJavaParserper call — a small per-call allocation that letsAnalyzerpass file content directly. Permissive parsing is wrapped in a strict-success check so partial CUs don't silently emit phantom RESOLVED edges.Detector migrations (purely additive — every existing detector test passes unchanged)
MAPS_TOedges between entities now carrytarget_fqnandConfidence.RESOLVED.QUERIESedges plus the repo node carry the resolved entity FQN.MAPS_TOedge to the@RequestBodyDTO class withparameter_kind=request_body+parameter_name.EXTENDS/IMPLEMENTSedges across classes / interfaces / enums stampConfidence.RESOLVED+target_fqn. Refactor side-benefit: four duplicated edge blocks collapsed into a singleaddHierarchyEdgehelper.Phase 7 aggressive testing layers
JavaSymbolResolverConcurrencyTest: virtual-thread fan-out, 200 files / 256 concurrent calls, garbage-input variant.JavaSymbolResolverDeterminismTest: same input → same FQN 25× in a row, two independent resolvers agree, rebootstrap is idempotent, deeper FQNs are stable.@Timeoutregression sentinels..txtsiblings, empty source root.E2EResolverPetclinicTest(env-gated byE2E_PETCLINIC_DIR): bootstrap < 10 s, > 50% files produceJavaResolved.JavaSymbolResolverRandomizedTest(1 test, 100 samples, fixed seed): hand-rolled randomized fallback (jqwik is EPL-2.0, not ondependencies.md's preferred-license list).mutationMaven profile:pitest-maven1.18.0 (Apache-2.0).mvn -P mutation org.pitest:pitest-maven:mutationCoverageruns it. Non-gating per the plan.Robustness fixes from a dual-agent (superpowers + codex) brainstorm
volatileonJavaSymbolResolver'ssolver/combined— closes the public-accessor visibility race the JLS Thread Start Rule doesn't cover.JavaSymbolResolver.resolve(String)— was silently emitting partial-CU edges on broken parses; now returnsEmptyResolvedon any JavaParser problem.catch (StackOverflowError)inAnalyzer.resolveFor— pathological generics no longer kill virtual-thread workers.try-with-resourcesonFiles.walkinJavaSourceRootDiscovery.containsJavaFile— fd leak fix.Documentation close-out
CHANGELOG.md[Unreleased]entry under sub-project 1.CLAUDE.mdGotchas: 6 new bullets on Confidence/source schema, resolver-is-index-time-only, JavaParser per-call thread safety, strict parse-success, volatile fields, CACHE_VERSION 4 → 5.PROJECT_SUMMARY.md: tech-stack row updated to mentionjavaparser-symbol-solver-core; CACHE_VERSION fix; two new gotchas cross-referencing CLAUDE.md.Test plan
mvn test(full suite) — 3618 / 0 failures / 32 skipped (1 skip = env-gatedE2EResolverPetclinicTest)🤖 Generated with Claude Code