Skip to content

Commit 43867a6

Browse files
harshithsunkuclaude
andcommitted
ctags export recovery + caller-graph defs cache: eval hits 65/65 (v1.4.0)
Two tool-behavior improvements, both kernel-verified. 1. ctags export recovery — closes the last eval known-fail. GNU Global's C parser derails on sparse annotations (__acquires(lock) on a forward declaration) and silently drops every definition below the derail point; find_definition ("mutex_lock") returned 7 shadows/variants but never the real kernel/locking/mutex.c:314. EXPORT_SYMBOL*(sym) always sits in the defining .c and IS indexed, so: an export site whose file has no definition record is proof of a parser miss — confirm with a (cached) Universal Ctags scan and prepend the definition, flagged resolved_via "ctags:EXPORT_SYMBOL". - macros.export_sites() extracts (path, line, variant) per export. - enrich.definition_tag() picks definition-shaped ctags tags (prototypes/externvars rejected). - server._export_recovery() + shared _resolve_definitions(): wired into find_definition, symbol_info (free — refs already fetched), get_symbol_body, and find_callees, so the recovered definition is also readable and analyzable. -sx retry covers fully-missed symbols whose occurrences land in GSYMS. Disabled with the existing enrichment knob (--no-enrich / GTAGS_MCP_ENRICH / enrich=false); best-effort, never fails a query. - def-mutex_lock (known-fail since v1.0.0) now PASSES and asserts top_path + resolved_via: eval is 65/65 = 100% recall, 15/15 = 100% precision@1 on the mid-2026 master snapshot. 2. Per-file `global -fx` definition cache for the caller graph. reachability/blast_radius run _callers_of once per BFS node and kept re-running identical `global -fx <file>` subprocesses for hot files. New LRU (capacity 4096) keyed by (root, index generation, path) — generation bumps on every build/refresh path, so entries die with the index state they were read from; negative results cached; thread-safe. Measured: warm reachability 552ms -> 46ms, warm find_callers on a 200-caller symbol 61ms -> 3ms. Tests 293 -> 304: export_gap_project fixture reproduces the parser derail end-to-end (skips if upstream fixes it), recovery via find_definition/symbol_info/get_symbol_body/find_callees, opt-out inertness, -fx run-counting (one run per file per generation), cache invalidation via update_index, generation-bump coverage, plus pure export_sites/definition_tag units. Stability exercise: 28-call matrix (new def-recovered-mutex_lock case), 28/28 sane, zero anomalies; docs/capability.md operational table and known-limitations rewritten accordingly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 65e31d7 commit 43867a6

17 files changed

Lines changed: 574 additions & 95 deletions

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Every AI coding agent — Claude Code, Cursor, Codex, you name it — answers *"
2525

2626
- **~100× faster per query** — milliseconds instead of seconds, at any codebase size
2727
- **Radically less noise** — the definition, not 7,873 lines of matches
28-
- **Speaks kernel**`#ifdef` guard stacks with `.config` filtering, and macro-generated symbols (`sys_read` → its `SYSCALL_DEFINE3` site) that no other tagging tool resolves
28+
- **Speaks kernel**`#ifdef` guard stacks with `.config` filtering, macro-generated symbols (`sys_read` → its `SYSCALL_DEFINE3` site) that no other tagging tool resolves, and definitions the parser misses recovered from their `EXPORT_SYMBOL` site via ctags
2929
- **Zero index management** — first query builds the index, every query auto-refreshes it
3030
- **Correctness measured in CI** — a 65-case golden eval covering all 11 tools against a pinned kernel: 100% recall, 100% precision@1 ([docs/capability.md](docs/capability.md))
3131
- **Works everywhere MCP does** — Claude Code, Claude Desktop, Cursor, any MCP client
@@ -42,6 +42,7 @@ Measured on a full Linux kernel checkout — **65,163 C/C++ files, 37.1 million
4242
| Show me `tcp_v4_rcv`'s implementation | *read a 3,500-line file* | **`get_symbol_body`** | **exactly the 271-line function** |
4343
| Who calls `ext4_mark_inode_dirty`? | 245 raw match lines | **`find_callers`** | **62 deduped caller functions, with counts** |
4444
| Where is `sys_read` *really* defined? | *no answer — the name is macro-generated* | **0.03 s** | `fs/read_write.c SYSCALL_DEFINE3(read, ...)`, flagged `resolved_via` |
45+
| Where is `mutex_lock` defined? | 24,774 noisy match lines | **0.2 s** | `kernel/locking/mutex.c:314` — recovered via `EXPORT_SYMBOL` + ctags after gtags' parser derails on it |
4546
| Does `ksys_read` ever reach `rw_verify_area`? | *N rounds of grep + reading* | **0.6 s** | the shortest call chain, with every call site's file:line |
4647
| What does my uncommitted diff impact? | *not answerable* | **0.1 s** | `blast_radius`: changed functions + callers, ranked by distance |
4748

