Skip to content

docs: design exploration for a Spring-aware code graph - #9

Merged
stephane-segning merged 1 commit into
mainfrom
docs/spring-aware-graph
Aug 8, 2026
Merged

docs: design exploration for a Spring-aware code graph#9
stephane-segning merged 1 commit into
mainfrom
docs/spring-aware-graph

Conversation

@stephane-segning

Copy link
Copy Markdown
Contributor

1. Summary

This PR changes:

  • Adds docs/design/spring-aware-graph.md (453 lines) — a design exploration of making the code graph understand Spring Framework, Spring Boot and Spring Cloud

Documentation only. No src/, tests/, or manifest changes.

It solves:


2. Intent

The intent of this PR is:

To answer "should we build a Spring parser?" with enough evidence to decide, and the honest answer turned out to be build much less than the question implied, and fix something else first.

The spike ran the crate's real, unmodified extract_file + resolve over a realistic six-file Spring fixture set (controller, service interface + impl, Spring Data repository, JPA entity, Feign client) and got zero calls edges — then isolated the cause to a two-class fixture with no interfaces and no annotations at all. So "Spring's call graph is a lie" is mostly not Spring's fault: it is a general resolver gap that bites any interface-typed or field-injected call in any Java codebase, and reproduces identically in TypeScript and Python. That is now #8.

This matters for sequencing, not just accuracy: a large share of the apparent Spring value falls out of fixing #8, for free, with zero framework knowledge. The doc therefore puts #8 as Phase 0 and explicitly says stopping there may be the right call.


3. Scope

In Scope

  • The design document, its spike evidence, and the phasing recommendation

Out of Scope

  • Any implementation. Nothing in src/ changes; this is a decision document.
  • Fixing Instance calls through a variable receiver never resolve (all tags languages) #8. Identified here, tracked separately, deliberately not bundled into a docs PR.
  • XML bean config, full @ComponentScan/classpath resolution, and @Profile/conditional-bean evaluation — the doc rejects these outright rather than deferring them.

4. Verification

I verified this change by:

  • Running manual tests
  • Running automated tests
  • Checking logs
  • Checking metrics
  • Testing error cases
  • Testing permissions/security behavior
  • Testing rollback or failure behavior, if relevant

The doc's load-bearing claims were verified by running code, not by reading it.

Commands run:

cargo run -q            # spike crate, path-dependency on lci-codegraph, over Spring + plain fixtures

Results:

Java: a.helper()  instance call via variable   -> 0 calls edges
Java: A.helper()  type-qualified               -> 1
Java: helper()    bare                         -> 1
TS  : instance call via variable               -> 0        TS  : bare call -> 1
PY  : instance call via variable               -> 0        PY  : bare call -> 1

Reproduced three times independently — by an adversarial review of the #5 fix, by this Spring spike, and once more by hand before filing #8.

The spike also confirmed all four extractions the design depends on are genuinely available from tree-sitter-java: class-level annotations with arguments, method-level @GetMapping("/path") including the argument string, constructor parameter types for injection, and the generic type argument from extends JpaRepository<Account, Long>.

The write/read asymmetry in §5.1 was verified against the consumer repository:

services/control-plane/src/integrations/neo4j.rs:86
    MERGE (a)-[r:REL {relation: $rel}]->(b)     <- new relations write for free

same file, graph_get_callers
    -[:REL {relation: 'calls'}]->               <- but only 'calls' can be traversed

5. Screenshots / Evidence

Add evidence here:


6. Risk Assessment

Risk level:

  • Low
  • Medium
  • High

Potential risks:

Mitigation:

  • Documentation only — nothing executes, and no behaviour changes on merge.
  • The measurements are stated as measurements with the commands that produced them, so they can be re-run rather than trusted.
  • Phase 1 is explicitly written to be re-evaluated after Phase 0 lands, and the doc states that stopping after Phase 0 is a legitimate outcome.

7. AI Usage Declaration

AI was used for:

  • Understanding existing code
  • Generating code
  • Refactoring
  • Generating tests
  • Drafting documentation
  • Reviewing the diff
  • Not used

Human verification:

  • I understand every meaningful change in this PR
  • I checked generated code manually
  • I checked generated tests manually
  • I removed unsupported AI assumptions
  • I accept responsibility for this PR

The doc's central empirical claim was independently reproduced before being written down, and the write/read asymmetry was checked directly against the consumer's Cypher rather than assumed.


8. Reviewer Focus

Please focus your review on:

  • Correctness
  • Architecture
  • Security
  • Performance
  • Tests
  • Maintainability
  • Product intent
  • Edge cases

Specifically:

  • Is Phase 1 worth building at all, or is "fix Instance calls through a variable receiver never resolve (all tags languages) #8 and stop" the right answer? The doc argues Phase 1 is worth it but explicitly does not argue against pausing. This is the actual decision.
  • Does adding framework knowledge compromise the crate's "pure extractor" identity? §5.2 argues the line holds because the proposed work is syntactic, but that is a judgement call worth a second opinion.
  • Is the route / external_service node-kind approach right, versus modelling routes as a new relation? The node-kind framing was chosen specifically to avoid the cross-repo dependency in §5.1.

Records an exploration of what it would take to make the graph understand
Spring Framework / Boot / Cloud, and recommends building much less than
the question implied.

The main finding is that most of what looks like Spring-specific breakage
is not Spring-specific at all: a plain two-class Java fixture with no
annotations and no interfaces already fails to resolve an instance call
through a variable receiver. That is issue #8, it affects Java, TypeScript
and Python equally, and a large share of the apparent Spring value falls
out of fixing it. The doc therefore proposes Phase 0 (the general fix,
tracked separately) before any Spring work, and explicitly allows for
stopping there.

What remains genuinely Spring-specific is small: two additive node kinds
(HTTP routes, outbound Feign clients) that need no new relation and no
consumer change, plus a Spring Data carve-out. The one new relation the
doc proposes is deferred behind an explicit cross-repo gate, because the
consumer's graph_get_callers hardcodes relation='calls' — new relations
write for free but are unqueryable until that repo ships a change.

Doc only. No src/ or tests/ changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Aug 8, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 6155362

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@stephane-segning
stephane-segning merged commit 293d12f into main Aug 8, 2026
7 checks passed
@stephane-segning
stephane-segning deleted the docs/spring-aware-graph branch August 8, 2026 03:19
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.

Instance calls through a variable receiver never resolve (all tags languages)

1 participant