Colour a Spring codebase by role
Spring’s structure lives in annotations, not in the call graph: a controller depends on a service it never constructs, and the container does the wiring at runtime. Codegraph can read those annotations and say what it inferred — clearly marked as an inference.
Before you start: a model.jsonl of a Spring codebase. The Spring
framework jars do not need to be present; matching tolerates the annotation
types being stubs, which is the normal case.
Colour the city by role
codegraph city model.jsonl --framework spring --serve--framework spring classifies types by Spring’s own vocabulary (service,
repository, controller…), attaches a role to each building and adds a roles
legend block to the artifact. In the viewer this becomes a Role colour mode,
legended like every other channel. Without the flag the city says nothing about
roles at all.
The command tells you what the inference found, on stderr:
note: spring classified 0 buildings into no role — an inference from written annotations, not a fact about the code.Zero is the honest answer on a corpus with no Spring annotations. If you get it on a codebase that has them, the model was extracted from a source root that does not contain the annotated classes.
Inspect the wiring
codegraph analyze model.jsonl --report wiringThe report is Spring’s; there is no framework flag on analyze. It prints its
own disclaimer first, and it means it:
framework: spring — every line below is DERIVED from annotations,
never a declared fact; candidate targets are `dynamic-candidate`.
architectural roles (0 types):
entry points: 0 (called from outside the corpus)
injection points: 0
derived 0 candidate edge(s); 0 injection point(s) have no corpus implementation, 0 narrowed by @Primary/@Qualifier, 0 with an unresolved type.Add --json for the same information under the standard report envelope.
What is inferred, and how far to trust it
For every injection point — an annotated field, a constructor parameter on a
stereotyped class, or the sole constructor of one — the pass derives a
dynamic-candidate edge to every corpus implementation of the declared
interface. Then:
@Primarynarrows by its presence on the producer;@Qualifiernarrows on its written argument value against the bean’s names — exact strings, never guesswork;- narrowing is never silent: the injection point says what it narrowed from.
An empty candidate list is a result, not a gap. On spring-petclinic at
a6e81a5, the recorded verification found all 6 @Autowired sites as 9
injection points: the 5 injecting ClinicService list exactly
ClinicServiceImpl, the corpus’s one implementation, and the 4 injecting Spring
Data repository interfaces list nothing — because the container implements those
at runtime, so the empty set is a fact about the corpus. On petclinic HEAD,
which has no @Autowired at all, the profile’s sole-constructor rule finds 6
injection points.
declared view of a corpus is
byte-identical whether or not the wiring pass ran — verified as a test, not
claimed. Every derived candidate is dynamic-candidate, so
--declared-only removes all of it.Feed the roles to something else
codegraph domain-facts model.jsonl --framework spring --out dossiers.json
codegraph explain model.jsonl --src src/main/java --framework spring --dry-rundomain-facts adds stereotypes, entry points and DI candidates to each type’s
dossier; explain adds them to the facts a prompt sees. Both label the layer as
an inference.