Skip to content

feat: sub-project 1 Phase 4-6 — resolver pipeline wiring + 4 Java detector migrations#104

Merged
aksOps merged 16 commits intomainfrom
feat/sub-project-1-pipeline-wiring-and-detectors
Apr 28, 2026
Merged

feat: sub-project 1 Phase 4-6 — resolver pipeline wiring + 4 Java detector migrations#104
aksOps merged 16 commits intomainfrom
feat/sub-project-1-pipeline-wiring-and-detectors

Conversation

@aksOps
Copy link
Copy Markdown
Contributor

@aksOps aksOps commented Apr 28, 2026

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

  • Analyzer bootstraps ResolverRegistry exactly once per pipeline entry point (run / runBatchedIndex / runSmartIndex) and threads a Resolved onto every DetectorContext at all three detect call sites. Per-file ResolutionException + RuntimeException are swallowed and fall back to EmptyResolved.INSTANCE.
  • JavaSymbolResolver.resolve() lazy-parses raw source String content with a fresh symbol-solver-configured JavaParser per call — a small per-call allocation that lets Analyzer pass 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)

  • JpaEntityDetectorMAPS_TO edges between entities now carry target_fqn and Confidence.RESOLVED.
  • RepositoryDetector — Spring Data repo QUERIES edges plus the repo node carry the resolved entity FQN.
  • SpringRestDetector — endpoints emit a MAPS_TO edge to the @RequestBody DTO class with parameter_kind=request_body + parameter_name.
  • ClassHierarchyDetectorEXTENDS / IMPLEMENTS edges across classes / interfaces / enums stamp Confidence.RESOLVED + target_fqn. Refactor side-benefit: four duplicated edge blocks collapsed into a single addHierarchyEdge helper.

Phase 7 aggressive testing layers

  • Layer 3JavaSymbolResolverConcurrencyTest: virtual-thread fan-out, 200 files / 256 concurrent calls, garbage-input variant.
  • Layer 6JavaSymbolResolverDeterminismTest: same input → same FQN 25× in a row, two independent resolvers agree, rebootstrap is idempotent, deeper FQNs are stable.
  • Layer 1 extended (16 tests): deep generics, inner classes (static + non-static), records, sealed hierarchies, enums w/ abstract methods, default-method interfaces, abstract classes, annotations, same-name-different-package by import direction, JDK Optional/Stream/List, multi-source-root cross-references, wildcard imports, cyclic imports.
  • Layer 4 (3): 10K-line class, 1000 imports (most unresolvable), 10-deep generic nesting with @Timeout regression sentinels.
  • Layer 5 (5): unbalanced braces (strict-success → EmptyResolved), mis-tagged Kotlin / random bytes, mixed source root with .txt siblings, empty source root.
  • Layer 7E2EResolverPetclinicTest (env-gated by E2E_PETCLINIC_DIR): bootstrap < 10 s, > 50% files produce JavaResolved.
  • Layer 8JavaSymbolResolverRandomizedTest (1 test, 100 samples, fixed seed): hand-rolled randomized fallback (jqwik is EPL-2.0, not on dependencies.md's preferred-license list).
  • Layer 9mutation Maven profile: pitest-maven 1.18.0 (Apache-2.0). mvn -P mutation org.pitest:pitest-maven:mutationCoverage runs it. Non-gating per the plan.

Robustness fixes from a dual-agent (superpowers + codex) brainstorm

  • volatile on JavaSymbolResolver's solver / combined — closes the public-accessor visibility race the JLS Thread Start Rule doesn't cover.
  • Strict parse-success check in JavaSymbolResolver.resolve(String) — was silently emitting partial-CU edges on broken parses; now returns EmptyResolved on any JavaParser problem.
  • catch (StackOverflowError) in Analyzer.resolveFor — pathological generics no longer kill virtual-thread workers.
  • try-with-resources on Files.walk in JavaSourceRootDiscovery.containsJavaFile — fd leak fix.

Documentation close-out

  • CHANGELOG.md [Unreleased] entry under sub-project 1.
  • CLAUDE.md Gotchas: 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 mention javaparser-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-gated E2EResolverPetclinicTest)
  • Resolver concurrency stress test green
  • Determinism gate green
  • All 4 detector resolved/fallback/mixed tests green
  • CI: Java CI / CodeQL / Security (OSS-CLI) / Socket — will fire on this re-targeted PR

🤖 Generated with Claude Code

@aksOps aksOps enabled auto-merge (squash) April 28, 2026 05:35
aksOps and others added 16 commits April 28, 2026 05:36
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>
@aksOps aksOps force-pushed the feat/sub-project-1-pipeline-wiring-and-detectors branch from d1cef4e to 1470182 Compare April 28, 2026 05:37
@aksOps aksOps merged commit 0c1a4c8 into main Apr 28, 2026
13 checks passed
@aksOps aksOps deleted the feat/sub-project-1-pipeline-wiring-and-detectors branch April 28, 2026 05:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant