find_cross_repo_consumers
Consumers of a symbol in other repositories, exact uses kept apart from package-level dependencies. A language server stops at its workspace.
Arguments
Section titled “Arguments”| Argument | Type | Default | Meaning |
|---|---|---|---|
cursor | string | none | Opaque token taken from next_cursor. Resumes the same query at the next offset. |
language | string | none | Keeps only consumers carrying this language: go, typescript or rust. Compared exactly; surrounding whitespace is rejected. |
limit | integer | 50 | Rows in this page. Must be between 1 and 500. |
path | string | none | Repository-relative path narrowing qualified_name. Requires repository. Rejected together with stable_key. |
qualified_name | string | none | Names the symbol to ask about. Either this or stable_key is required, never both. A name matching more than one symbol is rejected with AMBIGUOUS_SYMBOL instead of being resolved silently. |
repo | string | none | Keeps only consumers in this repository, by name, compared exactly. It filters the answer; repository selects the subject. |
repository | string | none | Repository of the symbol being asked about. Narrows qualified_name. |
response_format | string | concise | concise or detailed. Detailed adds the derived identifiers, such as consumer symbol, repository, package and file keys. Anything else is rejected with INVALID_ARGUMENT. |
stable_key | string | none | Names the symbol directly. Cannot be combined with qualified_name, repository or path. |
view | string | compact | The granularity of the answer, never a different answer. compact lifts the columns every consumer repeats into the header and gives a repository one entry for its package dependencies. full is the field-per-row shape. The answer is a set of consuming repositories and not a set of files, so files is rejected with INVALID_ARGUMENT, as is any other value. |
Answers
Section titled “Answers”Who, outside the symbol’s own repository, uses it or depends on the package that
declares it. A language server stops at its workspace, so this is the question
no editor answers. The response states the subject once and returns a
consumers list where every row carries a category, so an exact use is never
mixed with a dependency between packages. Rows inside the symbol’s own
repository are excluded by construction; ask
find_references for those.
By default the page arrives in the compact view: category, edge_kind,
confidence, provenance, evidence_kind and reason rise into the header
whenever every row that has them agrees, and requested_package and
requested_symbol do too, because the request is a property of the call and
not of the consumer. A row keeps only what the header does not state. Measured
over a 35-row page on the benchmark corpus, the compact view alone
brought 2.456 tokens down to 2.202; grouping the rows that still disagreed
took it to 926 — see reading a grouped page below.
That corpus is private, so the repository and package names quoted from it below
are substituted; the counts, the edge kinds and the token figures are the
measured ones.
Example
Section titled “Example”{ "repository": "kivgraph", "path": "internal/facts/facts.go", "qualified_name": "MergeAll"}{ "snapshot_id": 30, "total": 0, "returned": 0, "guidance": "no repository in the published graph consumes this symbol. Check graph_status if a consumer is registered but was not indexed, and find_references for uses inside its own repository", "results": { "subject": { "qualified_name": "MergeAll", "at": "kivgraph:internal/facts/facts.go:516", "pkg": "github.com/Luqueee/kivgraph/internal/facts", "module_path": "github.com/Luqueee/kivgraph", "end_line": 542 }, "consumers": [] }}The subject is the repository:path:line triple in at with its package in
pkg, and end_line appears only because the declaration spans more than one
line. There is no coverage at all: all four counters were zero, and four zeros
say only that the tool has four counters. truncated and next_cursor are
absent for the same reason.
The same answer in the full view:
{ "repository": "kivgraph", "path": "internal/facts/facts.go", "qualified_name": "MergeAll", "view": "full"}{ "snapshot_id": 30, "snapshot_age_ms": 9020, "total": 0, "returned": 0, "truncated": false, "next_cursor": null, "coverage": { "exact": 0, "candidate": 0, "unresolved_related": 0, "package_level": 0 }, "guidance": "no repository in the published graph consumes this symbol. Check graph_status if a consumer is registered but was not indexed, and find_references for uses inside its own repository", "results": { "subject": { "qualified_name": "MergeAll", "repository": "kivgraph", "package_name": "github.com/Luqueee/kivgraph/internal/facts", "module_path": "github.com/Luqueee/kivgraph", "file_path": "internal/facts/facts.go", "start_line": 516, "end_line": 542 }, "consumers": null }}This answer comes from snapshot 30 of two repositories, kivgraph and
mole.
Reading the result
Section titled “Reading the result”The captured answer has no consumers, and that is the interesting case. total
is 0, consumers is an empty list — null in the full view — and guidance
says what the zero means: no
repository in the published graph consumes this symbol, plus the two ways the
answer could still be wrong, both of which are about the index and not about the
code. That sentence is how the surface distinguishes a checked absence from an
empty page. Without it, zero rows read as “no such thing” and the session goes
back to grep.
When there are consumers, every row carries a category, in the header when the
whole page shares one and on the row when it does not:
category | What it means |
|---|---|
exact_symbol | A use of the queried symbol, resolved by a type checker. |
candidate | A relation to the symbol that is plausible and not proven. |
package | A PACKAGE_DEPENDS_ON dependency on the package that declares it. |
unresolved | A recorded reference the resolver could not follow, with the strings it asked for. |
An exact_symbol or candidate row is a reference row and reads like one from
find_references: edge_kind is the
relation, confidence is how well it is proven, provenance is the mechanism
that observed it. It also carries the consumer’s repository, package, file and
line range, so it can be opened without another call. In the compact view the
consumer’s repository is repo, its package is pkg, and the file and line are
one at of the form path:line, with end_line beside it only when the
declaration spans more than one line — repo plus the path in at plus
qualified_name is the triple the next call takes.
A package row carries no symbol, no file and no range, and it is not an
omission. The evidence is a dependency between packages: it has an edge_kind,
a confidence and a provenance of its own, and it names a consuming
repository and package, because that is all anybody observed. The compact view
gives a repository one entry for those dependencies, with pkg holding the bare
package name while one package of the repository carries the fact and the list of
names when several do; total, returned and coverage still count every
dependency, so one entry can stand for two package-level facts.
An unresolved row is evidence about a failed request, not a relationship. It
carries reason, requested_package, requested_symbol and detail, which
are the strings the resolver used and never graph keys. reason rises into the
header, or a group’s own header, whenever every row that carries it agrees;
detail never reaches the page header — across the whole answer it is
usually several distinct sentences — but a group built on a shared reason
often turns out to share one detail too, because the sentence is a template
keyed by the failure and not prose composed per file. requested_package and
requested_symbol rise into the page header whenever every row that names
them names the same one, because the request is a property of the call and
not of the consumer.
Reading a grouped page
Section titled “Reading a grouped page”category, edge_kind, confidence, provenance, evidence_kind and
reason are a page-wide hoist or nothing: mix one package dependency into a
page of exact uses and every one of those six columns drops back onto every
row. results.groups is the second tier that catches it, grouping rows by
whichever exact tuple of those six they still share — never detail, which
gets its own hoist attempt once a group is otherwise fixed, exactly as
reached_from does for get_blast_radius.
See when a page groups for the mechanism
shared by six tools.
Measured over the real 35-consumer page on the private benchmark corpus that motivated this tier:
22 package-level dependencies on @workspace/platform, each its own repository, all
sharing category: "package", edge_kind: "PACKAGE_DEPENDS_ON" and the same
confidence and provenance — one group, 22 bare { "repo", "pkg" } entries.
The other 13 rows are unresolved imports of the same package, but for two
different reasons: 7 files across one repository fail with
DECLARATION_SOURCE_NOT_MAPPED, all naming the identical .d.ts path as
detail; the other 6 fail with PROVIDER_SOURCE_UNAVAILABLE and the
identical sentence “no declaration map places this symbol in the provider’s
source”. Three groups, one per (category, reason) pair actually present,
and both detail sentences hoist to their group instead of repeating on 7
and 6 rows — the assumption that detail was each row’s own prose held
for a page small enough that it never had to be tested, and cost 2.202
tokens more than the 926 the grouped page now spends on the same evidence.
Why package_level is counted separately
Section titled “Why package_level is counted separately”coverage reports four counters, and the split is the point of this page. A
query about a symbol only counts as a consumer what was observed about that
symbol. exact counts uses proven by a type checker; candidate the plausible
ones; unresolved_related the references that named the symbol but could not be
followed. Package dependencies land in package_level and are never summed into
exact. The compact view writes only the counters that counted something, and
drops coverage entirely when none did, so an absent counter is a zero and never
a category the tool forgot.
The rule behind it is short: a package dependency proves that the consumer
depends on the provider and never that it uses the symbol. Adding it to exact
would report a use nobody saw. By the same rule, a resolution failure that named
no symbol at all, an unreadable module or an absent provider, belongs to the
package: it is declared with requested_package and never attributed to every
symbol that package exports.
So package_level is not a weaker exact. It answers a different question:
“could this repository be reaching me at all”, where exact answers “does it”.
A migration plan built on exact is a list of call sites; a migration plan built
on package_level is a list of places to go and look.
Every answer carries a completeness object whose verdict is COMPLETE when
nothing the index recorded could add to it and LOWER_BOUND when the answer is
a floor, with blind_spots for the individual references the resolver could not
follow and invisible_scopes for the packages it could not read at all. What
bounds this tool is deliberately wider than the rest: the scope half of the
check is global, so an unreadable package in any repository counts and not only
in the one the question names, because a package nobody could read anywhere is
exactly where an outside consumer hides. That matters most here, because this is
the tool with no native grep competitor and its empty answer gets read as a
finding — “nobody outside uses this” — so on LOWER_BOUND the zero-row
guidance refuses that reading and sends you to those two lists first. See
the completeness verdict for the shape shared by
the six tools that check.
Limits
Section titled “Limits”truncated is true when rows remain after this page, and next_cursor then
carries the token to continue; otherwise the full view says false and null
and the compact view omits both. This tool walks no graph, so it has no
traversal_truncated field: that one belongs to
trace_dependencies and
get_blast_radius, where the bound is on
the walk rather than on the page.
The cursor is an opaque base64url token over a binary body: the format version,
the snapshot id, the offset, a digest of the query identity, a digest of the
sorting contract consumers-v1 and a checksum over all of it. It is about 31
characters, where the previous format spent 314 of base64-wrapped JSON. Pass it
back unchanged. It fails closed:
| What changed | What you get |
|---|---|
| A new generation was published | CURSOR_SNAPSHOT_EXPIRED; restart pagination |
The selector, repo or language differ from the call that produced it | CURSOR_INVALID |
| The token was edited, re-encoded, truncated, or decodes with trailing bytes after the checksum | CURSOR_INVALID |
| The token was minted by a server of the previous cursor version | CURSOR_INVALID; the body declares version 2 and version 1 is not reinterpreted under the new layout |
limit, view and response_format are not part of the cursor identity, so
changing one of them mid-pagination is accepted: the same cursor continues the
page in another view, and changing limit shifts what the remaining pages
contain.
guidance appears only when the count alone would mislead: on zero rows, with
the sentence quoted above, and on a truncated page, where it names repo and
language as the way to narrow. It stays absent on a complete non-empty answer.
This tool takes no include_derived argument. Providers derived from the
machine, the ones in the rust: namespace, are withheld by default only from
find_symbol,
find_references,
trace_dependencies and
get_blast_radius.
Where it loses
Section titled “Where it loses”The answer is bounded by what is registered. A consumer nobody indexed produces
no row, and the zero-consumer guidance says so rather than pretending otherwise;
graph_status and
list_repositories are where you check.
It also says nothing about uses inside the symbol’s own repository, which are
the common case and belong to
find_references. For a single-repository
corpus this tool has nothing to add.