@@ -53,7 +54,7 @@ One-time index build: **66 s** for the whole kernel. Incremental refresh after e
5354

5455
The speed is nice. The real win is **precision**: an agent that gets 5 exact lines instead of 7,873 noisy ones keeps its context window for actual reasoning.
5556

56-
And the answers are *measured*, not assumed: CI runs a [65-case golden eval](evals/golden.jsonl) against pinned kernel v6.16 on every push — currently **100% recall, 100% precision@1** across definitions, macro resolution, references, callers, callees, definition bodies, `#ifdef` guards, and reachability, covering all 11 tools. The full methodology, numbers, and honest limitations live in [docs/capability.md](docs/capability.md).
57+
And the answers are *measured*, not assumed: CI runs a [65-case golden eval](evals/golden.jsonl) against pinned kernel v6.16 on every push — currently **100% recall, 100% precision@1** across definitions, macro resolution, export recovery, references, callers, callees, definition bodies, `#ifdef` guards, and reachability, covering all 11 tools. The full methodology, numbers, and honest limitations live in [docs/capability.md](docs/capability.md).
5758

5859
## Quick start (60 seconds)
5960

ROADMAP.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,18 @@ Trust through measurable quality — reported on its own terms.
117117
**Done when:** an eval command prints a score in CI, and the capability writeup is live.
118118
`mcp-gtags-server eval --golden evals/golden.jsonl --root <kernel>` runs 65
119119
path-level cases covering all 11 tools in ~14s — 100% recall and 100% precision@1
120-
in CI on pinned v6.16; 98.5% recall on a 2026 master snapshot, where the one
121-
deliberate known-fail (a GNU Global parser gap triggered by the new `__acquires()`
122-
annotations) keeps the number honest. CI (`eval.yml`) clones the pinned kernel tag
123-
weekly and on main pushes and publishes the score in the job summary. A second
124-
operational layer, `scripts/stability_exercise.py`, measures per-tool latency and
125-
response size against the kernel (27-call matrix, zero anomalies — table in the
126-
writeup). The writeup lives at [docs/capability.md](docs/capability.md); posting
127-
it to the blog/HN is the Reach item below.
120+
on pinned v6.16 AND on a 2026 master snapshot. The one long-standing known-fail
121+
(a GNU Global parser gap triggered by the new `__acquires()` annotations that
122+
hid `mutex_lock`'s real definition) was closed in v1.4.0 by ctags export
123+
recovery: definitions the parser misses are restored from their `EXPORT_SYMBOL*`
124+
site, flagged `resolved_via: "ctags:..."`. v1.4.0 also added a per-file
125+
definition cache keyed by index generation, dropping warm reachability from
126+
~0.55s to ~46ms. CI (`eval.yml`) clones the pinned kernel tag weekly and on main
127+
pushes and publishes the score in the job summary. A second operational layer,
128+
`scripts/stability_exercise.py`, measures per-tool latency and response size
129+
against the kernel (28-call matrix, zero anomalies — table in the writeup). The
130+
writeup lives at [docs/capability.md](docs/capability.md); posting it to the
131+
blog/HN is the Reach item below.
128132

129133
---
130134

docs/capability.md

Lines changed: 54 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,16 @@ path-level so they hold across kernel versions.
2121
Current scores:
2222

2323
```
24-
local, mid-2026 master snapshot: 64/65 = 98.5% recall, 14/14 = 100.0% precision@1, 14s
25-
CI, kernel v6.16 (pinned): published on every push (100.0% on the previous 50-case set)
24+
local, mid-2026 master snapshot: 65/65 = 100.0% recall, 15/15 = 100.0% precision@1, 14s
25+
CI, kernel v6.16 (pinned): published on every push
2626
```
2727

28-
The one failure on newer kernels is kept in the set deliberately: recent
29-
kernels added sparse `__acquires()` annotations to mutex forward
30-
declarations, and GNU Global's C parser derails on them — the eval caught a
31-
real upstream parser regression triggered by a new kernel annotation style
32-
(see "Known limitations").
28+
One case (`def-mutex_lock`) was a deliberate known-fail from v1.0.0 through
29+
v1.3.1: recent kernels added sparse `__acquires()` annotations to mutex
30+
forward declarations, and GNU Global's C parser derails on them, missing the
31+
real `mutex_lock` definition. v1.4.0 closes it with **ctags export
32+
recovery** (see "What resolves"), and the case now also asserts the recovery
33+
mechanism itself.
3334

3435
## What resolves
3536

@@ -67,6 +68,24 @@ server resolves them from the index alone and flags the mechanism:
6768
Resolution costs 20–75 ms per query on the kernel; a miss costs ~9 ms.
6869
`symbol_info` also reports which `EXPORT_SYMBOL*` variant exports a symbol.
6970

71+
**Parser-missed definitions recover via their export site** (v1.4.0). When
72+
GNU Global's parser derails mid-file (sparse annotations like
73+
`__acquires(lock)` on a forward declaration are the known trigger), real
74+
definitions below that point vanish from the index. But `EXPORT_SYMBOL*(sym)`
75+
always sits in the `.c` file that defines `sym` — so when an export site's
76+
file has no definition record, a (cached) Universal Ctags scan of that file
77+
confirms and restores the definition, ranked first and flagged
78+
`resolved_via: "ctags:EXPORT_SYMBOL"`:
79+
80+
| query | gtags alone | with export recovery |
81+
|---|---|---|
82+
| `mutex_lock` | 7 records, none the real one | `kernel/locking/mutex.c:314` first, kind `function` |
83+
84+
The recovered definition flows through the whole tool surface —
85+
`get_symbol_body` reads its real body, `find_callees` analyzes it — and
86+
costs ~0.25 s once (then cached). Disabled together with enrichment
87+
(`--no-enrich`).
88+
7089
**Call-graph questions** collapse into single calls:
7190

7291
- `reachability(ksys_read, rw_verify_area)` → the shortest chain, with the
@@ -80,47 +99,51 @@ Resolution costs 20–75 ms per query on the kernel; a miss costs ~9 ms.
8099
## Operational profile (measured)
81100

82101
[scripts/stability_exercise.py](../scripts/stability_exercise.py) runs a
83-
27-call matrix over all 11 tools against a real kernel tree and records
102+
28-call matrix over all 11 tools against a real kernel tree and records
84103
latency and response size (`--json` for diffing between runs). Measured on
85-
the 37M-line mid-2026 snapshot, 8 GB machine, warm index — **27/27 sane,
104+
the 37M-line mid-2026 snapshot, 8 GB machine, warm index — **28/28 sane,
86105
zero anomalies**:
87106

88107
| call | warm latency | response size |
89108
|---|---|---|
90-
| `find_definition` (indexed hit) | ~2 ms | 0.5–1.7 KB |
91-
| `find_definition` (macro-generated, e.g. `sys_read`) | ~47 ms | 0.8 KB |
92-
| `find_references` (`kmalloc`, 2 744 refs, one page) | ~60 ms | 23 KB |
93-
| `get_symbol_body` (function / struct / macro) | ~2 ms | 0.6–7.5 KB |
94-
| `find_callers` (typical) | 13–61 ms | 0.5–5 KB |
95-
| `find_callers` (worst allowed: `schedule`, 610 callers / ~430 files) | ~1.5 s | |
96-
| `find_callers` (>500 files: guard refuses gracefully) | ~53 ms | 0.3 KB |
97-
| `summarize_references` (`kmalloc`, 1 553 files) | ~56 ms | 0.8 KB |
98-
| `find_callees` | 15–40 ms | 0.9–1.9 KB |
99-
| `symbol_info` | 5–56 ms | 1.4 KB |
100-
| `reachability` (3-hop chain found) | ~0.6 s | 0.6 KB |
109+
| `find_definition` (indexed hit) | 4–45 ms | 0.5–1.7 KB |
110+
| `find_definition` (macro-generated, e.g. `sys_read`) | ~40 ms | 0.8 KB |
111+
| `find_definition` (parser-missed, ctags-recovered: `mutex_lock`) | ~0.22 s | 2.6 KB |
112+
| `find_references` (`kmalloc`, 2 744 refs, one page) | ~52 ms | 23 KB |
113+
| `get_symbol_body` (function / struct / macro) | 4–120 ms | 0.6–7.5 KB |
114+
| `find_callers` (typical, `-fx` cache warm) | 2–3 ms | 0.5–5 KB |
115+
| `find_callers` (>500 files: guard refuses gracefully) | ~54 ms | 0.3 KB |
116+
| `summarize_references` (`kmalloc`, 1 553 files) | ~52 ms | 0.8 KB |
117+
| `find_callees` | 17–45 ms | 0.9–1.9 KB |
118+
| `symbol_info` | 5–48 ms | 1.4 KB |
119+
| `reachability` (3-hop chain found) | ~46 ms warm (~0.35 s cold) | 0.6 KB |
101120
| `list_file_symbols` (80-symbol file) | ~4 ms | 27 KB |
102121
| `blast_radius` (real one-line edit, depth 1) | ~0.1 s | 2.5 KB |
103122
| `update_index` (synchronous incremental, 37M lines) | 6–10 s | 0.3 KB |
104123

105-
Every response an agent sees stays comfortably under typical MCP client
106-
timeouts, wide queries are bounded by pagination and breadth guards, and the
107-
one deliberately slow call (`update_index`) is the explicit freshness barrier
108-
— background refresh keeps normal queries off that path.
124+
The caller-graph tools share a per-file definition cache keyed by index
125+
generation (v1.4.0): a reachability walk that re-visits the same hot files
126+
went from ~0.55 s to ~46 ms warm, and repeated `find_callers` calls in one
127+
agent session cost single-digit milliseconds. Every response stays
128+
comfortably under typical MCP client timeouts, wide queries are bounded by
129+
pagination and breadth guards, and the one deliberately slow call
130+
(`update_index`) is the explicit freshness barrier — background refresh
131+
keeps normal queries off that path.
109132

110133
## Known limitations (measured, not hidden)
111134

112135
- **Function pointers are invisible to static tagging.** `vfs_read` never
113136
statically reaches `ext4_file_read_iter` — the route is `f_op->read_iter`.
114137
`reachability` says so explicitly rather than returning a wrong path; the
115138
golden set asserts this honest "no" (`reach-fnptr-honesty`).
116-
- **One upstream parser gap, kept as a failing eval case:** GNU Global's C
117-
parser derails on the sparse annotation kernels newer than ~v6.16 put on
118-
forward declarations
139+
- **Upstream parser gaps are recovered, not fixed.** GNU Global's C parser
140+
derails on the sparse annotation kernels newer than ~v6.16 put on forward
141+
declarations
119142
(`static void __sched __mutex_lock_slowpath(...) __acquires(lock);`) and
120-
misses the real `mutex_lock` definition at `kernel/locking/mutex.c:314`.
121-
The `CONFIG_DEBUG_LOCK_ALLOC` macro variant and the `PREEMPT_RT` variant
122-
are still found, and older kernels are unaffected (hence 100% in CI on
123-
v6.16 vs 98% on a 2026 master snapshot).
143+
misses the real `mutex_lock` definition. Since v1.4.0, export recovery
144+
restores such definitions from their `EXPORT_SYMBOL*` site via ctags — but
145+
a parser-missed definition that is *not* exported (static helpers below
146+
the derail point) stays invisible until the upstream parser is fixed.
124147
- **Prototypes rank alongside definitions.** `find_definition` can return a
125148
header prototype before the `.c` definition (both are index "definitions");
126149
the ctags `kind` field distinguishes them.

evals/golden.jsonl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@
1212
{"id": "def-tcp_v4_rcv", "category": "definition", "tool": "find_definition", "args": {"symbol": "tcp_v4_rcv"}, "expect": {"paths": ["net/ipv4/tcp_ipv4.c"], "top_path": ["net/ipv4/tcp_ipv4.c"]}}
1313
{"id": "def-ext4_map_blocks", "category": "definition", "tool": "find_definition", "args": {"symbol": "ext4_map_blocks"}, "expect": {"paths": ["fs/ext4/inode.c"]}}
1414
{"id": "def-kernel_clone", "category": "definition", "tool": "find_definition", "args": {"symbol": "kernel_clone"}, "expect": {"paths": ["kernel/fork.c"], "top_path": ["kernel/fork.c"]}}
15-
# def-mutex_lock is a deliberate KNOWN-FAIL: GNU Global's C parser derails on the
16-
# sparse annotation in "static void __sched __mutex_lock_slowpath(...) __acquires(lock);"
17-
# and misses the real mutex_lock definition at kernel/locking/mutex.c:314 entirely
18-
# (the CONFIG_DEBUG_LOCK_ALLOC macro and PREEMPT_RT variants are still found).
19-
# Tracked honestly instead of hidden — see docs/capability.md.
20-
{"id": "def-mutex_lock", "category": "definition", "tool": "find_definition", "args": {"symbol": "mutex_lock"}, "expect": {"paths": ["kernel/locking/mutex.c"]}}
15+
# def-mutex_lock exercises ctags export recovery (v1.4.0): GNU Global's C parser
16+
# derails on the sparse annotation in "static void __sched __mutex_lock_slowpath(...)
17+
# __acquires(lock);" and misses the real definition at kernel/locking/mutex.c:314
18+
# the server recovers it from the EXPORT_SYMBOL(mutex_lock) site via ctags and ranks
19+
# it first. This case was a deliberate known-fail from v1.0.0 to v1.3.1.
20+
{"id": "def-mutex_lock", "category": "definition", "tool": "find_definition", "args": {"symbol": "mutex_lock"}, "expect": {"paths": ["kernel/locking/mutex.c"], "top_path": ["kernel/locking/mutex.c"], "resolved_via": "ctags:EXPORT_SYMBOL"}}
2121
{"id": "def-skb_clone", "category": "definition", "tool": "find_definition", "args": {"symbol": "skb_clone"}, "expect": {"paths": ["net/core/skbuff.c"], "top_path": ["net/core/skbuff.c"]}}
2222
{"id": "def-do_dentry_open", "category": "definition", "tool": "find_definition", "args": {"symbol": "do_dentry_open"}, "expect": {"paths": ["fs/open.c"], "top_path": ["fs/open.c"]}}
2323
{"id": "def-wait_event", "category": "definition", "tool": "find_definition", "args": {"symbol": "wait_event"}, "expect": {"paths": ["include/linux/wait.h"]}}

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "mcp-gtags-server"
3-
version = "1.3.1"
3+
version = "1.4.0"
44
description = "Indexed code navigation for AI coding agents — replace grep scans with GNU Global (gtags) lookups over MCP. ~100x faster and radically less noise on million-line C/C++ codebases."
55
readme = "README.md"
66
requires-python = ">=3.10"

scripts/stability_exercise.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,24 @@ def status_ok(data):
9292
def has_suggestions(data):
9393
return None if data.get("suggestions") else "expected suggestions on the miss"
9494

