Trace usage
Once you can name a symbol, the next questions are: who touches it,
who calls it, and what sits above or below it in the type tree.
The raw/references, raw/call-hierarchy, and
raw/type-hierarchy methods answer those questions and tell you
whether the answer is complete.
Find references
raw/references returns every usage of a resolved symbol in the
workspace. kast narrows the file search based on the symbol's
Kotlin visibility: a private function searches its declaring file,
an internal function searches its module, a public function
searches every dependent module via the identifier index. You only
pay for the scope the language actually requires.
Every response carries a searchScope object. It tells you exactly
how kast searched and whether the result is complete — read it
before you trust the list.
{
"declaration": {
"fqName": "com.shop.OrderService.processOrder",
"kind": "FUNCTION",
"location": {
"filePath": "/workspace/src/main/kotlin/com/shop/OrderService.kt",
"startOffset": 42,
"endOffset": 54,
"startLine": 8,
"startColumn": 7,
"preview": "processOrder"
}
},
"references": [
{
"filePath": "/workspace/src/main/kotlin/com/shop/CheckoutController.kt",
"startOffset": 128,
"endOffset": 140,
"startLine": 15,
"startColumn": 9,
"preview": "processOrder"
},
{
"filePath": "/workspace/src/test/kotlin/com/shop/OrderServiceTest.kt",
"startOffset": 95,
"endOffset": 107,
"startLine": 12,
"startColumn": 15,
"preview": "processOrder"
}
],
"searchScope": {
"visibility": "PUBLIC",
"scope": "DEPENDENT_MODULES",
"exhaustive": true,
"candidateFileCount": 47,
"searchedFileCount": 47
},
"schemaVersion": 3
}
Read searchScope before you trust completeness
The five fields tell you what kast did and whether you should
believe the answer:
| Field | Meaning |
|---|---|
visibility |
Kotlin visibility kast resolved: PUBLIC, INTERNAL, PROTECTED, PRIVATE, LOCAL, or UNKNOWN. |
scope |
Breadth of the search: FILE, MODULE, or DEPENDENT_MODULES. |
exhaustive |
true when every candidate file was searched. Treat as partial when false. |
candidateFileCount |
Files the index flagged as possible reference holders. |
searchedFileCount |
Files kast actually analyzed. |
When exhaustive is false, the index was incomplete or the scope
couldn't cover every candidate. Widen the search or refresh the
workspace before claiming the list is final.
How visibility drives scope
kast uses the resolved visibility to pick the smallest scope that
still covers every possible reference. Tighter visibility means a
faster search:
flowchart TD
A["Resolve symbol visibility"] --> B{"Visibility?"}
B -->|PRIVATE / LOCAL| C["Search declaring file only"]
B -->|INTERNAL| D["Search declaring module"]
B -->|PUBLIC / PROTECTED| E["Search dependent modules via index"]
C --> F["searchScope.exhaustive = true"]
D --> F
E --> F
A private rename touches one file. A public rename fans out across
every module that depends on the declaring module. Same operation,
different blast radius — and kast knows which is which.
Expand the call hierarchy
raw/call-hierarchy builds a bounded call tree from a function or
method. INCOMING finds callers, OUTGOING finds callees. The
tree is never unbounded — kast enforces depth, total nodes,
per-node children, and a timeout. Every limit is reported back in
stats, so you always know whether you got the whole picture.
The response is the tree plus a stats object that reports whether
any bound stopped expansion early:
{
"root": {
"symbol": {
"fqName": "com.shop.OrderService.processOrder",
"kind": "FUNCTION",
"location": {
"filePath": "/workspace/src/main/kotlin/com/shop/OrderService.kt",
"startOffset": 42,
"endOffset": 54,
"startLine": 8,
"startColumn": 7,
"preview": "processOrder"
}
},
"children": [
{
"symbol": {
"fqName": "com.shop.CheckoutController.checkout",
"kind": "FUNCTION",
"location": {
"filePath": "/workspace/src/main/kotlin/com/shop/CheckoutController.kt",
"startOffset": 67,
"endOffset": 75,
"startLine": 10,
"startColumn": 7,
"preview": "checkout"
}
},
"callSite": {
"filePath": "/workspace/src/main/kotlin/com/shop/CheckoutController.kt",
"startOffset": 128,
"endOffset": 140,
"startLine": 15,
"startColumn": 9,
"preview": "processOrder"
},
"children": []
}
]
},
"stats": {
"totalNodes": 2,
"totalEdges": 1,
"truncatedNodes": 0,
"maxDepthReached": 1,
"timeoutReached": false,
"maxTotalCallsReached": false,
"maxChildrenPerNodeReached": false,
"filesVisited": 2
},
"schemaVersion": 3
}
How truncation works
kast stops expanding the tree the moment it hits a configured
bound. The conceptual tree below shows every reason a branch can
stop:
graph TD
A["processOrder()"] --> B["validateCart()"]
A --> C["applyDiscount()"]
A --> D["chargePayment()"]
B --> E["checkInventory()"]
B --> F["...truncated: MAX_CHILDREN_PER_NODE"]
D --> G["PaymentGateway.charge()"]
G --> H["...truncated: CYCLE back to chargePayment()"]
C --> I["...stop: depth limit reached"]
style F stroke-dasharray: 5 5
style H stroke-dasharray: 5 5
style I stroke-dasharray: 5 5
Each truncated node carries a truncation object with a reason:
| Reason | Meaning |
|---|---|
CYCLE |
The symbol already appears on the current path. kast cuts the branch to keep the tree finite. Not an error. |
MAX_CHILDREN_PER_NODE |
More direct callers or callees than maxChildrenPerNode allows. The node is partial. |
MAX_TOTAL_CALLS |
Total node count hit maxTotalCalls. Remaining branches are not expanded. |
TIMEOUT |
Traversal exceeded timeoutMillis. Remaining branches are not expanded. |
Depth-limited leaves stop because the configured depth ran out.
They do not carry a truncation object — read
stats.maxDepthReached to see how far the tree went.
Tip
Read stats before you claim a call tree is complete. Any
boolean flag set to true, or truncatedNodes > 0, means the
tree is partial. Raise the relevant bound and re-run.
Walk the type hierarchy
raw/type-hierarchy expands supertypes and subtypes from a class or
interface. direction picks the way:
SUPERTYPES— parents, interfaces, their ancestorsSUBTYPES— direct and transitive subclasses or implementorsBOTH— expand both ways from the root
The response is a tree rooted at the queried symbol. Supertypes
sit in the supertypes array on each node; subtypes appear as
children:
{
"root": {
"symbol": {
"fqName": "com.shop.FriendlyGreeter",
"kind": "CLASS",
"location": {
"filePath": "/workspace/src/main/kotlin/com/shop/Greeter.kt",
"startOffset": 45,
"endOffset": 60,
"startLine": 4,
"startColumn": 12,
"preview": "open class FriendlyGreeter : Greeter"
},
"containingDeclaration": "com.shop",
"supertypes": ["com.shop.Greeter"]
},
"children": [
{
"symbol": {
"fqName": "com.shop.Greeter",
"kind": "INTERFACE",
"location": {
"filePath": "/workspace/src/main/kotlin/com/shop/Greeter.kt",
"startOffset": 26,
"endOffset": 33,
"startLine": 3,
"startColumn": 11,
"preview": "interface Greeter"
},
"containingDeclaration": "com.shop"
},
"children": []
},
{
"symbol": {
"fqName": "com.shop.LoudGreeter",
"kind": "CLASS",
"location": {
"filePath": "/workspace/src/main/kotlin/com/shop/Greeter.kt",
"startOffset": 77,
"endOffset": 88,
"startLine": 5,
"startColumn": 7,
"preview": "class LoudGreeter : FriendlyGreeter()"
},
"containingDeclaration": "com.shop",
"supertypes": ["com.shop.FriendlyGreeter"]
},
"children": []
}
]
},
"stats": {
"totalNodes": 3,
"maxDepthReached": 1,
"truncated": false
},
"schemaVersion": 3
}
stats.truncated is the one flag to watch. true means the tree
is partial — raise depth or maxResults and re-run.
Next steps
- Refactor safely — plan and apply renames with conflict detection
- Use with agents — wire these operations into LLM agent workflows