What problem does this solve?
In a multi-repo JVM estate (~240 separately-indexed projects, each its own git repo), there is no way to answer "which projects consume artifact X?"
Concrete, measured case. A utility class ByteBufWriter lives in the repo platform-io, which publishes the Maven artifact com.acme.platform:platform-io. Other repos depend on it — but they consume it as a compiled JAR from an internal Maven repository, not as source.
| Measurement |
Value |
<dependency> blocks in platform-io's own pom.xml |
23 |
pom.xml files across the estate declaring a dependency on platform-io |
29 |
| Distinct repos those span |
~19 |
DEPENDS_ON edges present in any of those projects' graphs |
0 |
Critically, the consuming projects are indexed, and they do reference the type — the reference just dangles. search_graph for ByteBufWriter in a consumer returns nodes like:
{
"name": "mByteBufWriter",
"label": "Field",
"qualified_name": "consumer-repo.src.test.java.com.acme.config.SomeTest.mByteBufWriter",
"return_type": "ByteBufWriter",
"in_degree": 0,
"out_degree": 0
}
The return_type is captured as a bare string. There is no edge — in either direction — to the ByteBufWriter class node that exists, fully indexed, in platform-io. in_degree and out_degree are both 0. So:
- The type name is in the graph but is not a graph reference; it cannot be joined on.
trace_path stops dead at the repo boundary.
get_graph_schema() on these projects lists ~15 edge types — CALLS, DEFINES, USAGE, IMPORTS, INHERITS, HTTP_CALLS, … — and DEPENDS_ON is not among them. The same call on a Go or Python project would include it.
This is the surprising part: both halves of the answer are already in the index — the definition in one project, the typed reference in another — and nothing joins them, because cbm_pipeline_resolve_module has no pkgmap entry for another project's groupId.artifactId prefix and falls through to cbm_pipeline_fqn_module(ctx->project_name, module_path) (pass_pkgmap.c:1445), minting a QN inside the consumer's own namespace that matches nothing.
The dependency is fully declared and machine-readable — it is right there in all 29 pom.xml files, which pass_pkgmap already opens and reads — but the <dependencies> block is discarded. I recovered the 29 with rg -l --glob 'pom.xml' 'platform-io', which is precisely the filesystem grep the graph is meant to replace.
Root cause (from reading the source)
Three places, none of which cover Maven:
1. parse_pom_xml reads only the module's own identity, never its dependencies.
src/pipeline/pass_pkgmap.c:557 extracts <groupId> + <artifactId> and registers groupId.artifactId -> src/main/java so the module can self-register. The <dependencies> block is never parsed. pom_find_tag (:539) already tracks and skips <parent>, so the machinery for scoping XML scans exists — it just isn't applied to dependencies.
2. The dependency-manifest dispatcher has no Maven branch.
src/pipeline/pass_k8s.c:653:
if (is_gomod_file(base) || lang == CBM_LANG_GOMOD || is_requirements_file(base)) {
...
handle_dep_manifest(ctx, rel, dep_src,
is_requirements_file(base) ? "pypi" : "gomod");
go.mod and requirements.txt are handled. pom.xml, build.gradle, and build.gradle.kts are not — even though pass_pkgmap already finds and reads all of them.
3. pass_cross_repo is runtime-only.
Per src/pipeline/pass_cross_repo.h it emits CROSS_HTTP_CALLS, CROSS_ASYNC_CALLS, CROSS_CHANNEL, CROSS_GRPC_CALLS, CROSS_GRAPHQL_CALLS, CROSS_TRPC_CALLS — all runtime connections. There is no build-time / artifact dependency equivalent.
Proposed solution
Two independently-useful tiers.
Tier 1 — Maven/Gradle parity with go.mod and requirements.txt
Make pom.xml and build.gradle[.kts] emit DEPENDS_ON edges exactly the way go.mod already does. This needs no new node label and no new edge type — it reuses the existing generic helper at pass_k8s.c:486:
static int emit_dep_edge(cbm_pipeline_ctx_t *ctx, const cbm_gbuf_node_t *src,
const char *rel_path, const char *ecosystem, const char *name)
which already builds a shared, deduplicated external Package node (%s.__%s_dep__.%s, props {"source":"<ecosystem>","external":true}) and links file --DEPENDS_ON--> Package. It is already parameterized by ecosystem string, so "maven" and "gradle" slot straight in.
Work required:
parse_pom_deps() — walk <dependencies>, emit groupId:artifactId per <dependency>. Must skip <dependencyManagement> and <parent> (the existing in-<parent> tracking in pom_find_tag is the pattern to follow), and should tolerate ${property} version placeholders since only the coordinate matters.
parse_gradle_deps() — the implementation "g:a:v" / api(...) forms.
- Two lines in the
pass_k8s.c:653 dispatcher, plus the handle_dep_manifest ternary at :617 (already at the size where a small lookup table would read better than a chained ternary).
Even alone this is a large win: it makes
MATCH (f)-[:DEPENDS_ON]->(p:Package) WHERE p.name = 'platform-io' RETURN f
work per project, so a caller can loop indexed projects and get a real consumer list without touching the filesystem.
Tier 2 — resolve external Package nodes to indexed projects
The more interesting half. Because parse_pom_xml already registers every project's own groupId.artifactId, the join key needed to turn an external Package node into a link to the project that publishes it is already being computed — it's just discarded at the project boundary.
A post-index pass structured like cbm_cross_repo_match (same "match against all indexed projects, write bidirectionally into both DBs" shape, including the target_projects / "*" convention and cancellation handling) could:
- Build
groupId:artifactId -> project_name across indexed projects from the identities parse_pom_xml already produces.
- For each external
Package node emitted in Tier 1, look up its coordinate.
- On a hit, emit a cross-project edge —
CROSS_DEPENDS_ON, consistent with the existing CROSS_* naming.
That reduces the original question to:
MATCH (c)-[:CROSS_DEPENDS_ON]->(d) WHERE d.name = 'platform-io' RETURN c.name
and, combined with the existing intra-project graph, would let trace_path cross a JAR boundary for the first time.
Notes
- Happy to take a run at Tier 1 if the direction seems right — it looks self-contained and
emit_dep_edge does most of the work already.
- Tier 1 also benefits Kotlin/Scala/Android repos, which share the Gradle path.
Alternatives considered
Index the whole estate as one project. Works only for a true single-parent-pom monorepo. My repos are separate git repos with separate lifecycles; concatenating them also defeats the per-project watcher and DB model, and the file count is far past the auto-index limit.
mvn dependency:tree and ingest the output. Correct, and it resolves transitives and property placeholders properly — but it needs a working Maven toolchain, network access to the internal repository, and a successful resolve for every repo. That is far heavier and more fragile than reading a pom.xml as text, which the index already does for pass_pkgmap. Declared direct dependencies cover the "who consumes this" question; full transitive resolution is a nice-to-have, not the blocker.
Filesystem grep over pom.xml. What I do today. It answers the question but sits entirely outside the graph, so it composes with nothing — no trace_path, no combining with call-graph data, no MCP access.
Heuristic name matching across projects. Guesswork where an exact, declared coordinate is already available. Not worth the false positives.
Confirmations
What problem does this solve?
In a multi-repo JVM estate (~240 separately-indexed projects, each its own git repo), there is no way to answer "which projects consume artifact X?"
Concrete, measured case. A utility class
ByteBufWriterlives in the repoplatform-io, which publishes the Maven artifactcom.acme.platform:platform-io. Other repos depend on it — but they consume it as a compiled JAR from an internal Maven repository, not as source.<dependency>blocks inplatform-io's ownpom.xmlpom.xmlfiles across the estate declaring a dependency onplatform-ioDEPENDS_ONedges present in any of those projects' graphsCritically, the consuming projects are indexed, and they do reference the type — the reference just dangles.
search_graphforByteBufWriterin a consumer returns nodes like:{ "name": "mByteBufWriter", "label": "Field", "qualified_name": "consumer-repo.src.test.java.com.acme.config.SomeTest.mByteBufWriter", "return_type": "ByteBufWriter", "in_degree": 0, "out_degree": 0 }The
return_typeis captured as a bare string. There is no edge — in either direction — to theByteBufWriterclass node that exists, fully indexed, inplatform-io.in_degreeandout_degreeare both 0. So:trace_pathstops dead at the repo boundary.get_graph_schema()on these projects lists ~15 edge types —CALLS,DEFINES,USAGE,IMPORTS,INHERITS,HTTP_CALLS, … — andDEPENDS_ONis not among them. The same call on a Go or Python project would include it.This is the surprising part: both halves of the answer are already in the index — the definition in one project, the typed reference in another — and nothing joins them, because
cbm_pipeline_resolve_modulehas no pkgmap entry for another project'sgroupId.artifactIdprefix and falls through tocbm_pipeline_fqn_module(ctx->project_name, module_path)(pass_pkgmap.c:1445), minting a QN inside the consumer's own namespace that matches nothing.The dependency is fully declared and machine-readable — it is right there in all 29
pom.xmlfiles, whichpass_pkgmapalready opens and reads — but the<dependencies>block is discarded. I recovered the 29 withrg -l --glob 'pom.xml' 'platform-io', which is precisely the filesystem grep the graph is meant to replace.Root cause (from reading the source)
Three places, none of which cover Maven:
1.
parse_pom_xmlreads only the module's own identity, never its dependencies.src/pipeline/pass_pkgmap.c:557extracts<groupId>+<artifactId>and registersgroupId.artifactId -> src/main/javaso the module can self-register. The<dependencies>block is never parsed.pom_find_tag(:539) already tracks and skips<parent>, so the machinery for scoping XML scans exists — it just isn't applied to dependencies.2. The dependency-manifest dispatcher has no Maven branch.
src/pipeline/pass_k8s.c:653:go.modandrequirements.txtare handled.pom.xml,build.gradle, andbuild.gradle.ktsare not — even thoughpass_pkgmapalready finds and reads all of them.3.
pass_cross_repois runtime-only.Per
src/pipeline/pass_cross_repo.hit emitsCROSS_HTTP_CALLS,CROSS_ASYNC_CALLS,CROSS_CHANNEL,CROSS_GRPC_CALLS,CROSS_GRAPHQL_CALLS,CROSS_TRPC_CALLS— all runtime connections. There is no build-time / artifact dependency equivalent.Proposed solution
Two independently-useful tiers.
Tier 1 — Maven/Gradle parity with go.mod and requirements.txt
Make
pom.xmlandbuild.gradle[.kts]emitDEPENDS_ONedges exactly the waygo.modalready does. This needs no new node label and no new edge type — it reuses the existing generic helper atpass_k8s.c:486:which already builds a shared, deduplicated external
Packagenode (%s.__%s_dep__.%s, props{"source":"<ecosystem>","external":true}) and linksfile --DEPENDS_ON--> Package. It is already parameterized by ecosystem string, so"maven"and"gradle"slot straight in.Work required:
parse_pom_deps()— walk<dependencies>, emitgroupId:artifactIdper<dependency>. Must skip<dependencyManagement>and<parent>(the existing in-<parent>tracking inpom_find_tagis the pattern to follow), and should tolerate${property}version placeholders since only the coordinate matters.parse_gradle_deps()— theimplementation "g:a:v"/api(...)forms.pass_k8s.c:653dispatcher, plus thehandle_dep_manifestternary at:617(already at the size where a small lookup table would read better than a chained ternary).Even alone this is a large win: it makes
work per project, so a caller can loop indexed projects and get a real consumer list without touching the filesystem.
Tier 2 — resolve external Package nodes to indexed projects
The more interesting half. Because
parse_pom_xmlalready registers every project's owngroupId.artifactId, the join key needed to turn an externalPackagenode into a link to the project that publishes it is already being computed — it's just discarded at the project boundary.A post-index pass structured like
cbm_cross_repo_match(same "match against all indexed projects, write bidirectionally into both DBs" shape, including thetarget_projects/"*"convention and cancellation handling) could:groupId:artifactId -> project_nameacross indexed projects from the identitiesparse_pom_xmlalready produces.Packagenode emitted in Tier 1, look up its coordinate.CROSS_DEPENDS_ON, consistent with the existingCROSS_*naming.That reduces the original question to:
and, combined with the existing intra-project graph, would let
trace_pathcross a JAR boundary for the first time.Notes
emit_dep_edgedoes most of the work already.Alternatives considered
Index the whole estate as one project. Works only for a true single-parent-pom monorepo. My repos are separate git repos with separate lifecycles; concatenating them also defeats the per-project watcher and DB model, and the file count is far past the auto-index limit.
mvn dependency:treeand ingest the output. Correct, and it resolves transitives and property placeholders properly — but it needs a working Maven toolchain, network access to the internal repository, and a successful resolve for every repo. That is far heavier and more fragile than reading apom.xmlas text, which the index already does forpass_pkgmap. Declared direct dependencies cover the "who consumes this" question; full transitive resolution is a nice-to-have, not the blocker.Filesystem
grepoverpom.xml. What I do today. It answers the question but sits entirely outside the graph, so it composes with nothing — notrace_path, no combining with call-graph data, no MCP access.Heuristic name matching across projects. Guesswork where an exact, declared coordinate is already available. Not worth the false positives.
Confirmations