Repository navigation
v0.184.0
Upgrading: every index rebuilds once. INDEX_VERSION goes 125 → 126
because some Python call edges are no longer created (below). The MCP server
rebuilds the index when it starts; from the command line,
code-graph-mcp reindex does. To pin the binary back:
npm i -g @sdsrs/code-graph@0.183.0 (or, from source, cargo install --locked --git https://github.com/sdsrss/code-graph-mcp --tag v0.183.0 --features embed-model), then delete ~/.cache/code-graph/bin/code-graph-mcp
and set CODE_GRAPH_NO_AUTO_UPDATE=1, as described under 0.171.0; the older
binary rebuilds its own index once too.
Python: a call through an imported name no longer binds the caller's own definition
logging.FileHandler.emit(self, record) in logging/handlers.py,
codecs.IncrementalEncoder.__init__(self, errors) in encodings/utf_16.py,
or C.m() after from ext import C name a definition of another module.
When the imported module was part of the project, such a call resolved like a
bare call, so it bound every same-named definition of the caller's own file:
in the standard library, callgraph --direction callees on
BaseRotatingHandler.emit listed the 8 other emit methods of
logging/handlers.py (SocketHandler.emit, SMTPHandler.emit, …); now it
lists none of them. The call no longer binds into the caller's file when all
of these hold:
- Every binding of the call's first name in the file is an import (
import ext,import ext as e,import pkg.ext,from ext import C, undertry
or not), and the file only reads attributes rooted at it:getattr(ext, …),
f(ext),class D(ext.Base), annotations, default values (def f(x=ext.X)),
ext.tbl[0] = 1, and so on. - Nothing the name reaches may be the caller's own module. What a call
reaches is what one of the name's imports binds followed by the call's
attribute chain:import aanda.mod.go()reacha.mod,from c import modandmod.go()reachc.mod. Ina/mod.pyorsrc/a/mod.pythat is
the file itself, so those calls resolve as before; so do calls throughext
inlib/ext.pyorext/__init__.py, and through any name one of whose
imports is relative (from . import ext,from . import *).
Nothing is bound in place of the dropped edge; a call whose first name has no
same-named definition in the caller's file resolves as before. Everything else
resolves as before too: a name the file also binds some other way (an
assignment, a parameter, def, class, a loop or with … as target,
global, del, a match capture), a file that writes an attribute rooted at
the name (ext.go = go, del ext.go, ext.sub.go += 1), and a direct
ext.go() call on a name that holds one plain-imported module everywhere in
the file (since 0.176.0 it binds what from ext import go would).
Measured against 0.183.0: the Python standard library loses 517 call edges
(497 caller–callee pairs), nltk 3.9.2 170 (158) and flask 7, all same-file;
hono, leveldb, cobra and gin keep every edge. On none of the seven is an edge
added or its confidence label changed; 303 standard-library edges that stay
carry the new "loc":1 metadata key. On the standard library the
SCIP oracle's default floor (extracted + inferred) goes from 10,996 of
14,329 judged edges correct (76.7%) to 10,971 of 13,879 (79.0%): 2,908 wrong
edges instead of 3,333. The 25 correct pairs it loses, and the 5 gold calls
its recall loses, are all explicit base-class calls (logging.Handler.close(self),
Widget.destroy(self)) that the oracle credits to a subclass override in the
caller's file; Python runs the base class's method there. 54 standard-library
definitions whose only recorded callers were such calls now show no caller.
A full standard-library index took 1.3–2.7% more CPU time than 0.183.0 built
from source (two interleaved batches of five runs), and 0.2% less than the
released 0.183.0 binary (one batch).
Not covered
- A module patched at run time in a way other than writing an attribute
rooted at the call's own name:setattr(ext, "go", go),
globals()["ext"] = …,vars(ext)["go"] = go,ext.__dict__["go"] = go,
a write through another name (alias = ext; alias.go = go, a function
handedext, a list holding it, a second import of it), a write to a
segment of the import's path (pkg.b = Kbeforefrom pkg import b), a
__getattr__or__class__hook, a patch made in another file,
mock.patch,sys.modules,exec. On 11,588 generated files run under
CPython (1,973 of them inside a package they import, 792 patching the
module through another name), every call the new rule stopped binding
while Python ran the file's owngousedsetattr,globals(),vars()
or a write through another name. - A module that hands the caller's own definition back:
os.path.join
called insideposixpath.py, nltk'schunk/util.pycalling
nltk.chunk.tagstr2tree, whichnltk/chunk/__init__.pyimports from it,
or thedemo()functions of nltk'sframenet.pyandlin.py, which reach
their own file's reader throughnltk.corpus's lazy loader (from nltk.corpus import framenet as fn, thenfn.frames(); 11 edges). - A direct
ext.go()call on a name that holds one plain-imported module
everywhere in the file, wherefrom ext import gowould find nothing, keeps
its bind into the caller's file (15 wrong edges at the default floor in the
standard library). - A file that uses the name somewhere the parser does not recognise as a read
keeps every call through it as before, for examplewith ext.lock:,
yield ext.x,[ext.x for _ in y]andcase ext.CONST:. - Two import names of one module, one kept as before and one not (
import ext as m1,import ext as m2,m1.flag = 0), can store the same call edge
twice, once with the new"loc"metadata key;refs,impactand
callgraphlist it once. - A file whose syntax tree nests deeper than the import scan reads (one
expression adding 300 terms is enough; 200 is not) keeps every call
through an imported name as before.