Skip to content

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

Closed
aksOps wants to merge 16 commits intofeat/sub-project-1-resolver-spi-and-java-pilotfrom
feat/sub-project-1-pipeline-wiring-and-detectors
Closed

feat: sub-project 1 Phase 4-6 — resolver pipeline wiring + 4 Java detector migrations#102
aksOps wants to merge 16 commits intofeat/sub-project-1-resolver-spi-and-java-pilotfrom
feat/sub-project-1-pipeline-wiring-and-detectors

Conversation

@aksOps
Copy link
Copy Markdown
Contributor

@aksOps aksOps commented Apr 28, 2026

Summary

Stacked on top of #101. Picks up where the SPI scaffolding (Phase 1-3) left off and ships the rest of sub-project 1's resolver-and-Java-pilot work:

  • Phase 4 — pipeline wiring: Analyzer bootstraps ResolverRegistry once per pipeline entry point and threads a Resolved onto every DetectorContext. Per-resolver / per-file failures fall back to EmptyResolved.INSTANCE so one blow-up cannot disrupt the pass.
  • Resolver lazy-parse: JavaSymbolResolver.resolve() accepts raw String content (the orchestrator's structured parser doesn't cover Java) and parses it with a fresh symbol-solver-attached JavaParser per call. This is the bridge that lets ctx.resolved() carry JavaResolved for Java files.
  • Phase 6 — 4 Java detector migrations (additive — every existing detector test passes unchanged):
    • JpaEntityDetector@OneToMany / @ManyToOne MAPS_TO edges get target_fqn + RESOLVED.
    • RepositoryDetector — Spring Data JpaRepository<T, ID> QUERIES edges + repo node get the entity FQN.
    • SpringRestDetector — endpoints emit RESOLVED MAPS_TO edges to @RequestBody DTO classes with parameter_kind / parameter_name props.
    • ClassHierarchyDetectorEXTENDS / IMPLEMENTS edges across classes / interfaces / enums stamp RESOLVED + target_fqn when the parent type resolves. Side-benefit: 4 duplicated edge-emission blocks collapse into a single addHierarchyEdge helper.

Plan: docs/plans/2026-04-27-sub-project-1-resolver-spi-and-java-pilot.md (Phases 4 + 6 — using the actual detectors that exist in this repo, not the plan's hypothetical names).

Spec

docs/specs/2026-04-27-resolver-spi-and-java-pilot-design.md

Backward compatibility

  • Every existing detector test passes unchanged (29 JpaEntity, 30 ClassHierarchy, 27 SpringRest extended cases, plus the rest of the suite).
  • Analyzer's 6-arg backward-compat constructor still exists; defaults the new ResolverRegistry parameter to an empty registry — every ctx.resolved() reads back as Optional.of(EmptyResolved.INSTANCE), same observable behaviour as before.
  • When no resolver is registered or JavaSymbolResolver.bootstrap fails, every detector falls back to its original simple-name placeholder edge with the base-class default confidence.

Test plan

  • Full mvn test green: 3585 tests, 0 failures, 31 skipped (E2E petclinic — same as main).
  • 8 new analyzer wiring tests (AnalyzerResolverWiringTest) covering bootstrap-once, normalised-path, per-file resolverFor, JavaResolved carried through to detectors, legacy-ctor compatibility.
  • 4 new JavaSymbolResolverTest cases for the lazy-parse path (valid source, junk input, empty source, non-string-non-CU AST).
  • 18 new resolved-mode detector tests (5+4+4+5 across the 4 migrations) — every migration ships with the plan's three-mode coverage (resolved / fallback / mixed).
  • No regression on existing detector tests.

What's NOT in this PR

The follow-on Phases 7-8 from the plan are intentionally deferred:

  • Aggressive testing layers 3-9 (concurrency stress, pathological input, adversarial, jqwik property-based, PIT mutation testing). Layers 1-2 (unit + integration) are complete via the per-migration test suites above.
  • Configuration keys (intelligence.symbol_resolution.java.*).
  • Config docs + CLAUDE.md / PROJECT_SUMMARY.md gotchas updates.
  • KafkaListener migration (would require static-field constant resolution — meaningfully more surface area than the FQN-resolution shape used by the four migrated detectors).

These get their own follow-up work; the four migrations here demonstrate the value (RESOLVED-tier edges across the four highest-leverage Java edge types) while keeping the diff reviewable.

🤖 Generated with Claude Code

aksOps and others added 16 commits April 27, 2026 23:42
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 deleted the branch feat/sub-project-1-resolver-spi-and-java-pilot April 28, 2026 05:20
@aksOps aksOps closed this Apr 28, 2026
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