Repository navigation
v0.183.0
Upgrading: every index rebuilds once. INDEX_VERSION goes 124 → 125
because some Python call edges bind differently (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.182.0 (or, from source, cargo install --locked --git https://github.com/sdsrss/code-graph-mcp --tag v0.182.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: C.m() on the file's own class binds the method Python runs
A call that names a class explicitly, Widget.__init__(self, …) or
ExpatBuilder.reset(self), was resolved as a call on an untyped object, so
it bound every same-named method in the caller's file. Python looks m up
along C's MRO, and an explicit C.m(self) never dispatches to a subclass.
The call now binds the method of the first class in C's MRO (Python's C3
order) whose body defines m with one def, when the caller's file decides
that class:
Cis a class the file defines once, as a plain statement of the module
(not underif,tryorwith, not decorated), with nofrom … import *
after it, and every other occurrence of its name is one the parser
recognises as a read: a call, an argument or base class, an attribute, an
annotation, an assignment's value, an operand,except C,case C(), and
so on.- Every base in
C's hierarchy is such a class of the file, defined above
the class that names it, or an import, a builtin,object, or a dotted or
subscripted name rooted at one of those (mod.Base,Generic[T];class A[T]addsGeneric). A base that is anything else leaves the call
undecided: a decorated or conditional class, an alias, a call
(with_metaclass(M, B)), a subscript of a class of the file, or a name the
file binds some other way, a second import included. Such a base can hide
classes of the file that move the MRO. - No metaclass of the file in the hierarchy (its
__new__,__prepare__or
__init__can dropm) and no two metaclasses; one from outside the file
is taken to leave the class's names alone and to answer nothing first (no
data descriptor or__getattribute__, no__new__or__prepare__that
dropsm). No class keyword other thanmetaclass=(it goes to
__init_subclass__) and no**kw. No class of the MRO has__bases__or
__class__assigned. mis found before an import, a builtin orobjectin the MRO. No class
on the way binds it any other way (a class attribute, a seconddef, a
defunderif, a name__slots__lists, a walrus in a decorator, a
default or a lambda default) or writes its namespace throughlocals(),
vars(),execoreval(in the body or a header) or a computed
__slots__. The file has noC.m = …ordel C.mon any of them
(parenthesised or starred too), and the owner has nodef mnested in
another of its methods (the index names that oneC.mas well). A method
name Python mangles, or one spelled mangled (C.__m,C._C__m), leaves the
call undecided.- The MRO,
objectaside, is at most 64 classes long, and Python accepts it:
an inconsistent order (class C(object, A)) or a duplicate base leaves the
call undecided.
Any other call resolves as before, an m from an imported base, a builtin
(Exception.__init__) or object included. super() calls are unchanged.
What the rule does not see, so a decided call can still name a method Python
does not run (each reproduced): C.m replaced or removed through another
name (E = C; E.m = f), by setattr or delattr, globals()["C"].m = …,
exec (also reached as builtins.exec or another name, run = exec), a
frame (sys._getframe(1).f_locals), from another module (a.C.m = f), by a
base's __init_subclass__ (cls.m = …) or by a descriptor's
__set_name__; __bases__ replaced by setattr or through another name;
the class name rebound through globals() or sys.modules; an imported
metaclass with a data descriptor named m, or whose __new__ drops it; a
read of C that runs before its class statement (a default value, a lambda
default, C's own body), where a star import or a builtin supplies C; a
class Python refuses for how its imported bases relate (C(E1, E2) with
E2 deriving from E1) or for their instance layout; a module that fails to
import before the call could run (another class Python refuses, a statement
that raises). In each of these the old resolution bound the same method,
among the other same-named ones. Where the method Python runs is another m
of the same file (after delattr, a rebinding of the class name or of
__bases__, an exec or a frame write, a descriptor's __set_name__, an
imported metaclass's __new__), the old resolution had that edge too, and
this one drops it.
Measured against 0.182.0's source build:
- 15,000 generated files (
tasks/d370g-kit/enum: base kinds × hierarchy
shapes × metaclasses × body bindings × patches), each run by CPython 3.14:
of the 927 calls the rule decided in files that import, 834 bound the
method CPython ran; the other 93 all hadC.mreplaced through another
name (46) or bysetattr(47). Another 358 decided calls sit in files
CPython refuses to import. Every undecided call kept 0.182.0's edges and
pending rows byte for byte. - SCIP call oracle (
scripts/scip_oracle), edges at the defaultinferred
floor, Python 3.14 standard library: 4,824 → 3,333 wrong edges; precision
11,003/15,827 (69.5%) → 10,996/14,329 (76.7%); recall unchanged
(10,764/12,375). The edge set lost 1,499extractedsame-file edges and
gained none. 7 edges the oracle counts as right left the floor; each is a
subclass override reached from an explicit base-class call, such as
ExpatBuilder.reset(self)→ExpatBuilderNS.reset. The oracle credits an
override for any call to a method; the call bindsExpatBuilder.reset. - flask 3.1.0, hono 4.6.14, leveldb 1.23, gin 1.10.0, cobra 1.8.1: edge sets
identical. - In tkinter,
impact Label.__init__ --file tkinter/__init__.pysaidRisk: HIGH, 24 direct callers (StringVar.__init__,Button.__init__, …), none
of which calls it. It now finds no caller and says so (Risk: UNKNOWNwith
the not-in-the-call-graph warning). - Full index of the standard library (569 files,
--no-embed, 9 interleaved
runs each): user CPU median 5.44 s → 5.51 s (+1.3%; the 9 paired ratios
range from −0.4% to +3.8%), wall 3.37 s → 3.29 s. An incremental run after
a class-structure edit re-reads the same caller files as before (tkinter
Widget.__init__toggled 4 times). - Files near the 1 MiB size cap built to stress the rule, user CPU (peak
memory where it was the cost): 340,000 duplicate bases 1.82 s → 2.11 s;
200,000 nested lists naming the class 0.58 s → 0.75 s; 3,000 classes with
62 bases each 8.9 s → 5.9 s; a chain of 25,758 classes, each with its own
metaclass, 3.16 s and 132 MB → 3.66 s and 162 MB (medians of 3).