95+
def recovered_from_export(data):
96+
paths = [r["path"] for r in data["results"]]
97+
if "kernel/locking/mutex.c" not in paths:
98+
return f"expected recovered kernel/locking/mutex.c, got {paths[:3]}"
99+
if not str(data.get("resolved_via", "")).startswith("ctags:"):
100+
return f"expected ctags resolved_via, got {data.get('resolved_via')!r}"
101+
return None
102+
95103
return [
96-
# find_definition: plain, multi-def guarded, macro-generated, miss.
104+
# find_definition: plain, multi-def guarded, macro-generated,
105+
# parser-missed (ctags export recovery), miss.
97106
Case("def-plain", "find_definition", {"symbol": "vfs_read"}),
98107
Case("def-guarded-kmap", "find_definition", {"symbol": "kmap"}, min_results=2),
99108
Case("def-macro-sys_read", "find_definition", {"symbol": "sys_read"}),
109+
Case(
110+
"def-recovered-mutex_lock", "find_definition", {"symbol": "mutex_lock"},
111+
extra_check=recovered_from_export,
112+
),
100113
Case("def-struct", "find_definition", {"symbol": "task_struct"}),
101114
Case(
102115
"def-miss-suggestions", "find_definition", {"symbol": "tcp_v4_r"},

server.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66
"url": "https://github.com/harshithsunku/mcp-gtags-server",
77
"source": "github"
88
},
9-
"version": "1.3.1",
9+
"version": "1.4.0",
1010
"websiteUrl": "https://github.com/harshithsunku/mcp-gtags-server",
1111
"packages": [
1212
{
1313
"registryType": "pypi",
1414
"registryBaseUrl": "https://pypi.org",
1515
"identifier": "mcp-gtags-server",
16-
"version": "1.3.1",
16+
"version": "1.4.0",
1717
"runtimeHint": "uvx",
1818
"transport": {
1919
"type": "stdio"

src/gtags_mcp/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""MCP server exposing GNU Global (gtags) code navigation for C/C++ codebases."""
22

3-
__version__ = "1.3.1"
3+
__version__ = "1.4.0"

src/gtags_mcp/enrich.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,3 +235,22 @@ def best_tag(tags: dict[str, list[dict]], name: str, line: int) -> dict | None:
235235
if len(entries) == 1:
236236
return entries[0]
237237
return None
238+
239+
240+
# Declaration-shaped kinds: they point AT a definition, they are not one.
241+
_NON_DEFINITION_KINDS = frozenset({"prototype", "externvar"})
242+
243+
244+
def definition_tag(tags: dict[str, list[dict]], name: str) -> dict | None:
245+
"""The best definition-shaped tag for `name` in a file's tags, or None.
246+
247+
Prototypes and extern declarations are rejected — export recovery needs
248+
the site that actually defines the symbol. Ties break on kind priority,
249+
then the earliest line.
250+
"""
251+
entries = [
252+
t for t in tags.get(name, []) if t["kind"] not in _NON_DEFINITION_KINDS
253+
]
254+
if not entries:
255+
return None
256+
return min(entries, key=lambda t: (_kind_rank(t["kind"]), t["line"]))

src/gtags_mcp/macros.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,24 @@ def exported_via(symbol: str, sources: Iterable[str]) -> str | None:
186186
return None
187187

188188

189+
def export_sites(symbol: str, cxrefs: Iterable[Cxref]) -> list[tuple[str, int, str]]:
190+
"""Every EXPORT_SYMBOL* site of `symbol` among its occurrence cxrefs.
191+
192+
Returns (path, line, variant) tuples, deduplicated by path, source order
193+
kept. EXPORT_SYMBOL* always sits in the .c file that defines the symbol,
194+
so these paths are ground truth for where a definition must exist — the
195+
signal export recovery uses when the parser missed one.
196+
"""
197+
sites: list[tuple[str, int, str]] = []
198+
seen: set[str] = set()
199+
for _, lineno, path, source in cxrefs:
200+
match = _EXPORT_RE.search(source)
201+
if match and match.group("name") == symbol and path not in seen:
202+
seen.add(path)
203+
sites.append((path, lineno, match.group(0)))
204+
return sites
205+
206+
189207
def resolve(
190208
symbol: str, query: Query, direct_empty: bool = True
191209
) -> tuple[list[Cxref], str | None]:

0 commit comments

Comments
 (0)